This commit is contained in:
tree 2023-06-28 02:34:19 +00:00
rodič 4435c081b1
revize 48078e553c
33 změnil soubory, kde provedl 1041 přidání a 835 odebrání

Zobrazit soubor

@ -26,6 +26,12 @@ did-crawler:
fe-rebuild:
cd frontend && npm run build && pm2 restart atscan-fe
format:
cd backend && deno fmt
cd frontend && npm run format
fmt: format
test:
deno test --unstable --allow-read ./backend/test.js

Zobrazit soubor

@ -1,14 +1,14 @@
module.exports = {
root: true,
extends: ["eslint:recommended", "plugin:svelte/recommended", "prettier"],
parserOptions: {
sourceType: "module",
ecmaVersion: 2020,
extraFileExtensions: [".svelte"],
},
env: {
browser: true,
es2017: true,
node: true,
},
root: true,
extends: ['eslint:recommended', 'plugin:svelte/recommended', 'prettier'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
es2017: true,
node: true
}
};

Zobrazit soubor

@ -1,3 +1,4 @@
static
.DS_Store
node_modules
/build

Zobrazit soubor

@ -1,106 +1,104 @@
{
"prettier.documentSelectors": [
"**/*.svelte"
],
"tailwindCSS.classAttributes": [
"class",
"accent",
"active",
"background",
"badge",
"bgBackdrop",
"bgDark",
"bgDrawer",
"bgLight",
"blur",
"border",
"button",
"buttonAction",
"buttonBack",
"buttonClasses",
"buttonComplete",
"buttonDismiss",
"buttonNeutral",
"buttonNext",
"buttonPositive",
"buttonTextCancel",
"buttonTextConfirm",
"buttonTextFirst",
"buttonTextLast",
"buttonTextNext",
"buttonTextPrevious",
"buttonTextSubmit",
"caretClosed",
"caretOpen",
"chips",
"color",
"controlSeparator",
"controlVariant",
"cursor",
"display",
"element",
"fill",
"fillDark",
"fillLight",
"flex",
"gap",
"gridColumns",
"height",
"hover",
"justify",
"meter",
"padding",
"position",
"regionBackdrop",
"regionBody",
"regionCaption",
"regionCaret",
"regionCell",
"regionCone",
"regionContent",
"regionControl",
"regionDefault",
"regionDrawer",
"regionFoot",
"regionFootCell",
"regionFooter",
"regionHead",
"regionHeadCell",
"regionHeader",
"regionIcon",
"regionInterface",
"regionInterfaceText",
"regionLabel",
"regionLead",
"regionLegend",
"regionList",
"regionNavigation",
"regionPage",
"regionPanel",
"regionRowHeadline",
"regionRowMain",
"regionTab",
"regionTrail",
"ring",
"rounded",
"select",
"shadow",
"slotDefault",
"slotFooter",
"slotHeader",
"slotLead",
"slotMessage",
"slotMeta",
"slotPageContent",
"slotPageFooter",
"slotPageHeader",
"slotSidebarLeft",
"slotSidebarRight",
"slotTrail",
"spacing",
"text",
"track",
"width",
"zIndex"
]
"prettier.documentSelectors": ["**/*.svelte"],
"tailwindCSS.classAttributes": [
"class",
"accent",
"active",
"background",
"badge",
"bgBackdrop",
"bgDark",
"bgDrawer",
"bgLight",
"blur",
"border",
"button",
"buttonAction",
"buttonBack",
"buttonClasses",
"buttonComplete",
"buttonDismiss",
"buttonNeutral",
"buttonNext",
"buttonPositive",
"buttonTextCancel",
"buttonTextConfirm",
"buttonTextFirst",
"buttonTextLast",
"buttonTextNext",
"buttonTextPrevious",
"buttonTextSubmit",
"caretClosed",
"caretOpen",
"chips",
"color",
"controlSeparator",
"controlVariant",
"cursor",
"display",
"element",
"fill",
"fillDark",
"fillLight",
"flex",
"gap",
"gridColumns",
"height",
"hover",
"justify",
"meter",
"padding",
"position",
"regionBackdrop",
"regionBody",
"regionCaption",
"regionCaret",
"regionCell",
"regionCone",
"regionContent",
"regionControl",
"regionDefault",
"regionDrawer",
"regionFoot",
"regionFootCell",
"regionFooter",
"regionHead",
"regionHeadCell",
"regionHeader",
"regionIcon",
"regionInterface",
"regionInterfaceText",
"regionLabel",
"regionLead",
"regionLegend",
"regionList",
"regionNavigation",
"regionPage",
"regionPanel",
"regionRowHeadline",
"regionRowMain",
"regionTab",
"regionTrail",
"ring",
"rounded",
"select",
"shadow",
"slotDefault",
"slotFooter",
"slotHeader",
"slotLead",
"slotMessage",
"slotMeta",
"slotPageContent",
"slotPageFooter",
"slotPageHeader",
"slotSidebarLeft",
"slotSidebarRight",
"slotTrail",
"spacing",
"text",
"track",
"width",
"zIndex"
]
}

Zobrazit soubor

@ -1,37 +1,37 @@
{
"name": "atscan-fe",
"version": "0.3.4-alpha",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@floating-ui/dom": "^1.4.2",
"@skeletonlabs/skeleton": "^1.8.0",
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-node": "^1.2.4",
"@sveltejs/kit": "^1.5.0",
"@tailwindcss/forms": "^0.5.3",
"autoprefixer": "^10.4.14",
"date-fns": "^2.30.0",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.26.0",
"highlight.js": "^11.8.0",
"lodash": "^4.17.21",
"minidenticons": "^4.2.0",
"nats.ws": "^1.16.1",
"numbro": "^2.3.6",
"postcss": "^8.4.24",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.8.1",
"svelte": "^3.54.0",
"tailwindcss": "^3.3.2",
"vite": "^4.3.0"
},
"type": "module"
"name": "atscan-fe",
"version": "0.3.4-alpha",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@floating-ui/dom": "^1.4.2",
"@skeletonlabs/skeleton": "^1.8.0",
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-node": "^1.2.4",
"@sveltejs/kit": "^1.5.0",
"@tailwindcss/forms": "^0.5.3",
"autoprefixer": "^10.4.14",
"date-fns": "^2.30.0",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.26.0",
"highlight.js": "^11.8.0",
"lodash": "^4.17.21",
"minidenticons": "^4.2.0",
"nats.ws": "^1.16.1",
"numbro": "^2.3.6",
"postcss": "^8.4.24",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.8.1",
"svelte": "^3.54.0",
"tailwindcss": "^3.3.2",
"vite": "^4.3.0"
},
"type": "module"
}

Zobrazit soubor

@ -1,6 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
plugins: {
tailwindcss: {},
autoprefixer: {}
}
};

8
frontend/src/app.d.ts vendorováno
Zobrazit soubor

@ -2,8 +2,8 @@
// for information about these interfaces
// and what to do when importing types
declare namespace App {
// interface Locals {}
// interface PageData {}
// interface Error {}
// interface Platform {}
// interface Locals {}
// interface PageData {}
// interface Error {}
// interface Platform {}
}

Zobrazit soubor

@ -2,9 +2,9 @@
<html lang="en" class="dark">
<head>
<meta charset="utf-8" />
<link rel="apple-touch-icon" sizes="180x180" href="%sveltekit.assets%/apple-touch-icon.png"/>
<link rel="icon" type="image/png" sizes="32x32" href="%sveltekit.assets%/favicon-32x32.png"/>
<link rel="icon" type="image/png" sizes="16x16" href="%sveltekit.assets%/favicon-16x16.png"/>
<link rel="apple-touch-icon" sizes="180x180" href="%sveltekit.assets%/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="%sveltekit.assets%/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="%sveltekit.assets%/favicon-16x16.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%

Zobrazit soubor

@ -5,8 +5,8 @@ body {
}
.external::after {
padding-left: 10px;
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13.5' height='13.5' aria-hidden='true' viewBox='0 0 24 24' class='iconExternalLink_nPIU'%3E%3Cpath fill='currentColor' d='M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z'%3E%3C/path%3E%3C/svg%3E");
padding-left: 10px;
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13.5' height='13.5' aria-hidden='true' viewBox='0 0 24 24' class='iconExternalLink_nPIU'%3E%3Cpath fill='currentColor' d='M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z'%3E%3C/path%3E%3C/svg%3E");
}
@include ./../node_modules/@fortawesome/fontawesome-free/css/all.min.css;
@include ./../node_modules/ @fortawesome / fontawesome-free/css/all.min.css;

Zobrazit soubor

@ -1,8 +1,8 @@
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
const response = await resolve(event, {
filterSerializedResponseHeaders: (name) => name.startsWith('x-'),
});
const response = await resolve(event, {
filterSerializedResponseHeaders: (name) => name.startsWith('x-')
});
return response;
}
return response;
}

Zobrazit soubor

@ -1,21 +1,23 @@
<script>
export let data;
export let data;
</script>
<ol class="breadcrumb">
{#each data as crumb, i}
<!-- If crumb index is less than the breadcrumb length minus 1 -->
{#if i < data.length - 1}
<li class="crumb"><a class="btn variant-soft-secondary" href={crumb.link}>{crumb.label}</a></li>
<li class="crumb-separator" aria-hidden>&rsaquo;</li>
{:else}
<li class="crumb">
{#if crumb.link}
<a class="btn variant-soft-secondary" href={crumb.link}>{@html crumb.label}</a>
{:else}
{@html crumb.label}
{/if}
</li>
{/if}
{/each}
</ol>
{#each data as crumb, i}
<!-- If crumb index is less than the breadcrumb length minus 1 -->
{#if i < data.length - 1}
<li class="crumb">
<a class="btn variant-soft-secondary" href={crumb.link}>{crumb.label}</a>
</li>
<li class="crumb-separator" aria-hidden>&rsaquo;</li>
{:else}
<li class="crumb">
{#if crumb.link}
<a class="btn variant-soft-secondary" href={crumb.link}>{@html crumb.label}</a>
{:else}
{@html crumb.label}
{/if}
</li>
{/if}
{/each}
</ol>

Zobrazit soubor

@ -1,61 +1,78 @@
<script>
import Table from '$lib/components/Table.svelte';
import { tableMapperValues, tableSourceValues } from '@skeletonlabs/skeleton';
import { dateDistance, identicon, formatNumber, customTableMapper } from '$lib/utils.js';
export let sourceData;
export let data;
function tableMap ({ val, key, row }) {
if (key === 'world') {
}
if (key === 'srcHost') {
val = `<a href="/did?q=plc:${val}" class="anchor">${val}</a>`
}
if (key === 'pds') {
const host =
val = val.map(i => {
const host = i.replace(/^https?:\/\//, '')
return `<a href="/pds/${host}" class='anchor'>${host}</a>`
}).join(', ')
}
if (key === 'did') {
const did = val
const plc = data.plc.find(i => i.url === row.src)
val = `<div class="flex gap-6">`
val += ` <div>`
val += ` <div class="text-lg inline-block"><a href="/${did}" class=""><span class="opacity-50">did:plc:</span><span class="font-semibold opacity-100">${did.replace(/^did:plc:/, '')}</span></a></div>`
const handles = row.revs[row.revs.length-1].operation.alsoKnownAs.filter(h => !h.match(/at:\/\/data:x\//)).map(h => h.replace(/^at:\/\//, ''))
val += ` <div class="mt-1.5">`
val += ` <span class="mr-2 badge text-xs variant-filled ${plc.color} dark:${plc.color} opacity-70 text-white dark:text-black">${plc.name}</span>`
val += ` <span>${handles.map(h => `<a href="https://bsky.app/profile/${h}" target="_blank" class="anchor">@${h}</a>`).join(', ')}</span>`
val += ` </div>`
val += ` </div>`
val += "</div>"
}
if (key === 'time') {
val = dateDistance(val)
}
if (key === 'deep') {
val = row.revs.length
}
if (key === 'img') {
val = `<div class="text-right w-full"><div class="inline-block"><a href="/${row.did}"><img src="${identicon(row.did)}" class="w-16 h-16 rounded-lg bg-gray-200 dark:bg-gray-800 float-left" /></a></div></div>`
}
if (key === 'did_raw') {
val = row.did
}
if (key === 'url') {
val = `/${row.did}`
}
return val
}
$: tableSimple = {
// A list of heading labels.
head: ['', 'DID', '#', 'PLC', 'PDS', 'Last mod'],
body: customTableMapper(sourceData || [], ['img', 'did', 'deep', 'srcHost', 'pds', 'time'], tableMap),
meta: customTableMapper(sourceData || [], ['did_raw', 'url'], tableMap),
};
import Table from '$lib/components/Table.svelte';
import { tableMapperValues, tableSourceValues } from '@skeletonlabs/skeleton';
import { dateDistance, identicon, formatNumber, customTableMapper } from '$lib/utils.js';
export let sourceData;
export let data;
function tableMap({ val, key, row }) {
if (key === 'world') {
}
if (key === 'srcHost') {
val = `<a href="/did?q=plc:${val}" class="anchor">${val}</a>`;
}
if (key === 'pds') {
const host = (val = val
.map((i) => {
const host = i.replace(/^https?:\/\//, '');
return `<a href="/pds/${host}" class='anchor'>${host}</a>`;
})
.join(', '));
}
if (key === 'did') {
const did = val;
const plc = data.plc.find((i) => i.url === row.src);
val = `<div class="flex gap-6">`;
val += ` <div>`;
val += ` <div class="text-lg inline-block"><a href="/${did}" class=""><span class="opacity-50">did:plc:</span><span class="font-semibold opacity-100">${did.replace(
/^did:plc:/,
''
)}</span></a></div>`;
const handles = row.revs[row.revs.length - 1].operation.alsoKnownAs
.filter((h) => !h.match(/at:\/\/data:x\//))
.map((h) => h.replace(/^at:\/\//, ''));
val += ` <div class="mt-1.5">`;
val += ` <span class="mr-2 badge text-xs variant-filled ${plc.color} dark:${plc.color} opacity-70 text-white dark:text-black">${plc.name}</span>`;
val += ` <span>${handles
.map(
(h) => `<a href="https://bsky.app/profile/${h}" target="_blank" class="anchor">@${h}</a>`
)
.join(', ')}</span>`;
val += ` </div>`;
val += ` </div>`;
val += '</div>';
}
if (key === 'time') {
val = dateDistance(val);
}
if (key === 'deep') {
val = row.revs.length;
}
if (key === 'img') {
val = `<div class="text-right w-full"><div class="inline-block"><a href="/${
row.did
}"><img src="${identicon(
row.did
)}" class="w-16 h-16 rounded-lg bg-gray-200 dark:bg-gray-800 float-left" /></a></div></div>`;
}
if (key === 'did_raw') {
val = row.did;
}
if (key === 'url') {
val = `/${row.did}`;
}
return val;
}
$: tableSimple = {
// A list of heading labels.
head: ['', 'DID', '#', 'PLC', 'PDS', 'Last mod'],
body: customTableMapper(
sourceData || [],
['img', 'did', 'deep', 'srcHost', 'pds', 'time'],
tableMap
),
meta: customTableMapper(sourceData || [], ['did_raw', 'url'], tableMap)
};
</script>
<Table source={tableSimple} />
<Table source={tableSimple} />

Zobrazit soubor

@ -1,69 +1,102 @@
<script>
import Table from '$lib/components/Table.svelte';
import { tableMapperValues, tableSourceValues } from '@skeletonlabs/skeleton';
import { dateDistance, identicon, formatNumber, customTableMapper } from '$lib/utils.js';
export let sourceData;
export let data;
import Table from '$lib/components/Table.svelte';
import { tableMapperValues, tableSourceValues } from '@skeletonlabs/skeleton';
import { dateDistance, identicon, formatNumber, customTableMapper } from '$lib/utils.js';
export let sourceData;
export let data;
function tableMap ({ val, key, row }) {
if (key === 'plcs' && val) {
val = val.map(i => i.replace(/^https?:\/\//, '')).map(p => `<a href="/did?q=plc:${p}" class="anchor">${p}</a>`).join(', ')
if (row.inspect?.current.data?.availableUserDomains) {
val += '<br/>(' + row.inspect?.current.data?.availableUserDomains.join(', ') + ')'
}
}
if (key === 'env') {
let arr = []
const plc = (val === "sandbox") ? data.plc.find(i => i.code === 'sbox') : ((val === "bsky") ? data.plc.find(i => i.code === 'bsky') : null)
if (plc) {
arr.push(`<span class="badge variant-filled ${plc.color} dark:${plc.color} opacity-70 text-white dark:text-black">${plc.name}</span>`)
}
val = arr.reverse().join(' ')
}
if (key === 'host') {
val = `<a href="/pds/${val}" class=""><span class="font-semibold text-lg">${val}</span></a>`
}
if (key === 'ms') {
val = row.inspect?.current.err
? `<a href="${row.url}/xrpc/com.atproto.server.describeServer" target="_blank" title="${row.inspect.current.err}" class="anchor">error</a>`
: row.inspect?.current.ms ? `<a href="${row.url}/xrpc/com.atproto.server.describeServer" target="_blank" class="anchor">${row.inspect.current.ms + 'ms'}</a>` : '-'
}
if (key === 'location') {
val = row.ip && row.ip.country
? `<img src="/cc/${row.ip.country.toLowerCase()}.png" alt="${row.ip.country}" title="${row.ip.country}" class="inline-block mr-2" />`
: '-'
if (row.ip && row.ip.city) {
val += `${row.ip.city} - `
}
if (row.ip) {
const dnsIp = row.dns ? row.dns.Answer?.filter(a => a.type === 1)[0].data : null
val += `<a href="http://ipinfo.io/${dnsIp}" target="_blank" class="anchor">${dnsIp}</a>` || '-'
if (row.ip && row.ip.regionName) {
val += ' ('+row.ip.regionName+')'
}
val += `<br /><span class="text-xs">${row.ip?.org || 'n/a'}</span>`
}
}
if (key === 'didsCount') {
val = `<a href="/did?q=pds:${row.host}" class="anchor">${formatNumber(val)}</a>`
}
if (key === 'lastOnline' && row.inspect) {
val = `<span class="text-xs">${row.inspect?.lastOnline ? dateDistance(row.inspect?.lastOnline) + ' ago' : '-'}</span>`
}
if (key === 'host_raw') {
val = row.host
}
if (key === 'url') {
val = `/pds/${row.host}`
}
return val
}
function tableMap({ val, key, row }) {
if (key === 'plcs' && val) {
val = val
.map((i) => i.replace(/^https?:\/\//, ''))
.map((p) => `<a href="/did?q=plc:${p}" class="anchor">${p}</a>`)
.join(', ');
if (row.inspect?.current.data?.availableUserDomains) {
val += '<br/>(' + row.inspect?.current.data?.availableUserDomains.join(', ') + ')';
}
}
if (key === 'env') {
let arr = [];
const plc =
val === 'sandbox'
? data.plc.find((i) => i.code === 'sbox')
: val === 'bsky'
? data.plc.find((i) => i.code === 'bsky')
: null;
if (plc) {
arr.push(
`<span class="badge variant-filled ${plc.color} dark:${plc.color} opacity-70 text-white dark:text-black">${plc.name}</span>`
);
}
val = arr.reverse().join(' ');
}
if (key === 'host') {
val = `<a href="/pds/${val}" class=""><span class="font-semibold text-lg">${val}</span></a>`;
}
if (key === 'ms') {
val = row.inspect?.current.err
? `<a href="${row.url}/xrpc/com.atproto.server.describeServer" target="_blank" title="${row.inspect.current.err}" class="anchor">error</a>`
: row.inspect?.current.ms
? `<a href="${
row.url
}/xrpc/com.atproto.server.describeServer" target="_blank" class="anchor">${
row.inspect.current.ms + 'ms'
}</a>`
: '-';
}
if (key === 'location') {
val =
row.ip && row.ip.country
? `<img src="/cc/${row.ip.country.toLowerCase()}.png" alt="${row.ip.country}" title="${
row.ip.country
}" class="inline-block mr-2" />`
: '-';
if (row.ip && row.ip.city) {
val += `${row.ip.city} - `;
}
if (row.ip) {
const dnsIp = row.dns ? row.dns.Answer?.filter((a) => a.type === 1)[0].data : null;
val +=
`<a href="http://ipinfo.io/${dnsIp}" target="_blank" class="anchor">${dnsIp}</a>` || '-';
if (row.ip && row.ip.regionName) {
val += ' (' + row.ip.regionName + ')';
}
val += `<br /><span class="text-xs">${row.ip?.org || 'n/a'}</span>`;
}
}
if (key === 'didsCount') {
val = `<a href="/did?q=pds:${row.host}" class="anchor">${formatNumber(val)}</a>`;
}
if (key === 'lastOnline' && row.inspect) {
val = `<span class="text-xs">${
row.inspect?.lastOnline ? dateDistance(row.inspect?.lastOnline) + ' ago' : '-'
}</span>`;
}
if (key === 'host_raw') {
val = row.host;
}
if (key === 'url') {
val = `/pds/${row.host}`;
}
return val;
}
$: tableSimple = {
head: ['Federation', 'Host', 'DIDs', 'Location', 'PLCs (User Domains)', 'Resp. time', 'Last Online'],
body: customTableMapper(sourceData, ['env', 'host', 'didsCount', 'location', 'plcs', 'ms', 'lastOnline' ], tableMap),
meta: customTableMapper(sourceData, ['host_raw', 'url'], tableMap),
head: [
'Federation',
'Host',
'DIDs',
'Location',
'PLCs (User Domains)',
'Resp. time',
'Last Online'
],
body: customTableMapper(
sourceData,
['env', 'host', 'didsCount', 'location', 'plcs', 'ms', 'lastOnline'],
tableMap
),
meta: customTableMapper(sourceData, ['host_raw', 'url'], tableMap)
};
</script>
<Table source={tableSimple} />
<Table source={tableSimple} />

Zobrazit soubor

@ -1,27 +1,31 @@
<script>
import { CodeBlock, clipboard } from '@skeletonlabs/skeleton';
export let model = 'pds';
export let data;
import { CodeBlock, clipboard } from '@skeletonlabs/skeleton';
const models = {
pds: { url: 'https://api.atscan.net/pds/%', key: 'host' },
did: { url: 'https://api.atscan.net/%', key: 'did' },
}
const config = models[model]
const url = config.url.replace('%', data.item[config.key])
export let model = 'pds';
export let data;
const models = {
pds: { url: 'https://api.atscan.net/pds/%', key: 'host' },
did: { url: 'https://api.atscan.net/%', key: 'did' }
};
const config = models[model];
const url = config.url.replace('%', data.item[config.key]);
</script>
<h2 class="h2">Source</h2>
<CodeBlock code={JSON.stringify(data.item, null, 2)} language="json" />
<div>
<p>You can get this data as JSON by making a simple HTTP GET request to our API endpoint:</p>
<div class="mt-4 mb-8 flex gap-4 w-full">
<div data-clipboard="exampleElement" class="textarea p-2">{url}</div>
<button use:clipboard={{ element: 'exampleElement' }} class="btn variant-filled"><i class="fa-regular fa-clipboard mr-2"></i> Copy</button>
<a href={url} class="btn variant-filled" target="_blank">Open <i class="ml-2 fa-solid fa-arrow-right"></i></a>
</div>
<!--p>Example:</p>
<div>
<p>You can get this data as JSON by making a simple HTTP GET request to our API endpoint:</p>
<div class="mt-4 mb-8 flex gap-4 w-full">
<div data-clipboard="exampleElement" class="textarea p-2">{url}</div>
<button use:clipboard={{ element: 'exampleElement' }} class="btn variant-filled"
><i class="fa-regular fa-clipboard mr-2" /> Copy</button
>
<a href={url} class="btn variant-filled" target="_blank"
>Open <i class="ml-2 fa-solid fa-arrow-right" /></a
>
</div>
<!--p>Example:</p>
<CodeBlock code={url} class="mt-4" /-->
</div>
</div>

Zobrazit soubor

@ -1,7 +1,7 @@
<script>
import { createEventDispatcher } from 'svelte';
import { tableA11y } from '@skeletonlabs/skeleton';
import { goto } from '$app/navigation';
import { goto } from '$app/navigation';
const dispatch = createEventDispatcher();
@ -13,7 +13,7 @@
export let source;
/** Enables row hover style and `on:selected` event when rows are clicked. */
export let interactive = false;
export let interactiveOnlyHover = true;
export let interactiveOnlyHover = true;
// Props (styles)
/** Override the Tailwind Element class. Replace this for a headless UI. */
@ -48,9 +48,9 @@
if (event.target.className !== 'anchor') {
event.preventDefault();
const rowMetaData = source.meta ? source.meta[rowIndex] : source.body[rowIndex];
const url = rowMetaData[1]
const url = rowMetaData[1];
if (url) {
goto(url)
goto(url);
}
}
}
@ -119,4 +119,4 @@
</tfoot>
{/if}
</table>
</div>
</div>

Zobrazit soubor

@ -1,41 +1,42 @@
import { formatDistanceToNow } from 'date-fns';
import { minidenticon } from 'minidenticons';
import { tableSourceValues } from '@skeletonlabs/skeleton';
import numbro from 'numbro';
export function dateDistance (date) {
return formatDistanceToNow(new Date(date))
export function dateDistance(date) {
return formatDistanceToNow(new Date(date));
}
export function identicon (...args) {
return 'data:image/svg+xml;utf8,' + encodeURIComponent(minidenticon(...args))
export function identicon(...args) {
return 'data:image/svg+xml;utf8,' + encodeURIComponent(minidenticon(...args));
}
numbro.setDefaults({
thousandSeparated: true,
//mantissa: 2
thousandSeparated: true
//mantissa: 2
});
export function formatNumber (number) {
return numbro(number).format()
export function formatNumber(number) {
return numbro(number).format();
}
export function customTableMapper (source, keys, process) {
return tableSourceValues(source.map((row) => {
const mappedRow = {};
keys.forEach((key) => {
let val = row[key]
val = process({ row, key, val })
return mappedRow[key] = val
})
return mappedRow;
}))
export function customTableMapper(source, keys, process) {
return tableSourceValues(
source.map((row) => {
const mappedRow = {};
keys.forEach((key) => {
let val = row[key];
val = process({ row, key, val });
return (mappedRow[key] = val);
});
return mappedRow;
})
);
}
export function getFlagEmoji(countryCode) {
const codePoints = countryCode
.toUpperCase()
.split('')
.map(char => 127397 + char.charCodeAt());
return String.fromCodePoint(...codePoints);
}
const codePoints = countryCode
.toUpperCase()
.split('')
.map((char) => 127397 + char.charCodeAt());
return String.fromCodePoint(...codePoints);
}

Zobrazit soubor

@ -1,13 +1,11 @@
import pkg from '../../package.json'
import * as _ from 'lodash'
import pkg from '../../package.json';
import * as _ from 'lodash';
export async function load({ fetch }) {
const res = await fetch("https://api.atscan.net/plc");
const plc = _.orderBy(await res.json(), ["code"], [
"asc",
]);
return {
pkg,
plc
};
}
const res = await fetch('https://api.atscan.net/plc');
const plc = _.orderBy(await res.json(), ['code'], ['asc']);
return {
pkg,
plc
};
}

Zobrazit soubor

@ -6,12 +6,12 @@
// Most of your app wide CSS should be put in this file
import '../app.postcss';
import { AppShell, AppBar, LightSwitch } from '@skeletonlabs/skeleton';
import { afterNavigate } from "$app/navigation";
import { afterNavigate } from '$app/navigation';
import { page } from '$app/stores';
import hljs from 'highlight.js';
import 'highlight.js/styles/github-dark.css';
import { storeHighlightJs } from '@skeletonlabs/skeleton';
import { storeHighlightJs } from '@skeletonlabs/skeleton';
import { onMount } from 'svelte';
import { connect, StringCodec, JSONCodec } from 'nats.ws';
@ -21,20 +21,20 @@
afterNavigate(() => {
//console.log('scrolltop');
//window.scrollTo(0, 0);
});
//window.scrollTo(0, 0);
});
onMount(async () => {
const nc = await connect({ servers: "wss://nats.gwei.cz" });
const nc = await connect({ servers: 'wss://nats.gwei.cz' });
const codec = JSONCodec();
const sub = nc.subscribe("greet.sue");
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");
console.log('subscription closed');
})();
})
});
</script>
<svelte:head>
@ -47,19 +47,42 @@
<!-- App Bar -->
<AppBar>
<svelte:fragment slot="lead">
<a href="/"><strong class="text-xl ml-4 font-bold text-gray-600 dark:text-gray-300"><span class="text-[#3d81f8]">AT</span>Scan</strong></a>
<a href="/"
><strong class="text-xl ml-4 font-bold text-gray-600 dark:text-gray-300"
><span class="text-[#3d81f8]">AT</span>Scan</strong
></a
>
<div class="lg:ml-8 flex gap-1">
<div class="relative hidden lg:block">
<a href="/did" class="btn hover:variant-soft-primary" class:bg-primary-active-token={$page.url.pathname.startsWith('/did')}><span>DIDs</span></a>
<a
href="/did"
class="btn hover:variant-soft-primary"
class:bg-primary-active-token={$page.url.pathname.startsWith('/did')}
><span>DIDs</span></a
>
</div>
<div class="relative hidden lg:block">
<a href="/pds" class="btn hover:variant-soft-primary" class:bg-primary-active-token={$page.url.pathname.startsWith('/pds')}><span>PDS Instances</span></a>
<a
href="/pds"
class="btn hover:variant-soft-primary"
class:bg-primary-active-token={$page.url.pathname.startsWith('/pds')}
><span>PDS Instances</span></a
>
</div>
<div class="relative hidden lg:block">
<a href="/plc" class="btn hover:variant-soft-primary" class:bg-primary-active-token={$page.url.pathname === '/plc'}><span>PLC Directories</span></a>
<a
href="/plc"
class="btn hover:variant-soft-primary"
class:bg-primary-active-token={$page.url.pathname === '/plc'}
><span>PLC Directories</span></a
>
</div>
<div class="relative hidden lg:block">
<a href="/api" class="btn hover:variant-soft-primary" class:bg-primary-active-token={$page.url.pathname === '/api'}><span>API</span></a>
<a
href="/api"
class="btn hover:variant-soft-primary"
class:bg-primary-active-token={$page.url.pathname === '/api'}><span>API</span></a
>
</div>
</div>
</svelte:fragment>
@ -97,7 +120,7 @@
target="_blank"
rel="noreferrer"
>
<i class="fa-brands fa-github"></i>
<i class="fa-brands fa-github" />
</a>
<LightSwitch />
</svelte:fragment>
@ -108,4 +131,3 @@
<!--svelte:fragment slot="footer">
</svelte:fragment-->
</AppShell>

Zobrazit soubor

@ -1,6 +1,6 @@
import { redirect } from '@sveltejs/kit';
export function load() {
// ...
throw redirect(302, '/did');
}
// ...
throw redirect(302, '/did');
}

Zobrazit soubor

@ -1,6 +1,5 @@
<div class="w-full h-full bg-ats-bsky"></div>
<script>
/** dark:bg-ats-bsky dark:bg-ats-sbox */
</script>
/** dark:bg-ats-bsky dark:bg-ats-sbox */
</script>
<div class="w-full h-full bg-ats-bsky" />

Zobrazit soubor

@ -1,68 +1,88 @@
<script>
import { CodeBlock, clipboard } from '@skeletonlabs/skeleton';
import { tableMapperValues, tableSourceValues } from '@skeletonlabs/skeleton';
import Table from '$lib/components/Table.svelte';
import { customTableMapper } from '$lib/utils.js';
import { CodeBlock, clipboard } from '@skeletonlabs/skeleton';
import { tableMapperValues, tableSourceValues } from '@skeletonlabs/skeleton';
import Table from '$lib/components/Table.svelte';
import { customTableMapper } from '$lib/utils.js';
export let data;
export let data;
const sourceData = [
{ path: '/did', desc: 'Get all DIDs', example: ['/did', '/did?q=pds:bsky.social', '/did?q=\.cz$'] },
{ path: '/[did]', desc: 'Get DID item', example: ['/did:plc:524tuhdhh3m7li5gycdn6boe'] },
{ path: '/pds', desc: 'Get all PDS Instances', example: ['/pds', '/pds?q=gwei.cz'] },
{ path: '/pds/[id]', desc: 'Get PDS item', example: ['/pds/bsky.social'] },
{ path: '/plc', desc: 'Get all PLC Directories', example: ['/plc'] },
]
$: tableSimple = {
head: ['Method', 'Path', 'Description', 'Example'],
body: customTableMapper(sourceData, ['method', 'path', 'desc', 'example'],
({ val, key, row }) => {
if (key === 'method') {
val = `<span class="badge variant-filled-primary">${(val || 'get').toUpperCase()}</span>`
}
if (key === 'path' || key === 'example') {
val = val || ''
if (typeof(val) === 'string') {
val = [val]
}
val = val.map(v => `<code class="code ${key==='path' ? 'text-lg' : ''}">${v}</code>`).join('<br />')
}
return val
}),
meta: tableMapperValues(sourceData, ['did']),
};
const sourceData = [
{
path: '/did',
desc: 'Get all DIDs',
example: ['/did', '/did?q=pds:bsky.social', '/did?q=.cz$']
},
{ path: '/[did]', desc: 'Get DID item', example: ['/did:plc:524tuhdhh3m7li5gycdn6boe'] },
{ path: '/pds', desc: 'Get all PDS Instances', example: ['/pds', '/pds?q=gwei.cz'] },
{ path: '/pds/[id]', desc: 'Get PDS item', example: ['/pds/bsky.social'] },
{ path: '/plc', desc: 'Get all PLC Directories', example: ['/plc'] }
];
$: tableSimple = {
head: ['Method', 'Path', 'Description', 'Example'],
body: customTableMapper(
sourceData,
['method', 'path', 'desc', 'example'],
({ val, key, row }) => {
if (key === 'method') {
val = `<span class="badge variant-filled-primary">${(val || 'get').toUpperCase()}</span>`;
}
if (key === 'path' || key === 'example') {
val = val || '';
if (typeof val === 'string') {
val = [val];
}
val = val
.map((v) => `<code class="code ${key === 'path' ? 'text-lg' : ''}">${v}</code>`)
.join('<br />');
}
return val;
}
),
meta: tableMapperValues(sourceData, ['did'])
};
</script>
<svelte:head>
<title>API | ATScan</title>
</svelte:head>
<div class="container mx-auto p-8 space-y-8">
<h1 class="h1">API</h1>
<aside class="alert variant-filled-warning">
<!-- Icon -->
<i class="fa-solid fa-triangle-exclamation text-4xl"></i>
<!-- Message -->
<div class="alert-message">
<h3 class="h3">Experimental</h3>
<p>This API is not suitable for production use, it is just an experiment for now.</p>
</div>
</aside>
<aside class="alert variant-filled-warning">
<!-- Icon -->
<i class="fa-solid fa-triangle-exclamation text-4xl" />
<!-- Message -->
<div class="alert-message">
<h3 class="h3">Experimental</h3>
<p>This API is not suitable for production use, it is just an experiment for now.</p>
</div>
</aside>
<p>Our API is available at this address:<br /><span class="mt-2 pre text-sm inline-block">https://api.atscan.net</span></p>
<p>
Our API is available at this address:<br /><span class="mt-2 pre text-sm inline-block"
>https://api.atscan.net</span
>
</p>
<h3 class="h3">Mirrored paths (web & api)</h3>
<p>Most endpoints mirror the web interface path, so for example the <a href="https://atscan.net/did?q=pds:test-pds.gwei.cz" class="code">https://atscan.net/did?q=pds:test-pds.gwei.cz</a> page will list all DIDs on this PDS and the <a href="https://api.atscan.net/did?q=pds:test-pds.gwei.cz" class="code">https://api.atscan.net/did?q=pds:test-pds.gwei.cz</a> page will return the same as JSON.</p>
<h3 class="h3">Mirrored paths (web & api)</h3>
<p>
Most endpoints mirror the web interface path, so for example the <a
href="https://atscan.net/did?q=pds:test-pds.gwei.cz"
class="code">https://atscan.net/did?q=pds:test-pds.gwei.cz</a
>
page will list all DIDs on this PDS and the
<a href="https://api.atscan.net/did?q=pds:test-pds.gwei.cz" class="code"
>https://api.atscan.net/did?q=pds:test-pds.gwei.cz</a
> page will return the same as JSON.
</p>
<h2 class="h2">Endpoints</h2>
<h2 class="h2">Endpoints</h2>
<Table source={tableSimple} />
<!--CodeBlock code={`http -F api.atscan.net/did
<Table source={tableSimple} />
<!--CodeBlock code={`http -F api.atscan.net/did
> HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Alt-Svc: h3=":443"; ma=2592000
@ -83,7 +103,12 @@ Vary: Accept-Encoding
"revs": [
`} language="bash"></CodeBlock!-->
<h2 class="h2">Status</h2>
<p>To view the status of our services, go to <a href="https://status.gwei.cz/status/atscan" target="_blank" class="underline hover:no-underline">https://status.gwei.cz/status/atscan</a>.</p>
</div>
<h2 class="h2">Status</h2>
<p>
To view the status of our services, go to <a
href="https://status.gwei.cz/status/atscan"
target="_blank"
class="underline hover:no-underline">https://status.gwei.cz/status/atscan</a
>.
</p>
</div>

Zobrazit soubor

@ -1,26 +1,24 @@
import * as _ from "lodash";
import * as _ from 'lodash';
/** @type {import('./$types').PageLoad} */
export async function load({ params, fetch, url }) {
let q = url.searchParams.get('q')
const res = await fetch('https://api.atscan.net/did' + (q ? `?q=${q}` : ''), {
headers: { 'x-ats-wrapped': 'true' }
});
const json = await res.json()
const totalCount = json.count
const did = _.orderBy(json.items, ["time"], [
"desc",
]);
let onlySandbox = false
if (q?.match(/env:sbox/)) {
onlySandbox = true
q = q.replace('env:sbox', '').trim()
}
let q = url.searchParams.get('q');
const res = await fetch('https://api.atscan.net/did' + (q ? `?q=${q}` : ''), {
headers: { 'x-ats-wrapped': 'true' }
});
const json = await res.json();
const totalCount = json.count;
const did = _.orderBy(json.items, ['time'], ['desc']);
let onlySandbox = false;
if (q?.match(/env:sbox/)) {
onlySandbox = true;
q = q.replace('env:sbox', '').trim();
}
return {
did,
totalCount,
q,
onlySandbox
};
return {
did,
totalCount,
q,
onlySandbox
};
}

Zobrazit soubor

@ -1,74 +1,76 @@
<script>
import { ProgressRadial, SlideToggle } from '@skeletonlabs/skeleton';
import { formatNumber } from '$lib/utils.js';
import { goto, invalidate } from '$app/navigation';
import { writable } from 'svelte/store';
import { page } from '$app/stores';
import { onMount, onDestroy } from 'svelte';
import DIDTable from '$lib/components/DIDTable.svelte';
export let data;
const search = writable(data.q?.trim() || '')
$: sourceData = data.did;
let onlySandbox = data.onlySandbox || null
import { formatNumber } from '$lib/utils.js';
import { goto, invalidate } from '$app/navigation';
import { writable } from 'svelte/store';
import { page } from '$app/stores';
import { onMount, onDestroy } from 'svelte';
import DIDTable from '$lib/components/DIDTable.svelte';
function sandboxToggleHandler() {
sourceData = null
gotoNewTableState()
}
let periodicUpdate = null
onMount(() => {
periodicUpdate = setInterval(() => {
invalidate((url) => url.pathname === '/did');
}, 60 * 1000)
})
onDestroy(() => {
clearInterval(periodicUpdate)
})
export let data;
const search = writable(data.q?.trim() || '');
$: sourceData = data.did;
let onlySandbox = data.onlySandbox || null;
function gotoNewTableState () {
let q = $search || ''
if (onlySandbox && !q.match(/env:sbox/)) {
q += " env:sbox"
q = q.trim()
} else {
q = q.replace(/env:sbox/, '')
}
q = q.trim()
const path = '/did' + (q !== '' ? `?q=${q}` : '')
const currentPath = $page.url.pathname + $page.url.search
if (currentPath === path) {
return null
}
goto(path, { keepFocus: true, noScroll: true })
function sandboxToggleHandler() {
sourceData = null;
gotoNewTableState();
}
let lastWrite = null
let periodicUpdate = null;
onMount(() => {
periodicUpdate = setInterval(() => {
invalidate((url) => url.pathname === '/did');
}, 60 * 1000);
});
onDestroy(() => {
clearInterval(periodicUpdate);
});
function gotoNewTableState() {
let q = $search || '';
if (onlySandbox && !q.match(/env:sbox/)) {
q += ' env:sbox';
q = q.trim();
} else {
q = q.replace(/env:sbox/, '');
}
q = q.trim();
const path = '/did' + (q !== '' ? `?q=${q}` : '');
const currentPath = $page.url.pathname + $page.url.search;
if (currentPath === path) {
return null;
}
goto(path, { keepFocus: true, noScroll: true });
}
let lastWrite = null;
search.subscribe((val) => {
if (val?.trim() === data.q) {
return val
}
if (val?.trim() === data.q) {
return val;
}
sourceData = null
const current = new Date()
lastWrite = current
setTimeout(() => {
if (lastWrite === current) {
gotoNewTableState()
}
}, !$search ? 0 : 350)
return val
})
sourceData = null;
const current = new Date();
lastWrite = current;
setTimeout(
() => {
if (lastWrite === current) {
gotoNewTableState();
}
},
!$search ? 0 : 350
);
return val;
});
function formSubmit () {
console.log(search)
}
function selectionHandler (i) {
return goto(`/${i.detail[0]}`)
}
function formSubmit() {
console.log(search);
}
function selectionHandler(i) {
return goto(`/${i.detail[0]}`);
}
</script>
<svelte:head>
@ -77,41 +79,57 @@
<div class="container mx-auto p-8 space-y-8">
<h1 class="h1">DIDs</h1>
<form on:submit|preventDefault={formSubmit} class="flex gap-4">
<div class="flex w-full gap-4 items-center justify-center">
<div class="grow">
<input class="input" title="Input (text)" type="text" placeholder="Search for DID .." bind:value={$search} autocomplete='off' spellcheck='false' autocorrect='off' />
</div>
<div class="flex items-center gap-2">
<div>Only Sandbox</div>
<SlideToggle name="slide" bind:checked={onlySandbox} on:change={sandboxToggleHandler} active="bg-ats-sbox dark:bg-ats-sbox" />
</div>
</div>
<form on:submit|preventDefault={formSubmit} class="flex gap-4">
<div class="flex w-full gap-4 items-center justify-center">
<div class="grow">
<input
class="input"
title="Input (text)"
type="text"
placeholder="Search for DID .."
bind:value={$search}
autocomplete="off"
spellcheck="false"
autocorrect="off"
/>
</div>
<div class="flex items-center gap-2">
<div>Only Sandbox</div>
<SlideToggle
name="slide"
bind:checked={onlySandbox}
on:change={sandboxToggleHandler}
active="bg-ats-sbox dark:bg-ats-sbox"
/>
</div>
</div>
<!--button type="submit" class="btn variant-filled">Search</button-->
</form>
{#if sourceData === null}
<div class="flex justify-center items-center">
<div class="justify-center items-center">
<div class="text-center mb-6 text-lg">
{#if $search}
Searching for <code class="code text-xl">{$search}</code> {#if onlySandbox} (only sandbox){/if}...
{:else}
Looking for latest DIDs ...
{/if}
</div>
<div class="flex justify-center">
<ProgressRadial />
</div>
</div>
</div>
{:else}
<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)}):
{:else}
All DIDs {#if onlySandbox} on sandbox{/if} ({formatNumber(data.totalCount)}):
{/if}
</div>
<DIDTable {sourceData} {data} />
{/if}
</div>
</form>
{#if sourceData === null}
<div class="flex justify-center items-center">
<div class="justify-center items-center">
<div class="text-center mb-6 text-lg">
{#if $search}
Searching for <code class="code text-xl">{$search}</code>
{#if onlySandbox} (only sandbox){/if}...
{:else}
Looking for latest DIDs ...
{/if}
</div>
<div class="flex justify-center">
<ProgressRadial />
</div>
</div>
</div>
{:else}
<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)}):
{:else}
All DIDs {#if onlySandbox} on sandbox{/if} ({formatNumber(data.totalCount)}):
{/if}
</div>
<DIDTable {sourceData} {data} />
{/if}
</div>

Zobrazit soubor

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

Zobrazit soubor

@ -1,43 +1,49 @@
<script>
import { dateDistance, identicon } from '$lib/utils.js';
import { Table } from '@skeletonlabs/skeleton';
import { dateDistance, identicon } from '$lib/utils.js';
import { Table } from '@skeletonlabs/skeleton';
import { tableMapperValues, tableSourceValues } from '@skeletonlabs/skeleton';
import SourceSection from '$lib/components/SourceSection.svelte';
import Breadcrumb from '$lib/components/Breadcrumb.svelte';
import PDSTable from '$lib/components/PDSTable.svelte';
import SourceSection from '$lib/components/SourceSection.svelte';
import Breadcrumb from '$lib/components/Breadcrumb.svelte';
import PDSTable from '$lib/components/PDSTable.svelte';
export let data;
export let data;
const item = data.item;
const handles = item.revs[item.revs.length-1].operation.alsoKnownAs.map(h => h.replace(/^at:\/\//, ''))
const item = data.item;
const handles = item.revs[item.revs.length - 1].operation.alsoKnownAs.map((h) =>
h.replace(/^at:\/\//, '')
);
function tableMapperValuesLocal (source, keys) {
let i = 0
return tableSourceValues(source.map((row) => {
const mappedRow = {};
keys.forEach((key) => {
let val = row[key]
if (key === 'num') {
val = String('#'+i)
}
if (key === 'handle') {
val = row.operation.alsoKnownAs.map(a => a.replace(/^at:\/\//,'@')).join(', ')
}
if (key === 'createdAt') {
val = `<span title="${val}" alt="${val}">${dateDistance(val)}</span>`
}
return mappedRow[key] = val
})
i++
return mappedRow;
}).reverse())
}
function tableMapperValuesLocal(source, keys) {
let i = 0;
return tableSourceValues(
source
.map((row) => {
const mappedRow = {};
keys.forEach((key) => {
let val = row[key];
if (key === 'num') {
val = String('#' + i);
}
if (key === 'handle') {
val = row.operation.alsoKnownAs.map((a) => a.replace(/^at:\/\//, '@')).join(', ');
}
if (key === 'createdAt') {
val = `<span title="${val}" alt="${val}">${dateDistance(val)}</span>`;
}
return (mappedRow[key] = val);
});
i++;
return mappedRow;
})
.reverse()
);
}
const sourceData = item.revs
const historyTable = {
head: [ '#', 'Handle', 'CID', 'Age' ],
const sourceData = item.revs;
const historyTable = {
head: ['#', 'Handle', 'CID', 'Age'],
body: tableMapperValuesLocal(sourceData, ['num', 'handle', 'cid', 'createdAt']),
meta: tableMapperValues(sourceData, ['cid']),
meta: tableMapperValues(sourceData, ['cid'])
};
</script>
@ -46,31 +52,49 @@
</svelte:head>
<div class="container mx-auto p-8 space-y-8">
<Breadcrumb data={[
{ label: 'DIDs', link: '/did' },
{ label: `<span class="mr-2 badge ${item.env ? 'bg-ats-'+item.env : 'bg-gray-500'} text-white dark:text-black">${item.env}</span> federation`, link: `/did?q=${item.env}` }
]} />
<div class="flex gap-8">
<div>
<img src={identicon(item.did)} class="w-40 h-40 rounded-2xl bg-gray-200 dark:bg-gray-800 float-left" alt={item.did} />
</div>
<div class="grow">
<h1 class="h1">
<span class="opacity-50 font-normal">did:plc:</span><span class="font-semibold opacity-100">{item.did.replace(/^did:plc:/, '')}</span>
</h1>
<div class="text-2xl mt-4">
{@html handles.map(h => `<a href="https://bsky.app/profile/${h}" target="_blank" class="anchor">@${h}</a>`).join(', ')}
</div>
</div>
</div>
<Breadcrumb
data={[
{ label: 'DIDs', link: '/did' },
{
label: `<span class="mr-2 badge ${
item.env ? 'bg-ats-' + item.env : 'bg-gray-500'
} text-white dark:text-black">${item.env}</span> federation`,
link: `/did?q=${item.env}`
}
]}
/>
<div class="flex gap-8">
<div>
<img
src={identicon(item.did)}
class="w-40 h-40 rounded-2xl bg-gray-200 dark:bg-gray-800 float-left"
alt={item.did}
/>
</div>
<div class="grow">
<h1 class="h1">
<span class="opacity-50 font-normal">did:plc:</span><span class="font-semibold opacity-100"
>{item.did.replace(/^did:plc:/, '')}</span
>
</h1>
<div class="text-2xl mt-4">
{@html handles
.map(
(h) =>
`<a href="https://bsky.app/profile/${h}" target="_blank" class="anchor">@${h}</a>`
)
.join(', ')}
</div>
</div>
</div>
<h2 class="h2">Revisions <span class="font-normal text-2xl">({sourceData.length})</span></h2>
<Table source={historyTable} />
<h2 class="h2">Revisions <span class="font-normal text-2xl">({sourceData.length})</span></h2>
<Table source={historyTable} />
{#if data.pds}
<h2 class="h2">PDS</h2>
<PDSTable sourceData={data.pds} {data} />
{/if}
{#if data.pds}
<h2 class="h2">PDS</h2>
<PDSTable sourceData={data.pds} {data} />
{/if}
<SourceSection {data} model="did" />
</div>
<SourceSection {data} model="did" />
</div>

Zobrazit soubor

@ -1,18 +1,14 @@
import * as _ from "lodash";
import * as _ from 'lodash';
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, params }) {
const res = await fetch("https://api.atscan.net/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",
]);
return {
pds
};
const res = await fetch('https://api.atscan.net/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']);
return {
pds
};
}

Zobrazit soubor

@ -1,66 +1,63 @@
<script>
import { tableMapperValues, tableSourceValues } from '@skeletonlabs/skeleton';
import { dateDistance, formatNumber } from '$lib/utils.js';
import { goto } from '$app/navigation';
import { goto } from '$app/navigation';
import { writable } from 'svelte/store';
import { page } from '$app/stores';
import PDSTable from '$lib/components/PDSTable.svelte';
export let data;
const search = writable($page.url.searchParams.get('q') || '')
export let data;
function gotoNewTableState () {
const path = '/pds' + ($search !== '' ? `?q=${$search}` : '')
const currentPath = $page.url.pathname + $page.url.search
const search = writable($page.url.searchParams.get('q') || '');
function gotoNewTableState() {
const path = '/pds' + ($search !== '' ? `?q=${$search}` : '');
const currentPath = $page.url.pathname + $page.url.search;
if (currentPath === path) {
return null
return null;
}
goto(path, { keepFocus: true, noScroll: true })
goto(path, { keepFocus: true, noScroll: true });
}
search.subscribe((val) => {
gotoNewTableState()
return val
})
gotoNewTableState();
return val;
});
function selectionHandler (i) {
return goto(`/pds/${i.detail[0]}`)
}
$: sourceData = (function filterSourceData (bd) {
const tokens = $search.split(' ')
let base = JSON.parse(JSON.stringify(bd)) //.filter(d => d.inspect?.lastOnline)
let str = []
for (const t of tokens) {
const cmatch = t.match(/^country:([\w]{2})$/)
if (cmatch) {
console.log(cmatch[1])
base = base.filter(b => {
if (!b.ip) {
return false
}
return b.ip.country.toLowerCase() === cmatch[1].toLowerCase()
})
} else {
str.push(t)
}
}
const txt = str.join(' ').trim()
if (txt) {
base = base.filter(i => i.url.match(new RegExp(txt, 'i')))
}
return base
})(data.pds)
function formSubmit() {
const url = '?q='+$search
goto(url)
return false
function selectionHandler(i) {
return goto(`/pds/${i.detail[0]}`);
}
$: sourceData = (function filterSourceData(bd) {
const tokens = $search.split(' ');
let base = JSON.parse(JSON.stringify(bd)); //.filter(d => d.inspect?.lastOnline)
let str = [];
for (const t of tokens) {
const cmatch = t.match(/^country:([\w]{2})$/);
if (cmatch) {
console.log(cmatch[1]);
base = base.filter((b) => {
if (!b.ip) {
return false;
}
return b.ip.country.toLowerCase() === cmatch[1].toLowerCase();
});
} else {
str.push(t);
}
}
const txt = str.join(' ').trim();
if (txt) {
base = base.filter((i) => i.url.match(new RegExp(txt, 'i')));
}
return base;
})(data.pds);
function formSubmit() {
const url = '?q=' + $search;
goto(url);
return false;
}
</script>
<svelte:head>
@ -70,8 +67,14 @@
<div class="container mx-auto p-8 space-y-8">
<h1 class="h1">PDS Instances ({sourceData.length})</h1>
<form on:submit|preventDefault={formSubmit} class="flex gap-4">
<input class="input" title="Input (text)" type="text" placeholder="Search for PDS .." bind:value={$search} />
<input
class="input"
title="Input (text)"
type="text"
placeholder="Search for PDS .."
bind:value={$search}
/>
<!--button type="submit" class="btn variant-filled">Search</button-->
</form>
<PDSTable {sourceData} {data} />
</div>
</div>

Zobrazit soubor

@ -1,9 +1,10 @@
export async function load({ params, fetch }) {
const itemResp = await fetch(`https://api.atscan.net/pds/${params.host}`);
const didsResp = await fetch(`https://api.atscan.net/did?q=pds:${params.host}&limit=10`, {headers: { 'x-ats-wrapped': 'true' }});
return {
item: await itemResp.json(),
dids: await didsResp.json()
}
}
const itemResp = await fetch(`https://api.atscan.net/pds/${params.host}`);
const didsResp = await fetch(`https://api.atscan.net/did?q=pds:${params.host}&limit=10`, {
headers: { 'x-ats-wrapped': 'true' }
});
return {
item: await itemResp.json(),
dids: await didsResp.json()
};
}

Zobrazit soubor

@ -1,39 +1,83 @@
<script>
import Breadcrumb from '$lib/components/Breadcrumb.svelte';
import DIDTable from '$lib/components/DIDTable.svelte';
import { formatNumber, dateDistance, getFlagEmoji } from '$lib/utils.js';
import SourceSection from '$lib/components/SourceSection.svelte';
export let data;
import Breadcrumb from '$lib/components/Breadcrumb.svelte';
import DIDTable from '$lib/components/DIDTable.svelte';
import { formatNumber, dateDistance, getFlagEmoji } from '$lib/utils.js';
import SourceSection from '$lib/components/SourceSection.svelte';
const item = data.item;
export let data;
const infoMaps = []
infoMaps.push({
items: [
{ title: 'PLCs used', value: item.plcs.map(p => {
const host = p.replace(/^https?:\/\//, '')
return `<a href="/plc/${host}" class="anchor">${host}</a>`
}).join(', ')},
{ title: 'User domains', value: item.inspect?.current?.data?.availableUserDomains?.join(', ') || 'n/a' },
{ title: 'DID count', value: `${formatNumber(item.didsCount)} (<a href="/did?q=pds:${item.host}" class="anchor">list</a>)` },
{ title: 'Response time', value: item.inspect?.current?.ms ? item.inspect?.current?.ms+'ms (from Central Europe)' : `Error` },
{ title: 'Last online', value: (item.inspect?.lastOnline ? `${dateDistance(item.inspect?.lastOnline)} ago (${item.inspect?.lastOnline})` : 'never') + ` (<a href="${item.url}/xrpc/com.atproto.server.describeServer" class="anchor">inspect</a>)` },
item.inspect?.current?.err ? { title: 'Error', value: item.inspect?.current?.err ? `${item.inspect.current.err}` : '-' } : null,
{ title: 'Last scan', value: item.inspect?.current?.time ? `${dateDistance(item.inspect?.current?.time)} ago (${item.inspect?.current?.time})` : '-' },
]
})
infoMaps.push({
title: (item.ip ? `<span class="text-2xl">${getFlagEmoji(item.ip.country)}</span> <a href="http://ipinfo.io/${item.ip.ip}" target="_blank" class="anchor">${item.ip.ip}</a>` : 'ip not available'),
items: [
{ title: 'Host', value: item.ip?.hostname || '-' },
{ title: 'Location', value: item.ip ? `${item.ip.country} - ${item.ip.city}, ${item.ip.region} (<a href="https://www.google.com/maps?q=${item.ip.loc}" target="_blank" class="anchor">${item.ip.loc})</a>` : '-' },
//{ title: 'Coordinates', value: item.ip.loc },
{ title: 'AS / Organization', value: item.ip?.org || '-' },
//{ title: 'Postal code', value: item.ip.postal },
{ title: 'Timezone', value: item.ip?.timezone || '-' },
]
})
const item = data.item;
const infoMaps = [];
infoMaps.push({
items: [
{
title: 'PLCs used',
value: item.plcs
.map((p) => {
const host = p.replace(/^https?:\/\//, '');
return `<a href="/plc/${host}" class="anchor">${host}</a>`;
})
.join(', ')
},
{
title: 'User domains',
value: item.inspect?.current?.data?.availableUserDomains?.join(', ') || 'n/a'
},
{
title: 'DID count',
value: `${formatNumber(item.didsCount)} (<a href="/did?q=pds:${
item.host
}" class="anchor">list</a>)`
},
{
title: 'Response time',
value: item.inspect?.current?.ms
? item.inspect?.current?.ms + 'ms (from Central Europe)'
: `Error`
},
{
title: 'Last online',
value:
(item.inspect?.lastOnline
? `${dateDistance(item.inspect?.lastOnline)} ago (${item.inspect?.lastOnline})`
: 'never') +
` (<a href="${item.url}/xrpc/com.atproto.server.describeServer" class="anchor">inspect</a>)`
},
item.inspect?.current?.err
? {
title: 'Error',
value: item.inspect?.current?.err ? `${item.inspect.current.err}` : '-'
}
: null,
{
title: 'Last scan',
value: item.inspect?.current?.time
? `${dateDistance(item.inspect?.current?.time)} ago (${item.inspect?.current?.time})`
: '-'
}
]
});
infoMaps.push({
title: item.ip
? `<span class="text-2xl">${getFlagEmoji(item.ip.country)}</span> <a href="http://ipinfo.io/${
item.ip.ip
}" target="_blank" class="anchor">${item.ip.ip}</a>`
: 'ip not available',
items: [
{ title: 'Host', value: item.ip?.hostname || '-' },
{
title: 'Location',
value: item.ip
? `${item.ip.country} - ${item.ip.city}, ${item.ip.region} (<a href="https://www.google.com/maps?q=${item.ip.loc}" target="_blank" class="anchor">${item.ip.loc})</a>`
: '-'
},
//{ title: 'Coordinates', value: item.ip.loc },
{ title: 'AS / Organization', value: item.ip?.org || '-' },
//{ title: 'Postal code', value: item.ip.postal },
{ title: 'Timezone', value: item.ip?.timezone || '-' }
]
});
</script>
<svelte:head>
@ -41,34 +85,56 @@
</svelte:head>
<div class="container mx-auto p-8 space-y-8">
<Breadcrumb data={[
{ label: 'PDS Instances', link: '/pds' },
{ label: `<span class="mr-2 badge ${item.env === 'bsky' ? 'bg-ats-bsky' : (item.env === 'sandbox' ? 'bg-ats-sbox' : 'bg-gray-500')} text-white dark:text-black">${item.env}</span> federation`, link: `/pds?federation=${item.env}` }
]} />
<Breadcrumb
data={[
{ label: 'PDS Instances', link: '/pds' },
{
label: `<span class="mr-2 badge ${
item.env === 'bsky'
? 'bg-ats-bsky'
: item.env === 'sandbox'
? 'bg-ats-sbox'
: 'bg-gray-500'
} text-white dark:text-black">${item.env}</span> federation`,
link: `/pds?federation=${item.env}`
}
]}
/>
<h1 class="h1">
{item.host}
</h1>
<div class="lg:grid grid-cols-2 gap-4">
{#each infoMaps as map}
<div class="card bg-white/20 p-4 lg:mb-0 mb-2">
{#if map.title}
<h3 class="h4 uppercase mb-2">{@html map.title}</h3>
{/if}
<div class="">
{#each map.items.filter(i => Boolean(i)) as i}
<div class="flex gap-2 w-full mb-1"><div class="font-semibold">{i.title}</div><div class="">{@html i.value}</div></div>
{/each}
</div>
</div>
{/each}
</div>
{item.host}
</h1>
<div class="lg:grid grid-cols-2 gap-4">
{#each infoMaps as map}
<div class="card bg-white/20 p-4 lg:mb-0 mb-2">
{#if map.title}
<h3 class="h4 uppercase mb-2">{@html map.title}</h3>
{/if}
<div class="">
{#each map.items.filter((i) => Boolean(i)) as i}
<div class="flex gap-2 w-full mb-1">
<div class="font-semibold">{i.title}</div>
<div class="">{@html i.value}</div>
</div>
{/each}
</div>
</div>
{/each}
</div>
<h2 class="h2">
<a href="/did?q=pds:{item.host}">DIDs</a>
<span class="font-normal text-2xl">({formatNumber(data.dids.count)})</span>
</h2>
<DIDTable sourceData={data.dids.items} {data} />
{#if data.dids.count > data.dids.items.length}
<div>
<a href="/did?q=pds:{item.host}"
><button class="btn variant-filled"
>Show all {formatNumber(data.dids.count)} DIDs hosted by {item.host}</button
></a
>
</div>
{/if}
<h2 class="h2"><a href="/did?q=pds:{item.host}">DIDs</a> <span class="font-normal text-2xl">({formatNumber(data.dids.count)})</span></h2>
<DIDTable sourceData={data.dids.items} {data} />
{#if data.dids.count > data.dids.items.length}
<div><a href="/did?q=pds:{item.host}"><button class="btn variant-filled">Show all {formatNumber(data.dids.count)} DIDs hosted by {item.host}</button></a></div>
{/if}
<SourceSection {data} model="pds" />
</div>
<SourceSection {data} model="pds" />
</div>

Zobrazit soubor

@ -1,44 +1,45 @@
<script>
import Table from '$lib/components/Table.svelte';
import Table from '$lib/components/Table.svelte';
import { tableMapperValues, tableSourceValues } from '@skeletonlabs/skeleton';
import { dateDistance, formatNumber } from '$lib/utils.js';
import { dateDistance, formatNumber } from '$lib/utils.js';
export let data;
export let data;
function tableMapperValuesLocal (source, keys) {
return tableSourceValues(source.map((row) => {
const mappedRow = {};
keys.forEach((key) => {
let val = row[key]
if (key === 'world') {
val = `<span class="badge variant-filled ${row.color} dark:${row.color} opacity-70 text-white dark:text-black">${row.name}</span>`
}
if (key === 'host') {
val = `<span class="text-xl font-semibold">${val}</span>`
}
if (key === 'code') {
val = `<div class="inline-block font-mono">${val}</div>`
}
if (key === 'lastUpdate') {
val = dateDistance(val)
}
if (key === 'didsCount') {
val = `<a href="/did?q=plc:${row.host}" class="anchor">${formatNumber(val)}</a>`
}
return mappedRow[key] = val
})
return mappedRow;
}))
}
function tableMapperValuesLocal(source, keys) {
return tableSourceValues(
source.map((row) => {
const mappedRow = {};
keys.forEach((key) => {
let val = row[key];
if (key === 'world') {
val = `<span class="badge variant-filled ${row.color} dark:${row.color} opacity-70 text-white dark:text-black">${row.name}</span>`;
}
if (key === 'host') {
val = `<span class="text-xl font-semibold">${val}</span>`;
}
if (key === 'code') {
val = `<div class="inline-block font-mono">${val}</div>`;
}
if (key === 'lastUpdate') {
val = dateDistance(val);
}
if (key === 'didsCount') {
val = `<a href="/did?q=plc:${row.host}" class="anchor">${formatNumber(val)}</a>`;
}
return (mappedRow[key] = val);
});
return mappedRow;
})
);
}
const sourceData = data.plc;
const tableSimple = {
const sourceData = data.plc;
const tableSimple = {
// A list of heading labels.
head: ['Federation', 'Host', 'DIDs', 'Last mod'],
body: tableMapperValuesLocal(sourceData, ['world', 'host', 'didsCount', 'lastUpdate']),
meta: tableMapperValues(sourceData, ['position', 'name', 'symbol', 'weight']),
meta: tableMapperValues(sourceData, ['position', 'name', 'symbol', 'weight'])
};
</script>
<svelte:head>
@ -48,4 +49,4 @@
<div class="container mx-auto p-8 space-y-8">
<h1 class="h1">PLC Directories ({sourceData.length})</h1>
<Table source={tableSimple} />
</div>
</div>

Zobrazit soubor

@ -1,16 +1,16 @@
import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from "@sveltejs/kit/vite";
import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter(),
},
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter()
}
};
export default config;

Zobrazit soubor

@ -1,27 +1,21 @@
import { join } from "path";
import skeleton from "@skeletonlabs/skeleton/tailwind/skeleton.cjs";
import { join } from 'path';
import skeleton from '@skeletonlabs/skeleton/tailwind/skeleton.cjs';
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: "class",
content: [
"./src/**/*.{html,js,svelte,ts}",
join(
require.resolve("@skeletonlabs/skeleton"),
"../**/*.{html,js,svelte,ts}",
),
"../spec/*.yaml",
],
theme: {
extend: {
colors: {
'ats-bsky': '#3399ff',
'ats-sbox': '#eab308',
}
}
},
plugins: [
require('@tailwindcss/forms'),
...skeleton()
],
darkMode: 'class',
content: [
'./src/**/*.{html,js,svelte,ts}',
join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}'),
'../spec/*.yaml'
],
theme: {
extend: {
colors: {
'ats-bsky': '#3399ff',
'ats-sbox': '#eab308'
}
}
},
plugins: [require('@tailwindcss/forms'), ...skeleton()]
};

Zobrazit soubor

@ -1,6 +1,6 @@
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()],
plugins: [sveltekit()]
});