mirror of
https://github.com/MiyakoYakota/search.0t.rocks.git
synced 2025-12-21 10:00:00 +00:00
Initial Commit
This commit is contained in:
40
dataapp/templates/pages/donations.html
Normal file
40
dataapp/templates/pages/donations.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<head>
|
||||
<title>Top Donators</title>
|
||||
<style>
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #f2f2f2
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
Here's the sexy people who have donated to this site:
|
||||
|
||||
<br />
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Amount</th>
|
||||
</tr>
|
||||
{{#donations}}
|
||||
<tr>
|
||||
<td>{{name}}</td>
|
||||
<td>{{amount}}</td>
|
||||
</tr>
|
||||
{{/donations}}
|
||||
</table>
|
||||
|
||||
If you would like your name here instead of a BTC address, shoot me an email <a href="mailto:miyakoyakota@riseup.com">miyakoyakota@riseup.com</a> with your transaction info.
|
||||
</body>
|
||||
42
dataapp/templates/pages/donations.json
Normal file
42
dataapp/templates/pages/donations.json
Normal file
@@ -0,0 +1,42 @@
|
||||
[
|
||||
{
|
||||
"name": "bc1qm4zyhxaktt27djp634ncx4gtev85aqy7mntszf",
|
||||
"amount": 151.81
|
||||
},
|
||||
{
|
||||
"name": "Ro****",
|
||||
"amount": 8.86
|
||||
},
|
||||
{
|
||||
"name": "bc1qqzg35y5w9zrhjc5nzdj4drjnhkpyl2yepu96fq",
|
||||
"amount": 10.22
|
||||
},
|
||||
{
|
||||
"name": "bc1qm4fvq825xmpqzz9uxt7h9muq2g84fz5cyf9uf5",
|
||||
"amount": 9.50
|
||||
},
|
||||
{
|
||||
"name": "33WNF9aM3yeDtens53Ch7T4q9smdQzB9ZY",
|
||||
"amount": 9.01
|
||||
},
|
||||
{
|
||||
"name": "bc1qqzg35y5w9zrhjc5nzdj4drjnhkpyl2yepu96fq",
|
||||
"amount": 10.21
|
||||
},
|
||||
{
|
||||
"name": "81e14f875d12895ef111a2092e2a93da8b3b7c00b0bc8fe9fc7b7f275dfcec48",
|
||||
"amount": 41.99
|
||||
},
|
||||
{
|
||||
"name": "33xdRBuineAtGGobP1zVoMdJ5vYWWrjC5S",
|
||||
"amount": 98.17
|
||||
},
|
||||
{
|
||||
"name": "Papa 0x27",
|
||||
"amount": 20.00
|
||||
},
|
||||
{
|
||||
"name": "C99.nl",
|
||||
"amount": 25.00
|
||||
}
|
||||
]
|
||||
523
dataapp/templates/pages/exports.html
Normal file
523
dataapp/templates/pages/exports.html
Normal file
@@ -0,0 +1,523 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.x/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"
|
||||
integrity="sha512-E8QSvWZ0eCLGk4km3hxSsNmGWbLtSCSUcewDQPQWZF6pEU8GlT8a5fF32wOl1i8ftdMhssTrF/OhyGWwonTcXA=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bcryptjs@2.4.3/dist/bcrypt.min.js"></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Query Export - Illicit Services</title>
|
||||
|
||||
<meta name="description" content="Export from our database of leaked information.">
|
||||
<meta name="robots" content="index, follow">
|
||||
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:title" content="Illict Services LTD" />
|
||||
<meta property="og:description" content="Export from our database of leaked information." />
|
||||
<meta property="og:url" content="https://search.illicit.services" />
|
||||
<meta property="og:site_name" content="Database Search" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="Content-Language" content="en" />
|
||||
|
||||
|
||||
<meta name="apple-mobile-web-app-status-bar" content="#000000" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
{
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app,
|
||||
v-main__wrap {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
/* */
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<v-app>
|
||||
<v-main>
|
||||
<span>
|
||||
<!-- Wallet goes in the top-right -->
|
||||
<v-toolbar flat color="transparent" dark app
|
||||
style="position: absolute; top: 0; right: 0; z-index: 1000">
|
||||
<v-toolbar-title class="white--text">
|
||||
<v-btn @click="showWallet = !showWallet" elevation="0" class="white--text" text><span
|
||||
id="creditCount" v-text="creditStr+' Credits'"></span><v-icon>
|
||||
mdi-wallet
|
||||
</v-icon></v-btn>
|
||||
</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<!-- Wallet Modal -->
|
||||
<v-dialog v-model="showWallet" max-width="500px">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="headline">Wallet Management</span>
|
||||
<v-spacer></v-spacer>
|
||||
<!-- About tooltip -->
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn icon v-on="on" @click="showAbout = !showAbout">
|
||||
<v-icon>mdi-information</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Credits currently can be used for: <ul>
|
||||
<li>Bulk data exports (thousands to hundres of thousands of records)</li>
|
||||
</ul>Credits will soon be used for: <ul>
|
||||
<li>Mass-deletion of records (avoid crypto fees)</li>
|
||||
<li>Enrichment of your own data-sets (via CSV upload)</li>
|
||||
<li>Exporting data from the visualization tool as CSV or Relational JSON
|
||||
</li>
|
||||
<li>Any other resource intensive task</li>
|
||||
</ul><br />Of course, regular search will remain free, forever.</span>
|
||||
</v-tooltip>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<!-- Allow user to copy their wallet ID -->
|
||||
<v-text-field id="walletId" v-model="walletId" label="Wallet ID" append
|
||||
append-icon="mdi-content-copy" @change="updateWalletID"></v-text-field>
|
||||
<!-- Subtle warning to user that anyone can use their credits if they have their wallet ID -->
|
||||
<v-alert dense type="warning" outlined>
|
||||
Your wallet ID is the key to your credits and exports. If you share it with
|
||||
someone,
|
||||
that person can use your credits. If you lose it, you lose your credits.
|
||||
</v-alert>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<!-- Allow user to re-fill their wallet -->
|
||||
<v-row>
|
||||
<v-col cols="10">
|
||||
<v-text-field v-model="walletFill" label="Fill Wallet (1 USD = 100)"
|
||||
append prepend-icon="mdi-currency-usd"></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="2">
|
||||
<v-btn class="my-0 mt-3" color="blue darken-1" block text
|
||||
@click="doFillWallet">
|
||||
Fill
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue darken-1" text @click="showWallet = false">
|
||||
Close
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<!-- Export Modal -->
|
||||
<v-dialog v-model="showExport" max-width="500px">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="headline">New Export</span>
|
||||
<v-spacer></v-spacer>
|
||||
<!-- About tooltip -->
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn icon v-on="on" @click="showAbout = !showAbout">
|
||||
<v-icon>mdi-information</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Exports are a way to download your data. You can export your data as a CSV
|
||||
file, or as a JSON file. The JSON file will be in a relational format, so you
|
||||
can easily import it into a database. <br /><br />You can also export your data
|
||||
from the visualization tool as CSV or Relational JSON.</span>
|
||||
</v-tooltip>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<!-- Allow user to paste in search url as input -->
|
||||
<v-text-field id="exportUrl" v-model="exportUrl" label="Search URL" append
|
||||
append-icon="mdi-content-paste"></v-text-field>
|
||||
|
||||
<!-- Input how many records to export -->
|
||||
<v-text-field id="exportCount" v-model="exportCount" label="Record Count" append
|
||||
append-icon="mdi-numeric"></v-text-field>
|
||||
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="red darken-1" text @click="showExport = false">
|
||||
Close
|
||||
</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue darken-1" text @click="doExport">
|
||||
Export
|
||||
</v-btn>
|
||||
|
||||
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</span>
|
||||
<v-container flex pa-0>
|
||||
<h1 style="margin-top: 5%">Database Exports</h1>
|
||||
<!-- Lists exports for wallet -->
|
||||
<v-card style="margin-top: 5%">
|
||||
<v-card-title>
|
||||
<span class="headline">Exports List</span>
|
||||
<v-spacer></v-spacer>
|
||||
<!-- New export button (plus icon) -->
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn icon v-on="on" @click="showExport = !showExport">
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Create a new export</span>
|
||||
</v-tooltip>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-data-table :items="exports" :headers="exportsTableHeaders">
|
||||
<!-- Download button slot -->
|
||||
<template v-slot:item.download="{ item }">
|
||||
<v-btn :disabled="item.status != 'complete'" color="blue darken-1" text
|
||||
@click="downloadExport(item)">
|
||||
Download
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
|
||||
</v-card>
|
||||
|
||||
|
||||
<small>Copyright <a href="https://illicit.services">Illicit Services LTD</a>. Email: <a
|
||||
href="mailto:miyakoyakota@riseup.com">miyakoyakota@riseup.com</a>. By using this service you agree with the <a
|
||||
href="/terms">terms of use</a>. Also, treat these downloads as "hot" storage, don't expect
|
||||
your download to stay up forever should MEGA delete it.</small>
|
||||
</v-container>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
|
||||
<script>
|
||||
const creditCountElement = document.getElementById("creditCount")
|
||||
new Vue({
|
||||
el: '#app',
|
||||
vuetify: new Vuetify({
|
||||
theme: { dark: true },
|
||||
// Mdi icons
|
||||
icons: {
|
||||
iconfont: 'mdi',
|
||||
},
|
||||
}),
|
||||
data: () => ({
|
||||
showWallet: false,
|
||||
balance: 0,
|
||||
exact: true,
|
||||
showExport: false,
|
||||
creditStr: '<loading>',
|
||||
exportUrl: '',
|
||||
walletFill: 0.0,
|
||||
exportCount: 200,
|
||||
walletId: '',
|
||||
exports: [],
|
||||
exportsTableHeaders: [
|
||||
{ text: 'Export ID', value: 'jobid' },
|
||||
{ text: 'Status', value: 'status' },
|
||||
{ text: 'Query', value: 'query' },
|
||||
{ text: 'Record Count', value: 'exportCount' },
|
||||
{ text: 'Download', value: 'download' }
|
||||
],
|
||||
hashTypes: ['BCrypt', 'MD5', 'SHA1', 'SHA256', 'SHA512', 'RIPEMD160', 'SHA3', 'SHA224', 'SHA384'],
|
||||
queryOptions: [
|
||||
'First Name',
|
||||
'Last Name',
|
||||
'Email',
|
||||
'Username',
|
||||
'Password',
|
||||
'Domain',
|
||||
'IP Address',
|
||||
'ASN Number',
|
||||
'ASN Name',
|
||||
'Continent',
|
||||
'Country',
|
||||
'Phone',
|
||||
'Address',
|
||||
'License Plate Number',
|
||||
'Birth Year',
|
||||
'VIN',
|
||||
'City',
|
||||
'State',
|
||||
'Zip',
|
||||
'Source'
|
||||
],
|
||||
queryFieldMap: {
|
||||
'First Name': 'firstName',
|
||||
'Last Name': 'lastName',
|
||||
'Email': 'emails',
|
||||
'Username': 'usernames',
|
||||
'Password': 'passwords',
|
||||
'Domain': 'domain',
|
||||
'IP Address': 'ips',
|
||||
'ASN Number': 'asn',
|
||||
'ASN Name': 'asnOrg',
|
||||
'Continent': 'continent',
|
||||
'Country': 'country',
|
||||
'Phone': 'phoneNumbers',
|
||||
'Address': 'address',
|
||||
'License Plate Number': 'VRN',
|
||||
'Birth Year': 'birthYear',
|
||||
'VIN': 'vin',
|
||||
'City': 'city',
|
||||
'State': 'state',
|
||||
'Zip': 'zipCode',
|
||||
'Source': 'source'
|
||||
}
|
||||
}),
|
||||
mounted() {
|
||||
// Check if ?url= is in the URL
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.has('url')) {
|
||||
this.exportUrl = decodeURIComponent(urlParams.get('url'));
|
||||
this.showExport = true;
|
||||
}
|
||||
|
||||
|
||||
// Check if wallet is in local storage
|
||||
if (localStorage.getItem('walletId')) {
|
||||
console.log('wallet found')
|
||||
this.walletId = localStorage.getItem('walletId')
|
||||
// Get balance
|
||||
fetch(`/wallet/${this.walletId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(`wallet balance: ${data.credits}`)
|
||||
this.balance = data.credits
|
||||
this.creditStr = data.credits.toString()
|
||||
})
|
||||
|
||||
// Get exports
|
||||
fetch(`/exports/${this.walletId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(`wallet exports: ${data}`)
|
||||
this.exports = data
|
||||
})
|
||||
} else {
|
||||
// Create a new wallet ID (uuid)
|
||||
this.walletId = this.uuidv4()
|
||||
console.log('wallet created: ' + this.walletId)
|
||||
localStorage.setItem('walletId', this.walletId)
|
||||
this.balance = 0
|
||||
this.creditStr = '0.0'
|
||||
|
||||
// Get balance
|
||||
fetch(`/wallet/${this.walletId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(`wallet balance: ${data.credits}`)
|
||||
this.balance = data.credits
|
||||
this.creditStr = data.credits.toString()
|
||||
})
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateWalletID(id) {
|
||||
if (typeof id === 'string') {
|
||||
this.walletId = id
|
||||
console.log('wallet updated: ' + id)
|
||||
localStorage.setItem('walletId', id)
|
||||
}
|
||||
},
|
||||
downloadExport(item) {
|
||||
// Open item.link in new tab
|
||||
window.open(item.link, '_blank')
|
||||
},
|
||||
goToMap() {
|
||||
window.location.href = `/map`
|
||||
},
|
||||
isMobile() {
|
||||
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
uuidv4() {
|
||||
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
|
||||
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
||||
);
|
||||
},
|
||||
doExport() {
|
||||
// Check if wallet is valid
|
||||
if (this.walletId.length !== 36) {
|
||||
alert('Invalid wallet ID')
|
||||
return
|
||||
}
|
||||
|
||||
// Check if export count is valid
|
||||
if (this.exportCount > 100000) {
|
||||
alert('Max 100,000 records per export')
|
||||
return
|
||||
}
|
||||
|
||||
const exportUrlParams = new URL(this.exportUrl).searchParams;
|
||||
|
||||
// Construct the /export URL with the same query parameters
|
||||
const exportUrl = new URL('/export', window.location.origin);
|
||||
exportUrl.search = exportUrlParams.toString();
|
||||
|
||||
if (this.exportCount <= 100) {
|
||||
return this.actuallyDoExport(exportUrl)
|
||||
}
|
||||
|
||||
// Calculate cost. 1 credit = 10 records, first 100 records are free
|
||||
const cost = (this.exportCount - 100) / 10
|
||||
|
||||
// Check if user has enough credits
|
||||
if (this.balance < cost) {
|
||||
alert('Not enough credits')
|
||||
return
|
||||
}
|
||||
|
||||
// Confirm export
|
||||
if (!confirm(`Export ${this.exportCount} records for ${cost} credits?`)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.actuallyDoExport(exportUrl)
|
||||
|
||||
// Deduct from local wallet
|
||||
this.balance -= cost
|
||||
this.creditStr = this.balance.toString()
|
||||
|
||||
// Close modal
|
||||
this.showExport = false
|
||||
// reset search url
|
||||
this.exportUrl = ''
|
||||
// reset export count
|
||||
this.exportCount = 200
|
||||
|
||||
// If "url" is in the URL, remove it
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.has('url')) {
|
||||
urlParams.delete('url')
|
||||
window.history.replaceState({}, document.title, `${window.location.pathname}?${urlParams.toString()}`);
|
||||
}
|
||||
},
|
||||
actuallyDoExport(exportUrl) {
|
||||
fetch(exportUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
walletId: this.walletId,
|
||||
exportCount: this.exportCount
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('Export started... check back soon')
|
||||
this.exports.push({
|
||||
query: new URL(exportUrl).searchParams.toString(),
|
||||
exportCount: this.exportCount,
|
||||
status: 'pending',
|
||||
...data
|
||||
})
|
||||
console.log(this.exports)
|
||||
} else {
|
||||
alert('Error exporting. Credits have not been deducted')
|
||||
}
|
||||
})
|
||||
},
|
||||
doFillWallet() {
|
||||
const walletId = this.walletId
|
||||
// 1USD = 100 Credits
|
||||
const amount = this.walletFill * 100
|
||||
|
||||
// Check if amount is valid
|
||||
if (amount < 100) {
|
||||
alert('Minimum $1')
|
||||
return
|
||||
}
|
||||
|
||||
// Check if wallet is valid
|
||||
if (walletId.length !== 36) {
|
||||
alert('Invalid wallet ID')
|
||||
return
|
||||
}
|
||||
|
||||
window.location = `/fillWallet/${walletId}/${amount}`
|
||||
},
|
||||
copyWalletId() {
|
||||
// Method 1
|
||||
const el = document.createElement('textarea');
|
||||
el.value = this.walletId;
|
||||
document.body.appendChild(el);
|
||||
el.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(el);
|
||||
|
||||
// Method 2
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(this.walletId)
|
||||
}
|
||||
|
||||
// Method 3
|
||||
const input = document.getElementById('walletId');
|
||||
input.focus();
|
||||
input.select();
|
||||
document.execCommand('copy');
|
||||
},
|
||||
buildQuery() {
|
||||
let query = ''
|
||||
this.queries.forEach((q, idx) => {
|
||||
const field = this.queryFieldMap[q.field];
|
||||
const value = q.value;
|
||||
|
||||
if (q.not) {
|
||||
if (idx === 0) {
|
||||
query += `not${field}=${q.value}`;
|
||||
} else {
|
||||
query += `¬${field}=${q.value}`;
|
||||
}
|
||||
} else {
|
||||
if (idx === 0) {
|
||||
query += `${field}=${q.value}`;
|
||||
} else {
|
||||
query += `&${field}=${q.value}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (this.exact) {
|
||||
query += `&exact=true`;
|
||||
}
|
||||
});
|
||||
|
||||
return query
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
409
dataapp/templates/pages/home.mustache
Normal file
409
dataapp/templates/pages/home.mustache
Normal file
@@ -0,0 +1,409 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.x/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js" integrity="sha512-E8QSvWZ0eCLGk4km3hxSsNmGWbLtSCSUcewDQPQWZF6pEU8GlT8a5fF32wOl1i8ftdMhssTrF/OhyGWwonTcXA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bcryptjs@2.4.3/dist/bcrypt.min.js"></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Database Search - Illicit Services</title>
|
||||
|
||||
<meta name="description" content="Search our database of leaked information. All information is in the public domain and has been compiled into one search engine.">
|
||||
<meta name="robots" content="index, follow">
|
||||
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:title" content="Illict Services LTD" />
|
||||
<meta property="og:description" content="Search our database of leaked information. All information is in the public domain and has been compiled into one search engine." />
|
||||
<meta property="og:url" content="https://search.illicit.services" />
|
||||
<meta property="og:site_name" content="Database Search" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="Content-Language" content="en" />
|
||||
|
||||
|
||||
<meta name="apple-mobile-web-app-status-bar" content="#000000" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<style>
|
||||
html,
|
||||
body, {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
html,
|
||||
body, #app, v-main__wrap {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
/* */
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<v-app>
|
||||
<v-main>
|
||||
<span>
|
||||
<!-- Wallet goes in the top-right -->
|
||||
<v-toolbar flat color="transparent" dark app style="position: absolute; top: 0; right: 0; z-index: 1000">
|
||||
<v-toolbar-title class="white--text">
|
||||
<v-btn @click="showWallet = !showWallet" elevation="0" class="white--text" text><span id="creditCount" v-text="creditStr+' Credits'"></span><v-icon>
|
||||
mdi-wallet
|
||||
</v-icon></v-btn>
|
||||
</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<!-- Wallet Modal -->
|
||||
<v-dialog v-model="showWallet" max-width="500px">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="headline">Wallet Management</span>
|
||||
<v-spacer></v-spacer>
|
||||
<!-- About tooltip -->
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn icon v-on="on" @click="showAbout = !showAbout">
|
||||
<v-icon>mdi-information</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Credits currently can be used for: <ul><li>Bulk data exports (thousands to hundres of thousands of records)</li></ul>Credits will soon be used to allow for: <ul><li>Mass-deletion of records (avoid crypto fees)</li><li>Enrichment of your own data-sets (via CSV upload)</li><li>Exporting data from the visualization tool as CSV or Relational JSON</li><li>Any other resource intensive task</li></ul><br/>Of course, regular search will remain free, forever.</span>
|
||||
</v-tooltip>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<!-- Allow user to copy their wallet ID -->
|
||||
<v-text-field id="walletId" v-model="walletId" label="Wallet ID" append append-icon="mdi-content-copy" @click:append="copyWalletId" @change="updateWalletID"></v-text-field>
|
||||
<!-- Subtle warning to user that anyone can use their credits if they have their wallet ID -->
|
||||
<v-alert dense type="warning" outlined>
|
||||
Your wallet ID is the key to your credits. If you share it with someone, that person can use your credits. If you lose it, you lose your credits.
|
||||
</v-alert>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<!-- Allow user to re-fill their wallet -->
|
||||
<v-row>
|
||||
<v-col cols="10">
|
||||
<v-text-field v-model="walletFill" label="Fill Wallet (1 USD = 100)" append prepend-icon="mdi-currency-usd"></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="2">
|
||||
<v-btn class="my-0 mt-3" color="blue darken-1" block text @click="doFillWallet">
|
||||
Fill
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue darken-1" text @click="showWallet = false">
|
||||
Close
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</span>
|
||||
<v-container flex pa-0>
|
||||
<h1 style="margin-top: 5%">Records Search</h1>
|
||||
<h2 style="margin-top: 2%">{{count}} Records in DB</h2>
|
||||
<h3 style="margin-top: 2%"><a href="/map" target="_blank">Try the Map</a>! | <a href="/dear_peter">A Letter to Peter Kleissner</a> | <a href="https://t.me/illsvc">Join the Telegram</a> | <a href="/faq">FAQ</a> | <a href="/donations">Donations</a> | <a href="/exports">Exports</a></h3>
|
||||
|
||||
<!-- Align card to center of page-->
|
||||
<v-card elevation="0" style="background-color: transparent">
|
||||
<v-row>
|
||||
<v-col md="3">
|
||||
<h4 style="margin-top: 12%">Search Something...</h4>
|
||||
</v-col>
|
||||
<v-col md="2">
|
||||
<v-checkbox v-model="exact" label="exact?"></v-checkbox>
|
||||
</v-col>
|
||||
<v-col md="7">
|
||||
<v-spacer />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row v-for="query, idx in queries" style="width: 100%; margin-top: 2%">
|
||||
<v-col class="my-0 py-1 mx-0 px-0" md="1">
|
||||
<v-btn v-if="idx === 0" @click="addQuery" elevation="0" class="white--text" text
|
||||
fab><v-icon>
|
||||
mdi-plus
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else @click="queries.splice(idx, 1)" elevation="0" class="white--text" text
|
||||
fab><v-icon>
|
||||
mdi-minus
|
||||
</v-icon>
|
||||
</v-col>
|
||||
<v-col md="2">
|
||||
<v-select class="my-0 py-0 mx-0 px-0" v-model="query.field" :items="queryOptions" autocomplete="off"></v-select>
|
||||
</v-col>
|
||||
<v-col v-if="!isMobile()" md="1">
|
||||
<v-checkbox class="my-0 mx-0 px-0" v-model="query.not" label="Is Not"></v-checkbox>
|
||||
</v-col>
|
||||
<v-col v-if="query.field !== 'Password'" md="6">
|
||||
<v-text-field class="my-0 py-0 mx-0 px-0" v-model="query.value"></v-text-field>
|
||||
</v-col>
|
||||
<v-col v-if="query.field === 'Password'" md="4">
|
||||
<v-text-field class="my-0 py-0 mx-0 px-0" v-model="query.value"></v-text-field>
|
||||
</v-col>
|
||||
<v-col v-if="query.field === 'Password'" md="2">
|
||||
<v-checkbox class="my-0 mx-0 px-0" v-model="query.extendedSearch" label="Extended Search"></v-text-field>
|
||||
</v-col>
|
||||
<v-col md="2">
|
||||
<v-btn @click="buildAndSendQuery" v-if="idx === 0" class="my-0 py-0" elevation="0" class="white--text"
|
||||
text><v-icon>
|
||||
mdi-account-search
|
||||
</v-icon></v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
|
||||
<div style="margin-left: 10px">
|
||||
<p style="margin-top: 5%">Example Queries:</p>
|
||||
<p><a href="/records?firstName=Troy&lastName=Hunt">Name: Troy Hunt</a></p>
|
||||
<p><a href="/records?emails=larrytsantos@yahoo.com">Email: larrytsantos@yahoo.com</a></p>
|
||||
<p><a href="/records?usernames=Oni">Username: Oni</a></p>
|
||||
<p><a href="/records?VRN=kjg7920">License Plate: KJG7920</a></p>
|
||||
</div>
|
||||
<div style="margin-left: 10px">
|
||||
<p>If you find these services useful, BTC and Monero donations are appreciated. <br /> BTC: bc1qel7fg6yd96fp3yjh94av8pssz8er2yeluylzcw <br />XMR: 44y4PfBf2TmfgGEX7vAZ7MdCBohAuwDGee8BjwbVfUP75nmEQqbB3rDLRTfEcbkHq33ZVoofMrEX63fVPxHcsVv8AnD9KRo</p>
|
||||
</div>
|
||||
|
||||
|
||||
<small>Copyright <a href="https://illicit.services">Illicit Services LTD</a>. By using this service you agree with the <a href="/terms">terms of use</a>. <a href="/canary">Canary</a></small>
|
||||
</v-container>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
|
||||
<script>
|
||||
const creditCountElement = document.getElementById("creditCount")
|
||||
new Vue({
|
||||
el: '#app',
|
||||
vuetify: new Vuetify({
|
||||
theme: { dark: true },
|
||||
// Mdi icons
|
||||
icons: {
|
||||
iconfont: 'mdi',
|
||||
},
|
||||
}),
|
||||
data: () => ({
|
||||
queries: [
|
||||
{ field: 'First Name', value: '' },
|
||||
],
|
||||
showWallet: false,
|
||||
balance: 0,
|
||||
exact: false,
|
||||
creditStr: '<loading>',
|
||||
walletFill: 0.0,
|
||||
walletId: '',
|
||||
hashTypes: ['BCrypt', 'MD5', 'SHA1', 'SHA256', 'SHA512', 'RIPEMD160', 'SHA3', 'SHA224', 'SHA384'],
|
||||
queryOptions: [
|
||||
'First Name',
|
||||
'Last Name',
|
||||
'Email',
|
||||
'Username',
|
||||
'Password',
|
||||
'Domain',
|
||||
'IP Address',
|
||||
'ASN Number',
|
||||
'ASN Name',
|
||||
'Continent',
|
||||
'Country',
|
||||
'Phone',
|
||||
'Address',
|
||||
'License Plate Number',
|
||||
'Birth Year',
|
||||
'VIN',
|
||||
'City',
|
||||
'State',
|
||||
'Zip',
|
||||
'Source'
|
||||
],
|
||||
queryFieldMap: {
|
||||
'First Name': 'firstName',
|
||||
'Last Name': 'lastName',
|
||||
'Email': 'emails',
|
||||
'Username': 'usernames',
|
||||
'Password': 'passwords',
|
||||
'Domain': 'domain',
|
||||
'IP Address': 'ips',
|
||||
'ASN Number': 'asn',
|
||||
'ASN Name': 'asnOrg',
|
||||
'Continent': 'continent',
|
||||
'Country': 'country',
|
||||
'Phone': 'phoneNumbers',
|
||||
'Address': 'address',
|
||||
'License Plate Number': 'VRN',
|
||||
'Birth Year': 'birthYear',
|
||||
'VIN': 'vin',
|
||||
'City': 'city',
|
||||
'State': 'state',
|
||||
'Zip': 'zipCode',
|
||||
'Source': 'source'
|
||||
}
|
||||
}),
|
||||
mounted() {
|
||||
// Check if wallet is in local storage
|
||||
if (localStorage.getItem('walletId')) {
|
||||
console.log('wallet found')
|
||||
this.walletId = localStorage.getItem('walletId')
|
||||
// Get balance
|
||||
fetch(`/wallet/${this.walletId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(`wallet balance: ${data.credits}`)
|
||||
this.balance = data.credits
|
||||
this.creditStr = data.credits.toString()
|
||||
})
|
||||
} else {
|
||||
// Create a new wallet ID (uuid)
|
||||
this.walletId = this.uuidv4()
|
||||
console.log('wallet created: '+this.walletId)
|
||||
localStorage.setItem('walletId', this.walletId)
|
||||
this.balance = 0
|
||||
this.creditStr = '0.0'
|
||||
|
||||
// Get balance
|
||||
fetch(`/wallet/${this.walletId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(`wallet balance: ${data.credits}`)
|
||||
this.balance = data.credits
|
||||
this.creditStr = data.credits.toString()
|
||||
})
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateWalletID(id) {
|
||||
if (typeof id === 'string') {
|
||||
this.walletId = id
|
||||
console.log('wallet updated: '+id)
|
||||
localStorage.setItem('walletId', id)
|
||||
}
|
||||
},
|
||||
addQuery() {
|
||||
// Limit 5 queries
|
||||
if (this.queries.length >= 5) {
|
||||
alert('Max 5 fields per query')
|
||||
} else {
|
||||
this.queries.push({ field: 'First Name', value: '' })
|
||||
}
|
||||
},
|
||||
goToMap() {
|
||||
window.location.href = `/map`
|
||||
},
|
||||
isMobile() {
|
||||
if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
uuidv4() {
|
||||
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
|
||||
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
||||
);
|
||||
},
|
||||
doFillWallet() {
|
||||
const walletId = this.walletId
|
||||
// 1USD = 100 Credits
|
||||
const amount = this.walletFill * 100
|
||||
|
||||
// Check if amount is valid
|
||||
if (amount < 100) {
|
||||
alert('Minimum $1')
|
||||
return
|
||||
}
|
||||
|
||||
// Check if wallet is valid
|
||||
if (walletId.length !== 36) {
|
||||
alert('Invalid wallet ID')
|
||||
return
|
||||
}
|
||||
|
||||
window.location = `/fillWallet/${walletId}/${amount}`
|
||||
},
|
||||
copyWalletId() {
|
||||
// Method 1
|
||||
const el = document.createElement('textarea');
|
||||
el.value = this.walletId;
|
||||
document.body.appendChild(el);
|
||||
el.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(el);
|
||||
|
||||
// Method 2
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(this.walletId)
|
||||
}
|
||||
|
||||
// Method 3
|
||||
const input = document.getElementById('walletId');
|
||||
input.focus();
|
||||
input.select();
|
||||
document.execCommand('copy');
|
||||
},
|
||||
buildAndSendQuery() {
|
||||
let query = ''
|
||||
this.queries.forEach((q, idx) => {
|
||||
const field = this.queryFieldMap[q.field];
|
||||
const value = q.value;
|
||||
|
||||
if (q.not) {
|
||||
if (idx === 0) {
|
||||
query += `not${field}=${q.value}`;
|
||||
} else {
|
||||
query += `¬${field}=${q.value}`;
|
||||
}
|
||||
} else {
|
||||
if (idx === 0) {
|
||||
query += `${field}=${q.value}`;
|
||||
} else {
|
||||
query += `&${field}=${q.value}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (q.extendedSearch && field === "passwords") {
|
||||
this.hashTypes.forEach((hashType) => {
|
||||
if (hashType === 'BCrypt') {
|
||||
for (let i = 0; i <= 15; i++) {
|
||||
console.log(`Generating ${hashType} hash (rotation ${i}) for "${value}"...`)
|
||||
const hashValue = dcodeIO.bcrypt.hashSync(value, i);
|
||||
console.log(`${hashType} for "${value}" is "${hashValue}"`)
|
||||
query += `&passwords=${hashValue}`;
|
||||
}
|
||||
} else {
|
||||
console.log(`Generating ${hashType} hash for ${value}...`)
|
||||
const hashValue = CryptoJS[hashType](value).toString();
|
||||
console.log(`${hashType} for ${value} is ${hashValue}`)
|
||||
query += `&passwords=${hashValue}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (this.exact) {
|
||||
query += `&exact=true`;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Send query
|
||||
window.location.href = `/records?${query}`
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
41
dataapp/templates/pages/recordById.mustache
Normal file
41
dataapp/templates/pages/recordById.mustache
Normal file
@@ -0,0 +1,41 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:title" content="Record Report - {{id}}" />
|
||||
<meta name="description" content="Database Record Report {{#record}}{{key}}: {{value}} {{/record}}">
|
||||
<meta property="og:description" content="Database Record Report {{#record}}{{key}}: {{value}} {{/record}}" />
|
||||
<meta property="og:site_name" content="Database Search" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="Content-Language" content="en" />
|
||||
<meta name="robots" content="index, follow">
|
||||
<title>Record Report - {{id}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Record Report</h1>
|
||||
<table>
|
||||
{{#record}}
|
||||
<tr>
|
||||
<th>{{key}}</th>
|
||||
<td>{{value}}</td>
|
||||
</tr>
|
||||
{{/record}}
|
||||
</table>
|
||||
|
||||
<br />
|
||||
<a href='/visualize?id={{id}}' rel="nofollow">Visualize</a>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<h3>Related Records</h3>
|
||||
|
||||
{{#related}}
|
||||
<dl>
|
||||
<dt><a href="/documents/by_id/{{id}}">{{id}}</a></dt>
|
||||
{{#record}}
|
||||
<dd>{{key}}: {{value}}</dd>
|
||||
{{/record}}
|
||||
</dl>
|
||||
{{/related}}
|
||||
|
||||
|
||||
</html>
|
||||
36
dataapp/templates/pages/recordsListing.mustache
Normal file
36
dataapp/templates/pages/recordsListing.mustache
Normal file
@@ -0,0 +1,36 @@
|
||||
<html>
|
||||
<!-- Lists all record's first names, at the bottom of the page there's options to choose a-z -->
|
||||
<head>
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:title" content="Record Report" />
|
||||
<meta property="og:description" content="Database Record Report {{#record}}{{key}}: {{value}} {{/record}}" />
|
||||
<meta property="og:site_name" content="Database Search" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="Content-Language" content="en" />
|
||||
<meta name="robots" content="index, follow">
|
||||
<title>Records Result - {{count}} Results</title>
|
||||
|
||||
<script>
|
||||
function exportMore() {
|
||||
const encodedUrl = encodeURIComponent(window.location.href);
|
||||
window.location.href = `/exports?url=${encodedUrl}`;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Records Result</h1>
|
||||
<h2>{{resultCount}} Hits - {{count}} Results Shown <button onclick="exportMore()">Click here for more</button></h2>
|
||||
{{#records}}
|
||||
<div class="record">
|
||||
<dl>
|
||||
<dt>
|
||||
<a href="/documents/by_id/{{ id }}">{{id}}</a> - <a href='/visualize?id={{id}}' rel="nofollow">(Visualize)</a> - <a href="/faq" rel="nofollow">(See FAQ about Removal)</a></dt>
|
||||
{{#fields}}
|
||||
|
||||
<dd> {{ . }}</a></dd>
|
||||
{{/fields}}
|
||||
</dl>
|
||||
</div>
|
||||
{{/records}}
|
||||
</body>
|
||||
</html>
|
||||
298
dataapp/templates/pages/visualizer.html
Normal file
298
dataapp/templates/pages/visualizer.html
Normal file
@@ -0,0 +1,298 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Cytoscape JS App</title>
|
||||
<script src="https://unpkg.com/cytoscape@3.19.0/dist/cytoscape.min.js"></script>
|
||||
<style>
|
||||
#cy {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
/* Add this to your CSS file or inside a <style> tag */
|
||||
#floating-menu {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background-color: white;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
#floating-menu label,
|
||||
#floating-menu select,
|
||||
#floating-menu input {
|
||||
margin-right: 5px;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/qtip2/3.0.3/jquery.qtip.min.css" />
|
||||
<!-- Add these lines inside the <head> tag -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/tippy.js@6.3.1/dist/tippy.css" />
|
||||
<!-- Add these lines before the app.js script -->
|
||||
<script src="https://unpkg.com/@popperjs/core@2"></script>
|
||||
<script src="https://cytoscape.org/cytoscape.js-popper/cytoscape-popper.js"></script>
|
||||
<script src="https://unpkg.com/tippy.js@6"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Add this inside the <body> tag -->
|
||||
<div id="floating-menu">
|
||||
<label for="layout-select">Graph Type:</label>
|
||||
<select id="layout-select">
|
||||
<option value="cose">Cose</option>
|
||||
<option value="grid">Grid</option>
|
||||
<option value="circle">Circle</option>
|
||||
<option value="concentric">Concentric</option>
|
||||
<option value="breadthfirst">Breadthfirst</option>
|
||||
</select>
|
||||
<label for="min-similarity">Minimum Similarity Score:</label>
|
||||
<input type="number" id="min-similarity" min="0" value="10" />
|
||||
<br />
|
||||
<p>Statistics: The last query returned <span id="lastQueryCount">0</span> similar results, of which <span id="lastQueryShown">0</span> were shown. The lowest similarity score was <span id="lastQueryLowestScore">0</span>.</p>
|
||||
</div>
|
||||
|
||||
<div id="cy"></div>
|
||||
<script>
|
||||
const tips = {}
|
||||
|
||||
const layoutSelect = document.getElementById('layout-select');
|
||||
const minSimilarityInput = document.getElementById('min-similarity');
|
||||
|
||||
const lastQueryCount = document.getElementById("lastQueryCount")
|
||||
const lastQueryShownCount = document.getElementById("lastQueryShown")
|
||||
const lastQueryShownLowestScore = document.getElementById("lastQueryLowestScore")
|
||||
|
||||
|
||||
// Set default values
|
||||
let selectedLayout = 'circle';
|
||||
let minSimilarity = 10;
|
||||
|
||||
|
||||
// Initialize Cytoscape instance
|
||||
const cy = cytoscape({
|
||||
container: document.getElementById('cy'),
|
||||
elements: [],
|
||||
style: [
|
||||
{
|
||||
selector: 'node',
|
||||
style: {
|
||||
'background-color': '#0074D9',
|
||||
'label': 'data(id)'
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'edge',
|
||||
style: {
|
||||
'width': 3,
|
||||
'line-color': '#7FDBFF',
|
||||
'curve-style': 'bezier'
|
||||
}
|
||||
}
|
||||
],
|
||||
layout: {
|
||||
name: selectedLayout
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Function to apply the selected layout
|
||||
function applyLayout() {
|
||||
cy.layout({ name: selectedLayout }).run();
|
||||
}
|
||||
|
||||
// Event listener for layout selection
|
||||
layoutSelect.addEventListener('change', (event) => {
|
||||
selectedLayout = event.target.value;
|
||||
applyLayout();
|
||||
});
|
||||
|
||||
// Event listener for minimum similarity input
|
||||
minSimilarityInput.addEventListener('input', (event) => {
|
||||
|
||||
minSimilarity = parseInt(event.target.value);
|
||||
console.log("New minimum similarity: " + minSimilarity)
|
||||
});
|
||||
|
||||
// Apply the default layout when the graph is loaded
|
||||
applyLayout();
|
||||
|
||||
|
||||
var makeTippy = function (ele, text) {
|
||||
var ref = ele.popperRef();
|
||||
|
||||
// Since tippy constructor requires DOM element/elements, create a placeholder
|
||||
var dummyDomEle = document.createElement('div');
|
||||
|
||||
var tip = tippy(dummyDomEle, {
|
||||
getReferenceClientRect: ref.getBoundingClientRect,
|
||||
trigger: 'manual', // mandatory
|
||||
// dom element inside the tippy:
|
||||
content: function () { // function can be better for performance
|
||||
var div = document.createElement('div');
|
||||
|
||||
div.innerHTML = text;
|
||||
|
||||
return div;
|
||||
},
|
||||
// your own preferences:
|
||||
arrow: true,
|
||||
placement: 'bottom',
|
||||
hideOnClick: false,
|
||||
sticky: "reference",
|
||||
|
||||
// if interactive:
|
||||
interactive: true,
|
||||
appendTo: document.body // or append dummyDomEle to document.body
|
||||
});
|
||||
|
||||
return tip;
|
||||
};
|
||||
|
||||
function createDocSummary(doc) {
|
||||
const blacklist = ['id', '_version_', 'line', 'notes']
|
||||
const summary = []
|
||||
|
||||
for (const key in doc) {
|
||||
if (!blacklist.includes(key)) {
|
||||
summary.push(`${key}: ${doc[key]}<br />`)
|
||||
}
|
||||
}
|
||||
|
||||
return summary.join('\n')
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Function to add new nodes (modified to include tooltip creation)
|
||||
function addDocument(record) {
|
||||
const id = record.id
|
||||
|
||||
let newNode = null;
|
||||
|
||||
try {
|
||||
newNode = cy.add({
|
||||
group: 'nodes',
|
||||
data: { id },
|
||||
});
|
||||
|
||||
const tippy = makeTippy(newNode, createDocSummary(record));
|
||||
|
||||
tips[id] = tippy
|
||||
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
|
||||
cy.layout({
|
||||
name: selectedLayout,
|
||||
minNodeSpacing: 200
|
||||
}).run();
|
||||
|
||||
return newNode
|
||||
|
||||
// cy.layout({ name: 'grid', rows: 1 }).run();
|
||||
}
|
||||
|
||||
|
||||
// Function to create links between nodes
|
||||
function createLink(source, target) {
|
||||
cy.add({
|
||||
group: 'edges',
|
||||
data: {
|
||||
id: `${source}-${target}`,
|
||||
source,
|
||||
target
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Event handler for node click
|
||||
cy.on('tap', 'node', async function (event) {
|
||||
const nodeId = event.target.data('id');
|
||||
try {
|
||||
const response = await fetch(`/documents/by_id/${nodeId}?wt=json&moreLikeThis=true`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
lastQueryCount.innerText = data.related.length
|
||||
|
||||
let countShown = 0
|
||||
|
||||
let lowestScore = 1000000;
|
||||
|
||||
data.related.forEach((record) => {
|
||||
if (record['similarity score'] < lowestScore) {
|
||||
lowestScore = record['similarity score']
|
||||
}
|
||||
if (record['similarity score'] >= minSimilarity) {
|
||||
addDocument(record)
|
||||
createLink(nodeId, record.id)
|
||||
countShown += 1
|
||||
console.log(record['similarity score'])
|
||||
}
|
||||
})
|
||||
if (lowestScore === 1000000) {
|
||||
lastQueryShownLowestScore.innerText = 0
|
||||
} else {
|
||||
lastQueryShownLowestScore.innerText = lowestScore
|
||||
}
|
||||
} else {
|
||||
console.error('Error fetching data from API:', response.status, response.statusText);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching data from API:', error);
|
||||
}
|
||||
});
|
||||
|
||||
cy.on('mouseover', 'node', function (event) {
|
||||
tips[event.target.id()].show()
|
||||
});
|
||||
|
||||
cy.on('mouseout', 'node', function (event) {
|
||||
tips[event.target.id()].hide()
|
||||
});
|
||||
|
||||
|
||||
(async () => {
|
||||
// Get URL parameters
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const nodeId = urlParams.get('id');
|
||||
|
||||
if (nodeId) {
|
||||
const response = await fetch(`/documents/by_id/${nodeId}?wt=json`);
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (nodeId) {
|
||||
const initialNode = addDocument(data.record);
|
||||
const initialNodeTip = makeTippy(initialNode, "Click Me!");
|
||||
initialNodeTip.show()
|
||||
|
||||
setTimeout(() => {
|
||||
initialNodeTip.hide()
|
||||
}, 7000)
|
||||
}
|
||||
}
|
||||
else {
|
||||
alert("Error fetching from API")
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user