feat: Implement API V3 Testing Dashboard and Security Hardening
- Added comprehensive System Testing dashboard with live V3 trace tool. - Implemented Section 2D 'Fail-Fast' protocol in get_object helper. - Added reactive JWT synchronization in root layout to ensure V3 consistency. - Resolved Tailwind 4 @apply compilation errors in testing page.
This commit is contained in:
@@ -169,6 +169,13 @@ export const get_object = async function get_object({
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// FAIL FAST (Section 2D): Do not retry on Auth/Permission failures
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
console.error(`API Auth Failure (${response.status}). Failing fast as per Section 2D protocol.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('The response was not ok. Throwing an error!');
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
@@ -681,6 +681,17 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Sync JWT from local storage to API config for V3 endpoints
|
||||
$effect(() => {
|
||||
if ($ae_api.jwt !== $ae_loc.jwt) {
|
||||
if (log_lvl) console.log('ROOT: Syncing JWT to API config');
|
||||
$ae_api = {
|
||||
...$ae_api,
|
||||
jwt: $ae_loc.jwt
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (browser) {
|
||||
const interval = setInterval(() => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { api } from '$lib/api/api';
|
||||
import { ae_loc, ae_sess, ae_api, slct } from '$lib/stores/ae_stores';
|
||||
import { ae_loc, ae_api, ae_sess } from '$lib/stores/ae_stores';
|
||||
import { get_object } from '$lib/ae_api/api_get_object';
|
||||
import { post_object } from '$lib/ae_api/api_post_object';
|
||||
import {
|
||||
@@ -10,6 +10,8 @@
|
||||
Server,
|
||||
User,
|
||||
Users,
|
||||
UserCheck,
|
||||
Building2,
|
||||
MapPin,
|
||||
Contact,
|
||||
ShieldCheck,
|
||||
@@ -17,24 +19,40 @@
|
||||
RefreshCcw,
|
||||
Trash2,
|
||||
Bug,
|
||||
Zap
|
||||
Zap,
|
||||
Eye,
|
||||
EyeOff,
|
||||
Key,
|
||||
Unlock,
|
||||
ShieldAlert,
|
||||
ArrowRightLeft,
|
||||
Code,
|
||||
FlaskConical,
|
||||
Info
|
||||
} from 'lucide-svelte';
|
||||
|
||||
// Core Module Imports
|
||||
import { load_ae_obj_li__account } from '$lib/ae_core/ae_core__account';
|
||||
import { load_ae_obj_li__site, lookup_site_domain_v3 } from '$lib/ae_core/ae_core__site';
|
||||
import { load_ae_obj_li__person } from '$lib/ae_core/ae_core__person';
|
||||
import { load_ae_obj_li__address } from '$lib/ae_core/ae_core__address';
|
||||
import { load_ae_obj_li__contact } from '$lib/ae_core/ae_core__contact';
|
||||
import { load_ae_obj_li__user } from '$lib/ae_core/ae_core__user';
|
||||
import { lookup_site_domain_v3 } from '$lib/ae_core/ae_core__site';
|
||||
import { load_ae_obj_id__user } from '$lib/ae_core/ae_core__user';
|
||||
import { db_core } from '$lib/ae_core/db_core';
|
||||
|
||||
// State Variables
|
||||
let test_result: any = $state(null);
|
||||
let trace_details: any = $state(null);
|
||||
let test_fqdn = $state('');
|
||||
let db_counts: Record<string, number> = $state({});
|
||||
let show_raw_keys = $state(false);
|
||||
|
||||
// Trace Controls
|
||||
let trace_use_jwt = $state(true);
|
||||
let trace_jwt_method = $state('header'); // 'header' or 'url'
|
||||
let trace_use_bypass = $state(false);
|
||||
let trace_agent_key = 'IDF68Em5X4HTZlswRNgepQ';
|
||||
let trace_use_agent_key = $state(false);
|
||||
|
||||
onMount(async () => {
|
||||
console.log('Testing Dashboard: +page.svelte');
|
||||
console.log('Testing Dashboard: +page.svelte mounted');
|
||||
await update_db_counts();
|
||||
});
|
||||
|
||||
@@ -55,6 +73,7 @@
|
||||
|
||||
async function run_test(name: string, test_fn: () => Promise<any>) {
|
||||
test_result = 'loading...';
|
||||
trace_details = null;
|
||||
try {
|
||||
const result = await test_fn();
|
||||
test_result = { test: name, timestamp: new Date().toISOString(), result };
|
||||
@@ -64,97 +83,79 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SYSTEM & INFRASTRUCTURE TESTS
|
||||
*/
|
||||
async function run_trace_test() {
|
||||
test_result = 'loading...';
|
||||
const endpoint = '/v3/crud/journal/';
|
||||
const params: Record<string, any> = { limit: 1 };
|
||||
|
||||
const custom_headers: Record<string, string> = {};
|
||||
const active_key = trace_use_agent_key ? trace_agent_key : $ae_api.api_secret_key;
|
||||
custom_headers['x-aether-api-key'] = active_key;
|
||||
|
||||
if (trace_use_bypass) {
|
||||
custom_headers['x-no-account-id'] = 'bypass';
|
||||
} else {
|
||||
custom_headers['x-account-id'] = $ae_loc.account_id || 'nqOzejLCDXM';
|
||||
}
|
||||
|
||||
if (trace_use_jwt && $ae_loc.jwt) {
|
||||
if (trace_jwt_method === 'header') {
|
||||
custom_headers['Authorization'] = `Bearer ${$ae_loc.jwt}`;
|
||||
} else {
|
||||
params['jwt'] = $ae_loc.jwt;
|
||||
}
|
||||
}
|
||||
|
||||
trace_details = {
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: `${$ae_api.base_url}${endpoint}`,
|
||||
params,
|
||||
headers: custom_headers
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const url = new URL(endpoint, $ae_api.base_url);
|
||||
Object.keys(params).forEach(k => url.searchParams.append(k, String(params[k])));
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
method: 'GET',
|
||||
headers: custom_headers
|
||||
});
|
||||
|
||||
const status = response.status;
|
||||
const statusText = response.statusText;
|
||||
let body;
|
||||
try { body = await response.json(); } catch (e) { body = await response.text(); }
|
||||
|
||||
trace_details.response = { status, statusText, body };
|
||||
test_result = { test: 'V3 Journal Trace', status, success: response.ok };
|
||||
} catch (error: any) {
|
||||
test_result = { test: 'V3 Journal Trace', error: error.message };
|
||||
trace_details.response_error = error;
|
||||
}
|
||||
}
|
||||
|
||||
const test_site_domain_lookup = () => run_test('Site Domain Lookup', async () => {
|
||||
const fqdn = test_fqdn || window.location.host;
|
||||
return await lookup_site_domain_v3({ api_cfg: $ae_api, fqdn, log_lvl: 1 });
|
||||
});
|
||||
|
||||
const test_bootstrap_bypass = () => run_test('Bootstrap Paradox Bypass', async () => {
|
||||
const stripped_api_cfg = { base_url: $ae_api.base_url, headers: {} };
|
||||
const fqdn = test_fqdn || window.location.host;
|
||||
return await post_object({
|
||||
api_cfg: stripped_api_cfg,
|
||||
endpoint: '/v3/crud/site_domain/search',
|
||||
headers: { 'x-no-account-id': 'System Test' },
|
||||
data: { q: fqdn },
|
||||
log_lvl: 1
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* CORE MODULE TESTS (V3)
|
||||
*/
|
||||
|
||||
const test_load_accounts = () => run_test('Load Accounts', async () => {
|
||||
return await load_ae_obj_li__account({ api_cfg: $ae_api, enabled: 'all', log_lvl: 1 });
|
||||
});
|
||||
|
||||
const test_load_people = () => run_test('Load People (Account)', async () => {
|
||||
return await load_ae_obj_li__person({
|
||||
const test_whoami = () => run_test('Who Am I? (JWT Test)', async () => {
|
||||
if (!$ae_loc.user_id) throw new Error('No user_id found in session.');
|
||||
return await load_ae_obj_id__user({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_id: $ae_loc.account_id,
|
||||
enabled: 'all',
|
||||
user_id: $ae_loc.user_id,
|
||||
try_cache: false,
|
||||
log_lvl: 1
|
||||
});
|
||||
});
|
||||
|
||||
const test_load_addresses = () => run_test('Load Addresses', async () => {
|
||||
return await load_ae_obj_li__address({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_id: $ae_loc.account_id,
|
||||
enabled: 'all',
|
||||
log_lvl: 1
|
||||
});
|
||||
});
|
||||
|
||||
const test_load_contacts = () => run_test('Load Contacts', async () => {
|
||||
return await load_ae_obj_li__contact({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_id: $ae_loc.account_id,
|
||||
enabled: 'all',
|
||||
log_lvl: 1
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* USER MANAGEMENT & SCOPING TESTS
|
||||
*/
|
||||
|
||||
const test_load_users_account = () => run_test('Load Users (Account Only)', async () => {
|
||||
return await load_ae_obj_li__user({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_id: $ae_loc.account_id,
|
||||
include_global: false,
|
||||
log_lvl: 1
|
||||
});
|
||||
});
|
||||
|
||||
const test_load_users_global = () => run_test('Load Users (Global Only)', async () => {
|
||||
return await load_ae_obj_li__user({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_id: null,
|
||||
include_global: true,
|
||||
log_lvl: 1
|
||||
});
|
||||
});
|
||||
|
||||
const test_load_users_all = () => run_test('Load Users (All)', async () => {
|
||||
return await load_ae_obj_li__user({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_id: $ae_loc.account_id,
|
||||
include_global: true,
|
||||
log_lvl: 1
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* LOCAL DATABASE (DEXIE) TESTS
|
||||
*/
|
||||
|
||||
const clear_local_cache = () => run_test('Clear Local Cache', async () => {
|
||||
await Promise.all([
|
||||
db_core.user.clear(),
|
||||
@@ -169,149 +170,277 @@
|
||||
|
||||
</script>
|
||||
|
||||
<div class="container mx-auto p-4 space-y-8">
|
||||
<header class="flex justify-between items-center bg-surface-50-900-token p-6 rounded-container shadow-xl">
|
||||
<div class="space-y-1">
|
||||
<h1 class="h1 flex items-center gap-3">
|
||||
<Bug class="text-error-500" /> System Testing
|
||||
</h1>
|
||||
<p class="opacity-60">Validation dashboard for Aether Platform V3</p>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<div class="card p-2 variant-soft-secondary flex items-center gap-4 text-sm">
|
||||
<div class="flex flex-col items-center px-2">
|
||||
<span class="font-bold">{db_counts.user ?? 0}</span>
|
||||
<span class="text-[10px] uppercase">Users</span>
|
||||
</div>
|
||||
<div class="divider-vertical h-8" />
|
||||
<div class="flex flex-col items-center px-2">
|
||||
<span class="font-bold">{db_counts.person ?? 0}</span>
|
||||
<span class="text-[10px] uppercase">People</span>
|
||||
</div>
|
||||
<div class="divider-vertical h-8" />
|
||||
<button class="btn-icon btn-icon-sm" onclick={update_db_counts} title="Refresh Counts">
|
||||
<RefreshCcw size={14} />
|
||||
</button>
|
||||
<!-- Outer wrapper to enable scrolling if parent is overflow-hidden -->
|
||||
<div class="h-full w-full overflow-y-auto overflow-x-hidden bg-transparent">
|
||||
<div class="container mx-auto p-4 space-y-8 pb-20">
|
||||
<header class="flex justify-between items-center bg-surface-50-900-token p-6 rounded-container shadow-xl border border-gray-500">
|
||||
<div class="space-y-1">
|
||||
<h1 class="h1 flex items-center gap-3">
|
||||
<Bug class="text-error-500" /> System Testing
|
||||
</h1>
|
||||
<p class="opacity-60 text-sm">Validation dashboard for Aether Platform V3</p>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="grid grid-cols-1 xl:grid-cols-[1fr_400px] gap-8">
|
||||
<!-- Control Panel -->
|
||||
<main class="space-y-6">
|
||||
<!-- Global Config -->
|
||||
<div class="card p-4 variant-soft-surface space-y-4">
|
||||
<header class="flex items-center gap-2 border-b border-surface-500/30 pb-2">
|
||||
<Globe size={18} />
|
||||
<h3 class="h3">Environment Config</h3>
|
||||
</header>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<label class="label">
|
||||
<span>Current Account ID</span>
|
||||
<input type="text" class="input" readonly value={$ae_loc.account_id || 'Not Set'} />
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Override FQDN (for Domain Lookup)</span>
|
||||
<input type="text" class="input" placeholder={window.location.host} bind:value={test_fqdn} />
|
||||
</label>
|
||||
<div class="flex gap-4">
|
||||
<div class="card p-3 variant-soft-secondary flex items-center gap-4 shadow-inner">
|
||||
<div class="flex flex-col items-center px-2">
|
||||
<span class="font-bold">{db_counts.user ?? 0}</span>
|
||||
<span class="text-[10px] uppercase opacity-60">Users</span>
|
||||
</div>
|
||||
<div class="divider-vertical h-8 border-l border-gray-500"></div>
|
||||
<div class="flex flex-col items-center px-2">
|
||||
<span class="font-bold">{db_counts.person ?? 0}</span>
|
||||
<span class="text-[10px] uppercase opacity-60">People</span>
|
||||
</div>
|
||||
<div class="divider-vertical h-8 border-l border-gray-500"></div>
|
||||
<button class="btn-icon btn-icon-sm hover:variant-filled-secondary transition-all" onclick={update_db_counts} title="Refresh Row Counts">
|
||||
<RefreshCcw size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- Infra Tests -->
|
||||
<div class="card p-4 space-y-4 shadow-lg">
|
||||
<header class="flex items-center gap-2 text-primary-500">
|
||||
<Server size={18} />
|
||||
<h4 class="h4 font-bold">Infrastructure</h4>
|
||||
</header>
|
||||
<div class="flex flex-col gap-2">
|
||||
<button class="btn variant-filled-primary justify-start" onclick={test_site_domain_lookup}>
|
||||
<Zap size={14} class="mr-2" /> Lookup Site Domain
|
||||
</button>
|
||||
<button class="btn variant-ghost-error justify-start" onclick={test_bootstrap_bypass}>
|
||||
<ShieldCheck size={14} class="mr-2" /> Bootstrap Bypass Test
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Core Module Tests -->
|
||||
<div class="card p-4 space-y-4 shadow-lg">
|
||||
<header class="flex items-center gap-2 text-secondary-500">
|
||||
<Database size={18} />
|
||||
<h4 class="h4 font-bold">Core Modules (V3)</h4>
|
||||
</header>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<button class="btn variant-soft-secondary btn-sm" onclick={test_load_accounts}>Accounts</button>
|
||||
<button class="btn variant-soft-secondary btn-sm" onclick={test_load_people}>People</button>
|
||||
<button class="btn variant-soft-secondary btn-sm" onclick={test_load_addresses}>Addresses</button>
|
||||
<button class="btn variant-soft-secondary btn-sm" onclick={test_load_contacts}>Contacts</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Scoping -->
|
||||
<div class="card p-4 space-y-4 shadow-lg md:col-span-2">
|
||||
<header class="flex items-center gap-2 text-tertiary-500">
|
||||
<Users size={18} />
|
||||
<h4 class="h4 font-bold">User Management Scoping</h4>
|
||||
</header>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button class="btn variant-filled-tertiary" onclick={test_load_users_account}>
|
||||
<ShieldCheck size={14} class="mr-2" /> Current Account Only
|
||||
</button>
|
||||
<button class="btn variant-filled-tertiary" onclick={test_load_users_global}>
|
||||
<Globe size={14} class="mr-2" /> Global Only
|
||||
</button>
|
||||
<button class="btn variant-filled-tertiary" onclick={test_load_users_all}>
|
||||
<RefreshCcw size={14} class="mr-2" /> All (Inclusive)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Maintenance -->
|
||||
<div class="card p-4 variant-soft-error flex justify-between items-center">
|
||||
<div class="flex items-center gap-3">
|
||||
<Trash2 class="text-error-500" />
|
||||
<div>
|
||||
<p class="font-bold text-error-500">Local Cache Management</p>
|
||||
<p class="text-xs opacity-60">Wipe local IndexedDB tables for core objects</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn variant-filled-error" onclick={clear_local_cache}>Clear Local IDB</button>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Result View -->
|
||||
<aside class="space-y-4">
|
||||
<div class="card p-4 h-full bg-surface-100-800-token overflow-hidden flex flex-col sticky top-4 max-h-[calc(100vh-2rem)] shadow-2xl">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="h3">Output</h3>
|
||||
{#if test_result === 'loading...'}
|
||||
<span class="badge variant-filled-warning animate-pulse">Running...</span>
|
||||
{:else if test_result?.error}
|
||||
<span class="badge variant-filled-error">Failed</span>
|
||||
{:else if test_result}
|
||||
<span class="badge variant-filled-success">Success</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grid grid-cols-1 xl:grid-cols-[1fr_400px] gap-8">
|
||||
<main class="space-y-6">
|
||||
|
||||
<div class="flex-1 overflow-auto bg-black/20 rounded-container p-4 font-mono text-xs">
|
||||
{#if test_result === 'loading...'}
|
||||
<div class="placeholder animate-pulse space-y-4">
|
||||
<div class="placeholder-circle w-12" />
|
||||
<div class="placeholder-line w-full" />
|
||||
<div class="placeholder-line w-3/4" />
|
||||
<!-- Session Context Card -->
|
||||
<div class="card p-6 variant-soft-surface space-y-4 border border-gray-500 shadow-lg">
|
||||
<header class="flex justify-between items-center border-b border-gray-500 pb-3">
|
||||
<div class="flex items-center gap-2 text-surface-700 dark:text-surface-300">
|
||||
<User size={20} />
|
||||
<h3 class="h3 font-bold">Active Session Context</h3>
|
||||
</div>
|
||||
{:else if test_result}
|
||||
<pre>{JSON.stringify(test_result, null, 2)}</pre>
|
||||
{:else}
|
||||
<div class="flex flex-col items-center justify-center h-full opacity-30 py-20 text-center">
|
||||
<Zap size={48} class="mb-4" />
|
||||
<p>Select a validation test<br/>to begin audit</p>
|
||||
<span class="badge variant-filled-primary font-mono p-2" title="Your current logical rank in the application (e.g. super, manager, trusted).">
|
||||
Level: {$ae_loc.access_type || 'anonymous'}
|
||||
</span>
|
||||
</header>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div class="flex flex-col p-3 bg-gray-500/10 rounded" title="The Aether Username from the User object.">
|
||||
<span class="text-[10px] uppercase opacity-50 font-bold">Username</span>
|
||||
<span class="font-mono text-sm truncate">{$ae_loc.user?.username || '--'}</span>
|
||||
</div>
|
||||
<div class="flex flex-col p-3 bg-gray-500/10 rounded" title="Full Name from the linked Person object.">
|
||||
<span class="text-[10px] uppercase opacity-50 font-bold">Full Name</span>
|
||||
<span class="text-sm font-semibold truncate">{$ae_loc.person?.full_name || '--'}</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center p-3 rounded bg-gray-500/10" title="TRUE if session has matched a 'Trusted' level site passcode.">
|
||||
<span class="text-[10px] uppercase opacity-50 font-bold">Trusted</span>
|
||||
<div class="flex items-center gap-1 mt-1">
|
||||
{#if $ae_loc.trusted_access}
|
||||
<ShieldCheck size={18} class="text-success-500" />
|
||||
<span class="text-[10px] text-success-500 font-bold uppercase">YES</span>
|
||||
{:else}
|
||||
<Unlock size={18} class="opacity-30" />
|
||||
<span class="text-[10px] opacity-30 font-bold uppercase">NO</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center p-3 rounded bg-gray-500/10" title="TRUE if user has signed in with credentials (Username/Email).">
|
||||
<span class="text-[10px] uppercase opacity-50 font-bold">Authenticated</span>
|
||||
<div class="flex items-center gap-1 mt-1">
|
||||
{#if $ae_loc.authenticated_access}
|
||||
<ShieldCheck size={18} class="text-success-500" />
|
||||
<span class="text-[10px] text-success-500 font-bold uppercase">YES</span>
|
||||
{:else}
|
||||
<Unlock size={18} class="opacity-30" />
|
||||
<span class="text-[10px] opacity-30 font-bold uppercase">NO</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Security Dashboard -->
|
||||
<div class="card p-6 variant-soft-warning space-y-6 shadow-lg border border-warning-500">
|
||||
<header class="flex justify-between items-center border-b border-warning-500 pb-3">
|
||||
<div class="flex items-center gap-2 text-warning-700 dark:text-warning-500">
|
||||
<Key size={20} />
|
||||
<h3 class="h3 font-bold">API Security Controls</h3>
|
||||
</div>
|
||||
<button class="btn btn-sm variant-filled-warning shadow-md transition-all hover:scale-105" onclick={() => show_raw_keys = !show_raw_keys} title="Toggle visibility of the raw secret keys for character verification.">
|
||||
{#if show_raw_keys} <EyeOff size={14} class="mr-2" /> Hide Raw {:else} <Eye size={14} class="mr-2" /> Inspect Raw {/if}
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<label class="label space-y-2">
|
||||
<span class="text-[10px] uppercase font-bold opacity-70 flex items-center gap-1" title="Primary secret key identifying this application. Required for ALL V3 requests. Sent as 'x-aether-api-key'.">
|
||||
Active API Key <span class="text-secondary-500"><Info size={10} /></span>
|
||||
</span>
|
||||
<div class="flex shadow-sm border border-gray-500 rounded-lg overflow-hidden bg-surface-500/10">
|
||||
<input type={show_raw_keys ? 'text' : 'password'} readonly value={$ae_api.api_secret_key || 'MISSING'} class="flex-1 font-mono text-xs p-3 bg-transparent border-none focus:ring-0" />
|
||||
<div class="bg-gray-500/20 p-3 flex items-center"><span class="fas" class:fa-check-circle={$ae_api.api_secret_key} class:text-success-500={$ae_api.api_secret_key}></span></div>
|
||||
</div>
|
||||
</label>
|
||||
<label class="label space-y-2">
|
||||
<span class="text-[10px] uppercase font-bold opacity-70 flex items-center gap-1" title="Token received after successful user login. Stored in localStorage ($ae_loc.jwt). Required for multi-tenant isolation.">
|
||||
Local JWT Store <span class="text-secondary-500"><Info size={10} /></span>
|
||||
</span>
|
||||
<div class="flex shadow-sm border border-gray-500 rounded-lg overflow-hidden bg-surface-500/10">
|
||||
<input type={show_raw_keys ? 'text' : 'password'} readonly value={$ae_loc.jwt || 'No Token'} class="flex-1 font-mono text-xs p-3 bg-transparent border-none focus:ring-0" />
|
||||
<div class="bg-gray-500/20 p-3 flex items-center"><span class="fas" class:fa-key={$ae_loc.jwt} class:text-warning-500={$ae_loc.jwt}></span></div>
|
||||
</div>
|
||||
</label>
|
||||
<label class="label space-y-2">
|
||||
<span class="text-[10px] uppercase font-bold opacity-70 flex items-center gap-1" title="Verifies if the Local JWT has been synced to the active API configuration ($ae_api.jwt).">
|
||||
Sync Status <span class="text-secondary-500"><Info size={10} /></span>
|
||||
</span>
|
||||
<div class="card p-2 text-center text-xs font-bold border border-gray-500 shadow-sm flex items-center justify-center h-[46px] bg-transparent">
|
||||
{#if $ae_api.jwt === $ae_loc.jwt}
|
||||
<span class="text-success-600 dark:text-success-400 flex items-center gap-1"><ShieldCheck size={14}/> HANDSHAKE OK</span>
|
||||
{:else}
|
||||
<span class="text-error-600 dark:text-error-400 flex items-center gap-1"><ShieldAlert size={14}/> MISMATCH</span>
|
||||
{/if}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{#if show_raw_keys}
|
||||
<div class="p-4 bg-black/20 rounded-container text-[10px] font-mono break-all space-y-2 border border-gray-500 shadow-inner">
|
||||
<p class="text-warning-500 font-bold border-b border-gray-500 pb-1 flex items-center gap-2 uppercase tracking-widest">
|
||||
<Bug size={12} /> Raw Strings
|
||||
</p>
|
||||
<div class="grid grid-cols-[80px_1fr] gap-x-2 gap-y-1">
|
||||
<span class="opacity-50">CUR_KEY:</span> <span>{$ae_api.api_secret_key}</span>
|
||||
<span class="opacity-50">AGT_KEY:</span> <span>{trace_agent_key}</span>
|
||||
<span class="opacity-50">LOC_JWT:</span> <span>{$ae_loc.jwt || 'NULL'}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="space-y-4 border-t border-gray-500 pt-6">
|
||||
<header class="flex items-center gap-2">
|
||||
<FlaskConical size={18} class="text-secondary-500"/>
|
||||
<h4 class="h4 font-bold">V3 Trace Controls</h4>
|
||||
</header>
|
||||
<div class="p-4 bg-gray-500/5 rounded-container border border-gray-500 space-y-4 shadow-inner">
|
||||
<div class="flex flex-wrap gap-6 text-sm">
|
||||
<label class="flex items-center gap-2 cursor-pointer group" title="When checked, the request will include the JWT token. Mandatory for nearly all V3 CRUD calls.">
|
||||
<input type="checkbox" class="checkbox" bind:checked={trace_use_jwt} />
|
||||
<span class="group-hover:text-secondary-500 transition-colors">Include JWT</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 cursor-pointer group" title="Sets 'x-no-account-id: bypass'. Combined with valid API Key, grants superuser access across all accounts.">
|
||||
<input type="checkbox" class="checkbox" bind:checked={trace_use_bypass} />
|
||||
<span class="group-hover:text-error-500 transition-colors">Bypass Header</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 cursor-pointer group" title="Test using the dedicated Agent API Key (IDF68...).">
|
||||
<input type="checkbox" class="checkbox" bind:checked={trace_use_agent_key} />
|
||||
<span class="text-secondary-600 font-bold underline decoration-dotted">Use Agent Key</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-6 pt-3 border-t border-gray-500 mt-2" title="Section 6: JWT can be sent via Header (standard) or URL Parameter (for direct links/downloads).">
|
||||
<span class="text-[10px] uppercase font-bold opacity-50 flex items-center gap-1">JWT Method <span class="text-secondary-500"><Info size={10} /></span>:</span>
|
||||
<label class="flex items-center gap-3 cursor-pointer">
|
||||
<input type="radio" name="jwt_method" value="header" bind:group={trace_jwt_method} class="radio" />
|
||||
<span class="text-xs font-semibold">Header (Bearer)</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-3 cursor-pointer">
|
||||
<input type="radio" name="jwt_method" value="url" bind:group={trace_jwt_method} class="radio" />
|
||||
<span class="text-xs font-semibold">URL Param (?jwt=)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<button class="btn variant-filled-secondary p-4 font-bold shadow-lg transition-all hover:brightness-110 active:scale-95 flex items-center justify-center gap-2" onclick={run_trace_test} title="Initiate GET request to /v3/crud/journal/ and capture headers.">
|
||||
<ArrowRightLeft size={16} /> RUN TRACE
|
||||
</button>
|
||||
<button class="btn variant-filled-warning p-4 font-bold shadow-lg transition-all hover:brightness-110 active:scale-95 flex items-center justify-center gap-2" disabled={!$ae_loc.jwt} onclick={test_whoami} title="Verify user authentication by loading your own profile.">
|
||||
<UserCheck size={16} /> WHO AM I?
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Trace Visualizer -->
|
||||
{#if trace_details}
|
||||
<div class="card p-6 variant-soft-secondary space-y-4 shadow-xl border border-gray-500">
|
||||
<header class="flex items-center gap-2 border-b border-gray-500 pb-3">
|
||||
<Code size={20} class="text-secondary-500" />
|
||||
<h3 class="h3 font-bold">Request / Response Trace</h3>
|
||||
</header>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 font-mono text-[10px]">
|
||||
<div class="space-y-2">
|
||||
<p class="font-bold opacity-70 tracking-widest uppercase">📤 Sent</p>
|
||||
<div class="bg-black/20 p-4 rounded shadow-inner border border-black/10 overflow-auto">
|
||||
<p class="mb-2 text-secondary-500 font-bold">URL: {trace_details.request.url}</p>
|
||||
<p class="mb-1 text-secondary-500 underline uppercase">Headers:</p>
|
||||
<pre>{JSON.stringify(trace_details.request.headers, null, 2)}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<p class="font-bold opacity-70 tracking-widest uppercase">📥 Received</p>
|
||||
<div class="bg-black/20 p-4 rounded shadow-inner border border-black/10 overflow-auto max-h-[400px] border-l-4"
|
||||
class:border-l-success-500={test_result?.success}
|
||||
class:border-l-error-500={!test_result?.success}>
|
||||
<pre>{JSON.stringify(trace_details.response || trace_details.response_error, null, 2)}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="card p-6 space-y-4 border border-gray-500 shadow-lg transition-all group hover:bg-surface-500/5">
|
||||
<header class="flex items-center gap-2 text-primary-500 border-b border-gray-500 pb-2">
|
||||
<Server size={20}/>
|
||||
<h4 class="h4 font-bold uppercase tracking-widest">Infrastructure</h4>
|
||||
</header>
|
||||
<button class="btn variant-filled-primary p-4 w-full shadow-md transition-all hover:scale-[1.02] flex items-center justify-center gap-2" onclick={test_site_domain_lookup} title="Test guest site domain lookup.">
|
||||
<Globe size={16}/> Site Domain Lookup
|
||||
</button>
|
||||
</div>
|
||||
<div class="card p-6 space-y-4 border border-gray-500 shadow-lg transition-all group hover:bg-surface-500/5">
|
||||
<header class="flex items-center gap-2 text-secondary-500 border-b border-gray-500 pb-2">
|
||||
<Database size={20}/>
|
||||
<h4 class="h4 font-bold uppercase tracking-widest">Core V3</h4>
|
||||
</header>
|
||||
<button class="btn variant-filled-secondary p-4 w-full shadow-md transition-all hover:scale-[1.02] flex items-center justify-center gap-2" onclick={test_load_accounts} title="Test authenticated accounts load.">
|
||||
<Building2 size={16}/> Load Accounts
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Maintenance -->
|
||||
<div class="card p-6 variant-soft-error flex justify-between items-center shadow-lg border border-error-500">
|
||||
<div class="flex items-center gap-4 text-error-700 dark:text-error-400">
|
||||
<Trash2 size={24}/>
|
||||
<div>
|
||||
<p class="font-bold text-lg">Cache Management</p>
|
||||
<p class="text-xs opacity-70 font-mono italic uppercase tracking-tighter">Wipe IndexedDB (ae_core_db)</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn variant-filled-error p-4 font-bold shadow-lg transition-all hover:scale-105" onclick={clear_local_cache}>CLEAR LOCAL IDB</button>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<aside class="space-y-4">
|
||||
<div class="card p-6 h-full bg-surface-100-800-token overflow-hidden flex flex-col sticky top-4 max-h-[calc(100vh-2rem)] shadow-2xl border border-gray-500">
|
||||
<header class="flex justify-between items-center border-b border-gray-500 pb-3 mb-4">
|
||||
<h3 class="h3 font-bold uppercase tracking-widest opacity-70">Audit Result</h3>
|
||||
{#if test_result === 'loading...'} <span class="badge variant-filled-warning animate-pulse">RUNNING</span>
|
||||
{:else if test_result?.success} <span class="badge variant-filled-success font-bold uppercase">Passed</span>
|
||||
{:else if test_result?.error || (test_result && !test_result.success)} <span class="badge variant-filled-error font-bold italic uppercase">Failed</span>
|
||||
{:else} <span class="badge variant-soft-surface opacity-50 italic uppercase">Idle</span> {/if}
|
||||
</header>
|
||||
<div class="flex-1 overflow-auto bg-black/20 rounded p-4 font-mono text-xs shadow-inner border border-black/10">
|
||||
{#if test_result === 'loading...'}
|
||||
<div class="flex flex-col items-center justify-center h-full gap-4 opacity-30"><RefreshCcw size={32} class="animate-spin"/><p class="italic uppercase">Auditing...</p></div>
|
||||
{:else if test_result}
|
||||
<pre class="whitespace-pre-wrap leading-relaxed text-blue-600 dark:text-blue-400">{JSON.stringify(test_result, null, 2)}</pre>
|
||||
{:else}
|
||||
<div class="flex flex-col items-center justify-center h-full opacity-20 py-20 text-center"><Zap size={64} class="mb-4" /><p class="font-bold uppercase tracking-widest">Select test to begin</p></div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
/* No @apply on Skeleton tokens to prevent Tailwind 4 errors */
|
||||
.checkbox { width: 1.25rem; height: 1.25rem; cursor: pointer; }
|
||||
.radio { width: 1.15rem; height: 1.15rem; cursor: pointer; }
|
||||
.btn:disabled { opacity: 0.3; filter: saturate(0); cursor: not-allowed; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user