This commit is contained in:
tree 2023-07-05 15:57:37 +00:00
rodič 6a6d5fb807
revize 8693a52a71
29 změnil soubory, kde provedl 414 přidání a 234 odebrání

Zobrazit soubor

@ -26,6 +26,9 @@ repo-crawler:
repo-worker:
deno run --unstable --allow-net --allow-read --allow-write --allow-env --allow-ffi --allow-sys ./backend/repo-worker.js
firehose:
deno run --unstable --allow-net --allow-read --allow-env --allow-sys --allow-ffi ./backend/firehose.js
fe-rebuild:
cd frontend && npm run build && pm2 restart atscan-fe

Zobrazit soubor

@ -92,6 +92,7 @@ function prepareObject(type, item) {
case "pds":
item.host = item.url.replace(/^https?:\/\//, "");
item.fed = findPDSFed(item);
item.err = Boolean(item.inspect?.current?.err);
item.status = !item.inspect
? "unknown"
: (item.inspect?.current.err ? "offline" : "online");

Zobrazit soubor

@ -1,15 +1,41 @@
import {
ComAtprotoSyncSubscribeRepos,
subscribeRepos,
SubscribeReposMessage,
//SubscribeReposMessage,
} from "npm:atproto-firehose";
import * as atprotoApi from "npm:@atproto/api";
const { AppBskyActorProfile } = atprotoApi.default;
const client = subscribeRepos(`wss://bsky.social`, { decodeRepoOps: true });
client.on("message", (m) => {
//console.log(m)
if (ComAtprotoSyncSubscribeRepos.isHandle(m)) {
console.log("handle", m);
}
if (ComAtprotoSyncSubscribeRepos.isCommit(m)) {
//console.log(m)
m.ops.forEach((op) => {
//console.log(op.payload)
console.log(op.payload?.$type);
if (!op.payload) {
console.log(m);
}
if (op.payload?.$type === "app.bsky.actor.profile") {
if (AppBskyActorProfile.isRecord(op.payload)) {
console.log(`Profile updated: ${m.repo}`);
}
}
});
}
if (ComAtprotoSyncSubscribeRepos.isHandle(m)) {
console.log("handle update", m);
}
if (ComAtprotoSyncSubscribeRepos.isMigrate(m)) {
console.log("migrate update", m);
}
if (ComAtprotoSyncSubscribeRepos.isTombstone(m)) {
console.log("tombstone update", m);
}
if (ComAtprotoSyncSubscribeRepos.isInfo(m)) {
console.log("info update", m);
}
});

Zobrazit soubor

@ -56,14 +56,22 @@ async function crawlUrl(ats, url, host = "local") {
return { err: "unknown host" };
}
const codec = ats.JSONCodec;
const resp = await ats.nats.request(
`ats-nodes.${host}.http`,
codec.encode({ url }),
{
timeout: 60000,
},
);
const { err, data, ms } = codec.decode(resp.data);
let err, data, ms;
try {
const resp = await ats.nats.request(
`ats-nodes.${host}.http`,
codec.encode({ url }),
{
timeout: 60000,
},
);
const decoded = codec.decode(resp.data);
err = decoded.err;
data = decoded.data;
ms = decoded.ms;
} catch (e) {
err = e.message;
}
return { err, data, ms };
}
@ -149,11 +157,11 @@ async function crawl(ats) {
"ats.service.pds.update",
ats.JSONCodec.encode({ url: i.url }),
);
console.log(
/*console.log(
`[${chost}] -> ${i.url} ${ms ? "[" + ms + "ms]" : ""} ${
err ? "error = " + err : ""
}`,
);
);*/
}),
);
});

Zobrazit soubor

@ -1,85 +0,0 @@
{
"name": "typesense",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "typesense",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"typesense": "^1.5.4"
}
},
"node_modules/@babel/runtime": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz",
"integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==",
"peer": true,
"dependencies": {
"regenerator-runtime": "^0.13.11"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/axios": {
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
"dependencies": {
"follow-redirects": "^1.14.8"
}
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/loglevel": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz",
"integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==",
"engines": {
"node": ">= 0.6.0"
},
"funding": {
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/loglevel"
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"peer": true
},
"node_modules/typesense": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/typesense/-/typesense-1.5.4.tgz",
"integrity": "sha512-51/lyTWbn4NmH4oe3dXv4xSi9eqZwOB7/hXnzyGl84pgFn2MdeeMKb4Lr7z8fUNFmS0TNl7rujaIHRmNR1f2ZA==",
"dependencies": {
"axios": "^0.26.0",
"loglevel": "^1.8.0"
},
"peerDependencies": {
"@babel/runtime": "^7.17.2"
}
}
}
}

Zobrazit soubor

@ -1,14 +0,0 @@
{
"name": "typesense",
"version": "1.0.0",
"description": "",
"main": "typesense-init.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"typesense": "^1.5.4"
}
}

Zobrazit soubor

@ -1,24 +0,0 @@
const Typesense = require("typesense");
let client = new Typesense.Client({
"nodes": [{
"host": "localhost", // For Typesense Cloud use xxx.a1.typesense.net
"port": "8108", // For Typesense Cloud use 443
"protocol": "http", // For Typesense Cloud use https
}],
"apiKey": "Kaey9ahMo7xoob1haivaithe2Aighoo3azohl2Joo5Aemoh4aishoogugh3Oowim",
"connectionTimeoutSeconds": 2,
});
/*const schema = {
name: 'dids',
fields: [
{ name: 'did', type: 'string' },
{ name: 'handles', type: 'string[]' },
]
}
client.collections().create(schema)
.then(function (data) {
console.log(data)
})*/

Zobrazit soubor

@ -1 +0,0 @@
../.env

17
frontend/package-lock.json vygenerováno
Zobrazit soubor

@ -1,12 +1,12 @@
{
"name": "atscan-fe",
"version": "0.6.2-alpha",
"version": "0.7.0-alpha",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "atscan-fe",
"version": "0.6.2-alpha",
"version": "0.7.0-alpha",
"dependencies": {
"i18next": "^23.2.6",
"js-yaml": "^4.1.0",
@ -36,6 +36,7 @@
"prettier-plugin-svelte": "^2.8.1",
"svelte": "^3.54.0",
"svelte-echarts": "^0.0.5",
"svelte-local-storage-store": "^0.5.0",
"tailwindcss": "^3.3.2",
"vite": "^4.3.0"
}
@ -3124,6 +3125,18 @@
"svelte": "*"
}
},
"node_modules/svelte-local-storage-store": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/svelte-local-storage-store/-/svelte-local-storage-store-0.5.0.tgz",
"integrity": "sha512-SEDrpapeia6fUqta+r1NvSLlJYPkZ4pBcl15EYIOSPNzy6vhpoXu8cnzUDmZxsWl7fZGAHxrVH9UyZCbyO4W+g==",
"dev": true,
"engines": {
"node": ">=0.14"
},
"peerDependencies": {
"svelte": "^3.48.0 || ^4.0.0"
}
},
"node_modules/tailwindcss": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",

Zobrazit soubor

@ -1,6 +1,6 @@
{
"name": "atscan-fe",
"version": "0.6.2-alpha",
"version": "0.7.0-alpha",
"private": true,
"scripts": {
"dev": "vite dev",
@ -33,6 +33,7 @@
"prettier-plugin-svelte": "^2.8.1",
"svelte": "^3.54.0",
"svelte-echarts": "^0.0.5",
"svelte-local-storage-store": "^0.5.0",
"tailwindcss": "^3.3.2",
"vite": "^4.3.0"
},

Zobrazit soubor

@ -13,6 +13,13 @@
<link href="/font-awesome/css/fontawesome.min.css" rel="stylesheet" />
<link href="/font-awesome/css/brands.min.css" rel="stylesheet" />
<link href="/font-awesome/css/solid.min.css" rel="stylesheet" />
<script
src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.2/echarts.min.js"
integrity="sha512-VdqgeoWrVJcsDXFlQEKqE5MyhaIgB9yXUVaiUa8DR2J4Lr1uWcFm+ZH/YnzV5WqgKf4GPyHQ64vVLgzqGIchyw=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
</head>
<body data-sveltekit-preload-data="hover" data-theme="skeleton">
<div style="display: contents" class="h-full overflow-hidden">%sveltekit.body%</div>

11
frontend/src/lib/api.js Normal file
Zobrazit soubor

@ -0,0 +1,11 @@
//import { fetch } from 'svelte/fetch';
import { config } from '$lib/config';
export async function request(fetch, path, ...args) {
console.log(path);
const res = await fetch(config.api + path, ...args);
if (!res || res.status !== 200) {
return null;
}
return res.json();
}

Zobrazit soubor

@ -13,6 +13,7 @@
import { format } from 'echarts';
export let sourceData;
export let data;
export let sorting = false;
const dispatch = createEventDispatcher();
@ -83,19 +84,27 @@
val = `/${row.did}`;
}
if (key === 'commits') {
val = row.repo?.commits ? formatNumber(row.repo.commits) : '-';
val = row.repo?.commits
? formatNumber(row.repo.commits)
: '<i class="fa-solid fa-clock opacity-20" alt="Not yet indexed" title="Not yet indexed"></i>';
}
if (key === 'size') {
val =
'<div class="text-lg">' +
(row.repo?.size
? filesize(row.repo.size) +
'</div><div>' +
formatNumber(
Object.keys(row.repo.collections).reduce((t, c) => (t += row.repo.collections[c]), 0)
) +
' items</div>'
: '-');
val = row.repo?.size
? '<div class="text-lg">' +
filesize(row.repo.size) +
'</div><div>' +
formatNumber(
Object.keys(row.repo.collections).reduce((t, c) => (t += row.repo.collections[c]), 0)
) +
' items</div>'
: '<i class="fa-solid fa-clock opacity-20" alt="Not yet indexed" title="Not yet indexed"></i>';
}
if (key === 'lastSnapshot') {
val = `<span class="text-sm">${
row.repo?.time
? dateDistance(row.repo.time) + ' ago'
: '<i class="fa-solid fa-clock opacity-20" alt="Not yet indexed" title="Not yet indexed"></i>'
}</span>`;
}
return val;
}
@ -107,6 +116,7 @@
['PDS (PLC)', 'pds'],
['Repo size', 'size'],
['Commits', 'commits'],
//['Last snapshot', 'lastSnapshot'],
['Updated', 'lastMod']
],
body: customTableMapper(
@ -114,12 +124,13 @@
['img', 'did', 'srcHost', 'size', 'commits', 'lastMod'],
tableMap
),
meta: customTableMapper(sourceData || [], ['did_raw', 'url'], tableMap)
meta: customTableMapper(sourceData || [], ['did_raw', 'url', '_isChange'], tableMap)
};
</script>
<Table
source={tableSimple}
{sorting}
currentSort={data.sort}
defaultSort=""
on:headSelected={(e) => onHeadSelected(e)}

Zobrazit soubor

@ -12,6 +12,7 @@
} from '$lib/utils.js';
export let sourceData;
export let data;
export let sorting = false;
const dispatch = createEventDispatcher();
@ -19,6 +20,10 @@
dispatch('headSelected', event.detail);
}
function onFavoriteClick(event) {
dispatch('favoriteClick', event.detail);
}
function tableMap({ val, key, row }) {
if (key === 'plcs' && val) {
val = val
@ -72,7 +77,9 @@
val = `<a href="/dids?q=pds:${row.host}" class="anchor">${formatNumber(val)}</a>`;
}
if (key === 'size') {
val = row.size ? filesize(row.size) : '-';
val = row.size
? `<span alt="${row.size}" title="${row.size}">${filesize(row.size)}</span>`
: '-';
}
if (key === 'lastOnline' && row.inspect) {
val = `<span class="text-xs">${
@ -107,7 +114,7 @@
['Size', 'size'],
['Location', 'country'],
['PLCs (User Domains)', 'plcs'],
['Resp. time', 'responseTime'],
['Latency', 'responseTime'],
['Last Online', 'lastOnline']
],
body: customTableMapper(
@ -125,13 +132,16 @@
],
tableMap
),
meta: customTableMapper(sourceData, ['host_raw', 'url'], tableMap)
meta: customTableMapper(sourceData, ['host_raw', 'url', '_isChange', '_isFavorite'], tableMap)
};
</script>
<Table
source={tableSimple}
{sorting}
currentSort={data.sort}
defaultSort=""
favoriteColumn={2}
on:favoriteClick={(e) => onFavoriteClick(e)}
on:headSelected={(e) => onHeadSelected(e)}
/>

Zobrazit soubor

@ -2,6 +2,7 @@
import { createEventDispatcher } from 'svelte';
import { tableA11y } from '@skeletonlabs/skeleton';
import { goto } from '$app/navigation';
import { fade, slide } from 'svelte/transition';
const dispatch = createEventDispatcher();
@ -13,7 +14,9 @@
export let source;
export let currentSort = null;
export let defaultSort;
export let defaultSort = null;
export let favoriteColumn = null;
export let sorting = false;
if (!currentSort) {
currentSort = defaultSort;
@ -67,6 +70,12 @@
dispatch('headSelected', meta);
}
function onFavoriteClick(event, id) {
event.preventDefault();
event.stopPropagation();
dispatch('favoriteClick', id);
}
// Row Keydown Handler
function onRowKeydown(event, rowIndex) {
if (['Enter', 'Space'].includes(event.code)) onRowClick(event, rowIndex);
@ -91,10 +100,10 @@
<thead class="table-head {regionHead}">
<tr>
{#each source.head as heading}
<th class="{regionHeadCell} cursor-pointer hover:underline text-sm"
<th class="{regionHeadCell} {sorting ? 'cursor-pointer hover:underline' : ''} text-sm"
on:click={(e) => { onHeaderClick(e, Array.isArray(heading) ? heading[1] : heading) }}>
{@html Array.isArray(heading) ? heading[0] : heading}
{#if Array.isArray(heading) && (currentSort?.replace(/^!/, '') === heading[1] || '!'+currentSort === heading[1])}
{#if sorting && Array.isArray(heading) && (currentSort?.replace(/^!/, '') === heading[1] || '!'+currentSort === heading[1])}
{#if currentSort?.startsWith('!')}
{:else}
@ -111,6 +120,7 @@
<!-- Row -->
<!-- prettier-ignore -->
<tr
id={source.meta[rowIndex][0]}
on:click={(e) => { onRowClick(e, rowIndex); }}
on:keydown={(e) => { onRowKeydown(e, rowIndex); }}
aria-rowindex={rowIndex + 1}
@ -119,12 +129,15 @@
<!-- Cell -->
<!-- prettier-ignore -->
<td
class="{regionCell}"
class="{source.meta[rowIndex][2] === 'update' ? 'bg-yellow-500/50' : (source.meta[rowIndex][2] === 'create' ? 'bg-green-500/50' : '')} transition-all duration-[2500ms] ease-out {regionCell}"
role="gridcell"
aria-colindex={cellIndex + 1}
tabindex={cellIndex === 0 ? 0 : -1}
>
{@html cell ? cell : '-'}
{#if favoriteColumn !== null && favoriteColumn === cellIndex}
<i class="favorite fa-regular fa-star ml-1 {source.meta[rowIndex][3] !== undefined && source.meta[rowIndex][3] ? 'inline-block active text-yellow-500 opacity-100 hover:text-red-500' : 'opacity-50 hidden hover:text-green-500'} hover:opacity-100" on:click={(ev) => onFavoriteClick(ev, source.meta[rowIndex][0])}></i>
{/if}
</td>
{/each}
</tr>
@ -142,3 +155,12 @@
{/if}
</table>
</div>
<style>
.table-body tr td .favorite {
display: none;
}
.table-body tr:hover td .favorite {
display: inline-block;
}
</style>

Zobrazit soubor

@ -0,0 +1,9 @@
export const config = {
name: 'ATScan',
domain: 'atscan.net',
api: 'https://api.atscan.net',
web: 'https://atscan.net',
git: 'https://github.com/atscan/atscan',
status: 'https://status.gwei.cz/status/atscan',
ecosystem: 'https://mirror.ecosystem.atscan.net/index.json'
};

Zobrazit soubor

@ -0,0 +1,12 @@
import { config } from '$lib/config';
export let ecosystem = null;
export async function loadEcosystem() {
if (ecosystem) {
return ecosystem;
}
const ecosystemRes = await fetch(config.ecosystem);
ecosystem = ecosystemRes ? await ecosystemRes.json() : null;
return ecosystem;
}

Zobrazit soubor

@ -0,0 +1,11 @@
import { connect as NATSConnect, StringCodec, JSONCodec } from 'nats.ws';
import { writable } from 'svelte/store';
export let connected = writable(null);
export let nats = null;
export let codec = JSONCodec();
export async function connect() {
nats = await NATSConnect({ servers: 'wss://nats.gwei.cz' });
await connected.set(true);
}

Zobrazit soubor

@ -0,0 +1,5 @@
import { persisted } from 'svelte-local-storage-store';
export const preferences = persisted('preferences', {
favoritePDS: []
});

Zobrazit soubor

@ -1,22 +1,11 @@
import pkg from '../../package.json';
import { config } from '$lib/config';
import { loadEcosystem } from '$lib/ecosystem';
export async function load({ fetch }) {
const config = {
name: 'ATScan',
domain: 'atscan.net',
api: 'https://api.atscan.net',
web: 'https://atscan.net',
git: 'https://github.com/atscan/atscan',
status: 'https://status.gwei.cz/status/atscan',
ecosystem: 'https://mirror.ecosystem.atscan.net/index.json'
};
const ecosystemRes = await fetch(config.ecosystem);
const ecosystem = ecosystemRes ? await ecosystemRes.json() : null;
return {
config,
ecosystem,
ecosystem: await loadEcosystem(),
pkg,
basicSpacing: 'p-4 md:p-4 space-y-6 md:space-y-6'
};

Zobrazit soubor

@ -14,30 +14,24 @@
import { storeHighlightJs } from '@skeletonlabs/skeleton';
import { Drawer, drawerStore } from '@skeletonlabs/skeleton';
import { onMount } from 'svelte';
import { connect, StringCodec, JSONCodec } from 'nats.ws';
import { i18n } from '$lib/i18n.js';
import { nats, connect, connected } from '$lib/sockets.js';
import { navigating } from '$app/stores';
import { ProgressBar } from '@skeletonlabs/skeleton';
export let data;
storeHighlightJs.set(hljs);
onMount(() => {
connect();
});
afterNavigate(() => {
//console.log('scrolltop');
//window.scrollTo(0, 0);
});
onMount(async () => {
const nc = await connect({ servers: 'wss://nats.gwei.cz' });
const codec = JSONCodec();
const sub = nc.subscribe('greet.sue');
(async () => {
for await (const m of sub) {
console.log(`[${sub.getProcessed()}]: ${JSON.stringify(codec.decode(m.data))}`);
}
console.log('subscription closed');
})();
});
function drawerOpen() {
drawerStore.open({});
}
@ -103,8 +97,13 @@
<!-- App Shell -->
<AppShell>
<svelte:fragment slot="header">
<div class="h-1.5 bg-surface-100-800-token">
{#if $navigating}
<div class="w-full"><ProgressBar meter="bg-primary-500" track="bg-surface-100-800-token" /></div>
{/if}
</div>
<!-- App Bar -->
<AppBar>
<AppBar padding="px-4 pb-4 pt-2.5">
<svelte:fragment slot="lead">
<div class="flex items-center">
<button class="lg:hidden btn btn-sm" on:click={drawerOpen}>
@ -176,6 +175,11 @@
Twitter
</a>
-->
{#if $connected}
<div class="text-xs pr-4">
<i class="fa-solid fa-circle text-green-500 text-xs mr-1 animate-pulse" /> Connected
</div>
{/if}
<LightSwitch />
<div class="relative hidden lg:block">
<a

Zobrazit soubor

@ -1,14 +1,13 @@
import { error } from '@sveltejs/kit';
import { request } from '$lib/api';
export async function load({ params, fetch, parent }) {
const { config } = await parent();
const did = `did:${params.id}`;
const itemRes = await fetch(`${config.api}/${did}`);
if (!itemRes) {
const item = await request(fetch, `/${did}`);
if (!item) {
throw error(404, { message: 'Not found' });
}
const item = await itemRes.json();
const pdsHost = item.pds[0]?.replace(/^https?:\/\//, '');
const pdsRes = pdsHost ? await fetch(`${config.api}/pds/${pdsHost}`) : null;
return {

Zobrazit soubor

@ -59,6 +59,11 @@
let current;
let currentError;
$: records =
item.repo && item.repo.collections
? Object.keys(item.repo?.collections).reduce((t, c) => (t += item.repo.collections[c]), 0)
: null;
onMount(async () => {
if (item.repo) {
try {
@ -102,7 +107,7 @@
</div>
</div>
<h2 class="h2">Revisions <span class="font-normal text-2xl">({sourceData.length})</span></h2>
<h2 class="h2">History <span class="font-normal text-2xl">({sourceData.length})</span></h2>
<Table source={historyTable} />
{#if data.pds}
@ -139,26 +144,20 @@
</tr>
<tr>
<th class="text-right">Records</th>
<td
>{formatNumber(
Object.keys(item.repo?.collections).reduce(
(t, c) => (t += item.repo.collections[c]),
0
)
)} items</td
>
<td>{formatNumber(records)} items</td>
</tr>
<tr>
<th class="text-right">Collections</th>
<td
>{#if item.repo?.collections.length > 0}{Object.keys(item.repo?.collections)
>{#if item.repo?.collections && records > 0}
{Object.keys(item.repo?.collections)
.map((c) => `${formatNumber(item.repo.collections[c])} ${c}`)
.join(', ')}{:else}<i>No items</i>{/if}</td
>
</tr>
<tr>
<th class="text-right">Indexed</th>
<td>{dateDistance(item.repo?.time)} ago</td>
<td>{dateDistance(item.repo?.time)} ago ({item.repo?.time})</td>
</tr>
<tr>
<th class="text-right">Up to date?</th>

Zobrazit soubor

@ -7,10 +7,12 @@
import { onMount, onDestroy } from 'svelte';
import DIDTable from '$lib/components/DIDTable.svelte';
import BasicPage from '$lib/components/BasicPage.svelte';
import { nats, connected, codec } from '$lib/sockets.js';
export let data;
const search = writable(data.q?.trim() || '');
$: sourceData = data.did;
$: totalCount = data.totalCount;
let onlySandbox = data.onlySandbox || null;
let sort = data.sort || null;
@ -20,13 +22,69 @@
}
let periodicUpdate = null;
const subscriptions = [];
function subscribeDIDs() {
const sub = nats.subscribe('ats.api.did.*');
subscriptions.push(sub);
(async () => {
for await (const m of sub) {
//console.log(`[${sub.getProcessed()}x]: ${JSON.stringify(m.data)}`);
switch (m.subject) {
case 'ats.api.did.update':
case 'ats.api.did.create':
const did = codec.decode(m.data);
const type = m.subject.replace(/^ats.api.did\./, '');
did._isChange = type;
if (m.subject === 'ats.api.did.create' && onlySandbox && did.fed === 'sandbox') {
totalCount += 1;
$: sourceData = [did, ...sourceData].slice(0, 100);
} else if (m.subject === 'ats.api.did.create' && !data.q && !sort && !onlySandbox) {
totalCount += 1;
$: sourceData = [did, ...sourceData].slice(0, 100);
} else {
const exist = sourceData.find((i) => i.did === did.did);
if (exist) {
const index = sourceData.indexOf(exist);
$: sourceData = [
...sourceData.slice(0, index),
did,
...sourceData.slice(index + 1)
];
}
}
setTimeout(() => {
$: sourceData = sourceData.map((sd) => {
if (sd.did === did.did) {
did._isChange = false;
}
return sd;
});
}, 2000);
break;
}
}
console.log('subscription closed');
})();
}
connected.subscribe((val) => {
if (val === true) {
subscribeDIDs();
}
});
onMount(() => {
periodicUpdate = setInterval(() => {
/*periodicUpdate = setInterval(() => {
invalidate((url) => url.pathname === '/dids');
}, 60 * 1000);
}, 60 * 1000);*/
});
onDestroy(() => {
clearInterval(periodicUpdate);
for (const sub of subscriptions) {
sub.unsubscribe();
}
});
function gotoNewTableState() {
@ -136,11 +194,11 @@
<div class="text-xl">
{#if $search && $search?.trim() !== ''}
Search for <code class="code text-2xl variant-tertiary">{$search.trim()}</code>
{#if onlySandbox}(only sandbox){/if} ({formatNumber(data.totalCount)}):
{#if onlySandbox}(only sandbox){/if} ({formatNumber(totalCount)}):
{:else}
All DIDs {#if onlySandbox} on sandbox{/if} ({formatNumber(data.totalCount)}):
All DIDs {#if onlySandbox} on sandbox{/if} ({formatNumber(totalCount)}):
{/if}
</div>
<DIDTable {sourceData} {data} on:headSelected={(e) => onHeadSelected(e)} />
<DIDTable {sourceData} {data} sorting="true" on:headSelected={(e) => onHeadSelected(e)} />
{/if}
</BasicPage>

Zobrazit soubor

@ -1,8 +1,7 @@
export async function load({ fetch, parent }) {
const { config } = await parent();
import { request } from '$lib/api';
const plcRes = await fetch(`${config.api}/plc`);
export async function load({ fetch }) {
return {
plc: await plcRes.json()
plc: request(fetch, '/plc')
};
}

Zobrazit soubor

@ -1,18 +1,21 @@
import * as _ from 'lodash';
import { browser } from '$app/environment';
async function loadPDS(config) {
const res = await fetch(`${config.api}/pds`);
return res.json();
}
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, parent, url }) {
const { config } = await parent();
const res = await fetch(`${config.api}/pds`);
const arr = (await res.json()).map((i) => {
i.err = Boolean(i.inspect?.current?.err);
return i;
});
//const pds = _.orderBy(arr, ['env', 'err', 'didsCount'], ['asc', 'asc', 'desc']);
let q = url.searchParams.get('q');
let sort = url.searchParams.get('sort');
const pds = loadPDS(config);
return {
pds: arr,
pds: browser ? pds : await pds,
sort,
q
};

Zobrazit soubor

@ -7,13 +7,22 @@
import PDSTable from '$lib/components/PDSTable.svelte';
import BasicPage from '$lib/components/BasicPage.svelte';
import { orderBy } from 'lodash';
import { compute_slots } from 'svelte/internal';
import { nats, connected, codec } from '$lib/sockets.js';
import { preferences } from '$lib/stores.js';
import { onMount, onDestroy } from 'svelte';
export let data;
const subscriptions = [];
const search = writable($page.url.searchParams.get('q') || '');
let sort = data.sort || null;
let baseData = data.pds;
let sortedData = sortData(baseData);
let sourceData = filterSourceData(sortedData);
let favoritesData = baseData.filter((i) => $preferences.favoritePDS.includes(i.host)) || [];
function gotoNewTableState() {
let args = [];
if ($search !== '') {
@ -32,6 +41,7 @@
search.subscribe((val) => {
gotoNewTableState();
updateData();
return val;
});
@ -39,9 +49,22 @@
return goto(`/pds/${i.detail[0]}`);
}
$: sourceData = (function filterSourceData(bd) {
function updateData(base = null) {
if (!base) {
base = baseData;
}
base = base.map((i) => {
i._isFavorite = $preferences.favoritePDS.includes(i.host);
return i;
});
sortedData = sortData(base);
sourceData = filterSourceData(sortedData);
favoritesData = baseData.filter((i) => $preferences.favoritePDS.includes(i.host)) || [];
}
function filterSourceData(bd) {
const tokens = $search.split(' ');
let base = JSON.parse(JSON.stringify(bd)); //.filter(d => d.inspect?.lastOnline)
let base = bd; //.filter(d => d.inspect?.lastOnline)
base = base.map((i) => {
i.country = i.ip?.country;
//i.ms = !i.inspect?.current.err ? i.inspect?.current?.ms : null;
@ -70,6 +93,12 @@
if (txt) {
base = base.filter((i) => i.url.match(new RegExp(txt, 'i')));
}
return base;
//return sortData(base);
}
function sortData(base) {
if (sort) {
let sortReal = sort;
let sortDirection = 1;
@ -81,9 +110,8 @@
} else {
base = orderBy(base, ['env', 'err', 'didsCount'], ['asc', 'asc', 'desc']);
}
return base;
})(data.pds);
}
function formSubmit() {
const url = '?q=' + $search;
@ -94,10 +122,76 @@
function onHeadSelected(e) {
sort = sort === e.detail ? (sort.startsWith('!') ? '' : '!') + e.detail : e.detail;
gotoNewTableState();
updateData();
}
function onFavoriteClick(e) {
let favs = $preferences.favoritePDS;
if (favs.includes(e.detail)) {
favs.splice(favs.indexOf(e.detail), 1);
} else {
favs.push(e.detail);
}
preferences.set(Object.assign($preferences, { favoritePDS: favs }));
updateData();
}
function subscribePDS() {
const sub = nats.subscribe('ats.api.pds.*');
subscriptions.push(sub);
(async () => {
for await (const m of sub) {
//console.log(`[${sub.getProcessed()}x]: ${JSON.stringify(m.data)}`);
switch (m.subject) {
case 'ats.api.pds.update':
const pds = codec.decode(m.data);
pds._isChange = 'update';
const exist = baseData.find((i) => i.url === pds.url);
if (exist) {
const index = baseData.indexOf(exist);
baseData[index] = pds;
updateData(baseData);
//console.log(pds._isChange);
setTimeout(() => {
updateData(
baseData.map((sd) => {
if (sd.url === pds.url) {
pds._isChange = false;
}
return sd;
})
);
}, 250);
}
break;
}
}
console.log('subscription closed');
})();
}
connected.subscribe((val) => {
if (val === true) {
subscribePDS();
}
});
onDestroy(() => {
for (const sub of subscriptions) {
sub.unsubscribe();
}
});
</script>
<BasicPage {data} title="PDS Instances">
{#if $preferences.favoritePDS.length > 0}
<h2 class="h2">Your favourites</h2>
<PDSTable sourceData={favoritesData} {data} on:favoriteClick={(e) => onFavoriteClick(e)} />
<h2 class="h2">All instances</h2>
{/if}
<form on:submit|preventDefault={formSubmit} class="flex gap-4">
<input
class="input"
@ -108,7 +202,6 @@
/>
<!--button type="submit" class="btn variant-filled">Search</button-->
</form>
<div class="text-xl">
{#if $search && $search?.trim() !== ''}
Search for <code class="code text-2xl variant-tertiary">{$search.trim()}</code>
@ -117,5 +210,11 @@
All PDS Instances ({formatNumber(sourceData.length)}):
{/if}
</div>
<PDSTable {sourceData} {data} on:headSelected={(e) => onHeadSelected(e)} />
<PDSTable
{sourceData}
{data}
sorting="true"
on:headSelected={(e) => onHeadSelected(e)}
on:favoriteClick={(e) => onFavoriteClick(e)}
/>
</BasicPage>

Zobrazit soubor

@ -1,12 +1,10 @@
export async function load({ params, fetch, parent }) {
const { config } = await parent();
import { request } from '$lib/api';
const itemResp = await fetch(`${config.api}/pds/${params.host}`);
const didsResp = await fetch(`${config.api}/dids?q=pds:${params.host}&limit=10`, {
headers: { 'x-ats-wrapped': 'true' }
});
export async function load({ params, fetch }) {
return {
item: await itemResp.json(),
dids: await didsResp.json()
item: request(fetch, `/pds/${params.host}`),
dids: request(fetch, `/dids?q=pds:${params.host}&limit=10`, {
headers: { 'x-ats-wrapped': 'true' }
})
};
}

Zobrazit soubor

@ -17,6 +17,12 @@ module.exports = {
args: "daemon",
interpreter: "deno",
interpreterArgs: "run --unstable --allow-net --allow-read --allow-env --allow-sys",
}, {
name: "atscan-firehose",
script: "./backend/firehose.js",
args: "daemon",
interpreter: "deno",
interpreterArgs: "run --unstable --allow-net --allow-read --allow-env --allow-sys --allow-ffi",
}, {
name: "atscan-fe-dev",
interpreter: "mullvad-exclude",