zrcadlo https://github.com/atscan/atscan
This commit is contained in:
rodič
4435c081b1
revize
48078e553c
6
Makefile
6
Makefile
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
static
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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 {}
|
||||
}
|
||||
|
|
|
@ -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%
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>›</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>›</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>
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
|
||||
export function load() {
|
||||
// ...
|
||||
throw redirect(302, '/did');
|
||||
}
|
||||
// ...
|
||||
throw redirect(302, '/did');
|
||||
}
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()]
|
||||
};
|
||||
|
|
|
@ -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()]
|
||||
});
|
||||
|
|
Načítá se…
Odkázat v novém úkolu