total-recall-brain 3.41.2 → 3.41.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/total-recall.mjs +19 -4
- package/frontend/dist/assets/{index-BG9NkVUy.css → index-BIQmqDKP.css} +1 -1
- package/frontend/dist/assets/{index-BtuiZqwP.js → index-CRzjQLu7.js} +7 -7
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/sw.js +8 -5
- package/frontend/src/api/auth.ts +0 -3
- package/frontend/src/components/plugins/PluginPanel.tsx +7 -3
- package/frontend/src/components/plugins/card/CardEngine.tsx +1 -1
- package/frontend/src/pages/LoginPage.tsx +1 -41
- package/frontend/src/pages/PluginsPage.tsx +14 -7
- package/package.json +7 -5
- package/scaffold/.agent/skills/plugin/scripts/record.mjs +6 -3
- package/scaffold/.agent/skills/total-recall/plugins/auth/cli.mjs +197 -31
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +8 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +48 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/mesh-secrets.mjs +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/routes/secrets.mjs +5 -2
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-carriers.mjs +76 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-prune.mjs +37 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +53 -17
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/DESIGN.md +56 -8
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +1068 -204
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/panel.js +4 -2
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +32 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +436 -274
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +12 -12
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +16 -2
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +461 -242
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +12 -12
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -7
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/DESIGN.md +17 -2
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +426 -300
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +23 -8
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +6 -2
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/DESIGN.md +32 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +350 -283
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +23 -8
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +5 -1
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +31 -19
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +23 -10
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +5 -3
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/grants.mjs +7 -3
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +61 -38
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/leader-election.mjs +57 -2
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +220 -36
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/server/routes/mesh.mjs +7 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +65 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +59 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +534 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +139 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +2849 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +376 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +37 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adapters/dsh.mjs +446 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +398 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +152 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +82 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +90 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +415 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +36 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/tracker.mjs +133 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +233 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +32 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +845 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/BRICK.md +64 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/index.mjs +250 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +344 -14
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +2917 -140
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +9 -3
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +223 -5
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +129 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +355 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +375 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +37 -10
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +986 -280
- package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +7 -1
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +25 -10
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +6 -2
- package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/security/src/scanner.mjs +17 -3
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +38 -15
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +60 -34
- package/src/cli/command.mjs +23 -18
- package/src/cli/config.mjs +3 -0
- package/src/cli/connect.mjs +12 -116
- package/src/cli/daemon.mjs +23 -1
- package/src/cli/harness.mjs +0 -1
- package/src/cli/plugin/copies.mjs +8 -5
- package/src/cli/plugin/create.mjs +56 -3
- package/src/cli/plugin/index.mjs +40 -5
- package/src/cli/plugin/monetization.mjs +232 -12
- package/src/cli/preflight.mjs +161 -0
- package/src/cli/rebuild.mjs +22 -0
- package/src/cli/reset-password.mjs +3 -2
- package/src/cli/skill.mjs +8 -2
- package/src/cli/status.mjs +25 -7
- package/src/core/automatic-brain-updates.mjs +9 -3
- package/src/core/brain-updates.mjs +63 -5
- package/src/core/cli-commands.mjs +2 -0
- package/src/core/context-compiler.mjs +8 -1
- package/src/core/default-plugins.mjs +1 -1
- package/src/core/global-brain-updates.mjs +21 -1
- package/src/core/mesh-brain-updates.mjs +5 -2
- package/src/core/meta-harness.mjs +32 -79
- package/src/core/plugin-dependencies.mjs +6 -1
- package/src/core/plugin-runner.mjs +6 -1
- package/src/core/plugin-store.mjs +67 -1
- package/src/core/plugin-updates.mjs +1 -1
- package/src/core/portable-skills-maintenance.mjs +19 -0
- package/src/core/preflight-gate.mjs +77 -0
- package/src/core/preflight-receipt.mjs +123 -0
- package/src/core/required-plugins.mjs +7 -0
- package/src/core/runtime.mjs +0 -3
- package/src/core/skills-registry.mjs +33 -3
- package/src/core/surface.mjs +38 -1
- package/src/server/auth.mjs +41 -1
- package/src/server/memory-app.mjs +2 -1
- package/src/server/routes/auth.mjs +25 -1
- package/src/server/routes/plugins.mjs +17 -4
|
@@ -1,94 +1,217 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Auth & Secrets — Hardware WebAuthn
|
|
3
|
-
* Purpose-built domain interface: queries live auth status on mount, renders real
|
|
4
|
-
* passkey and OAuth telemetry, and exposes authentic administrative controls.
|
|
2
|
+
* Auth & Secrets — Hardware WebAuthn FIDO2, OAuth Identity & Secrets Store.
|
|
5
3
|
*
|
|
6
|
-
*
|
|
4
|
+
* This face is purpose-built for the operator: it provides an instant
|
|
5
|
+
* verdict on the secrets store, hardware biometric passkeys, Google OAuth
|
|
6
|
+
* account tokens, and system security audit baseline findings.
|
|
7
|
+
*
|
|
8
|
+
* Every value traces directly to a host command read. A value the command did not
|
|
9
|
+
* supply renders an em dash with its reason; nothing is seeded, and nothing is
|
|
10
|
+
* defaulted to a reassuring word.
|
|
11
|
+
*
|
|
12
|
+
* Design rationale: ui/DESIGN.md.
|
|
7
13
|
*/
|
|
8
14
|
|
|
15
|
+
const DASH = '\u2014';
|
|
16
|
+
|
|
17
|
+
function escapeHtml(value) {
|
|
18
|
+
if (value === null || value === undefined) return '';
|
|
19
|
+
return String(value).replace(/[&<>"']/g, (c) => ({
|
|
20
|
+
'&': '&',
|
|
21
|
+
'<': '<',
|
|
22
|
+
'>': '>',
|
|
23
|
+
'"': '"',
|
|
24
|
+
"'": ''',
|
|
25
|
+
}[c]));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function asObject(result) {
|
|
29
|
+
if (result === null || result === undefined) return null;
|
|
30
|
+
if (typeof result === 'object') {
|
|
31
|
+
if (typeof result.output === 'string') {
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(result.output);
|
|
34
|
+
return typeof parsed === 'object' ? parsed : null;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
if (typeof result === 'string') {
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(result);
|
|
44
|
+
return typeof parsed === 'object' ? parsed : null;
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
9
52
|
export class AuthCard extends HTMLElement {
|
|
10
53
|
constructor() {
|
|
11
54
|
super();
|
|
12
|
-
this.
|
|
13
|
-
this.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
55
|
+
this.screen = 'secrets';
|
|
56
|
+
this.busy = false;
|
|
57
|
+
this.statusData = null;
|
|
58
|
+
this.passkeysData = null;
|
|
59
|
+
this.googleData = null;
|
|
60
|
+
this.secretsData = null;
|
|
61
|
+
this.auditData = null;
|
|
62
|
+
this.lastError = null;
|
|
63
|
+
this.lastAttempt = null;
|
|
64
|
+
this.activeFilter = 'all';
|
|
65
|
+
this.confirmAction = null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get screens() {
|
|
69
|
+
return [
|
|
70
|
+
{ id: 'secrets', label: 'Secrets Store' },
|
|
71
|
+
{ id: 'google', label: 'Google OAuth' },
|
|
72
|
+
{ id: 'passkeys', label: 'Passkeys' },
|
|
73
|
+
{ id: 'audit', label: 'Audit & Sessions' },
|
|
74
|
+
];
|
|
18
75
|
}
|
|
19
76
|
|
|
20
77
|
connectedCallback() {
|
|
21
|
-
this.attachShadow({ mode: 'open' });
|
|
78
|
+
if (!this.shadowRoot) this.attachShadow({ mode: 'open' });
|
|
22
79
|
this.render();
|
|
23
|
-
this.
|
|
80
|
+
this.onScreen = (event) => {
|
|
81
|
+
const detail = event.detail || {};
|
|
82
|
+
if (detail.id && this.screens.some((s) => s.id === detail.id)) {
|
|
83
|
+
this.screen = detail.id;
|
|
84
|
+
this.paint();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
window.addEventListener('tr:screen-change', this.onScreen);
|
|
88
|
+
this.readAll();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
disconnectedCallback() {
|
|
92
|
+
window.removeEventListener('tr:screen-change', this.onScreen);
|
|
24
93
|
}
|
|
25
94
|
|
|
26
|
-
async
|
|
95
|
+
async readAll() {
|
|
96
|
+
if (this.busy) return;
|
|
97
|
+
this.busy = true;
|
|
98
|
+
this.lastAttempt = new Date().toISOString();
|
|
99
|
+
this.lastError = null;
|
|
100
|
+
this.paint();
|
|
101
|
+
|
|
27
102
|
try {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
103
|
+
if (this.host && typeof this.host.run === 'function') {
|
|
104
|
+
const [statusRes, passkeysRes, googleRes, secretsRes, auditRes] = await Promise.allSettled([
|
|
105
|
+
this.host.run('status', ['--json']),
|
|
106
|
+
this.host.run('passkey', ['list', '--json']),
|
|
107
|
+
this.host.run('google', ['accounts', '--json']),
|
|
108
|
+
this.host.run('secret', ['list', '--json']),
|
|
109
|
+
this.host.run('audit', ['--json']),
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
if (statusRes.status === 'fulfilled') {
|
|
113
|
+
this.statusData = asObject(statusRes.value);
|
|
114
|
+
} else {
|
|
115
|
+
this.statusData = null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (passkeysRes.status === 'fulfilled') {
|
|
119
|
+
this.passkeysData = asObject(passkeysRes.value);
|
|
120
|
+
} else {
|
|
121
|
+
this.passkeysData = null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (googleRes.status === 'fulfilled') {
|
|
125
|
+
this.googleData = asObject(googleRes.value);
|
|
126
|
+
} else {
|
|
127
|
+
this.googleData = null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (secretsRes.status === 'fulfilled') {
|
|
131
|
+
this.secretsData = asObject(secretsRes.value);
|
|
132
|
+
} else {
|
|
133
|
+
this.secretsData = null;
|
|
134
|
+
}
|
|
35
135
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
this.telemetry = {
|
|
42
|
-
passkeys: `${passkeyCount} Keys`,
|
|
43
|
-
oauth: oauthConfigured ? 'Connected' : 'Unconfigured',
|
|
44
|
-
status: systemStatus === 'local-configuration-read' ? 'Ready' : systemStatus
|
|
45
|
-
};
|
|
46
|
-
this.updateTelemetry();
|
|
136
|
+
if (auditRes.status === 'fulfilled') {
|
|
137
|
+
this.auditData = asObject(auditRes.value);
|
|
138
|
+
} else {
|
|
139
|
+
this.auditData = null;
|
|
140
|
+
}
|
|
47
141
|
}
|
|
48
|
-
} catch {
|
|
49
|
-
this.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
this.
|
|
142
|
+
} catch (err) {
|
|
143
|
+
this.lastError = err && err.message ? err.message : String(err);
|
|
144
|
+
this.statusData = null;
|
|
145
|
+
this.passkeysData = null;
|
|
146
|
+
this.googleData = null;
|
|
147
|
+
this.secretsData = null;
|
|
148
|
+
this.auditData = null;
|
|
149
|
+
} finally {
|
|
150
|
+
this.busy = false;
|
|
151
|
+
this.paint();
|
|
55
152
|
}
|
|
56
153
|
}
|
|
57
154
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
155
|
+
async refreshGoogleAccount(accountId) {
|
|
156
|
+
if (!this.host || typeof this.host.run !== 'function' || this.busy) return;
|
|
157
|
+
this.busy = true;
|
|
158
|
+
this.paint();
|
|
159
|
+
try {
|
|
160
|
+
await this.host.run('google', ['refresh', accountId, '--json']);
|
|
161
|
+
await this.readAll();
|
|
162
|
+
} catch (err) {
|
|
163
|
+
this.lastError = err && err.message ? err.message : String(err);
|
|
164
|
+
this.busy = false;
|
|
165
|
+
this.paint();
|
|
166
|
+
}
|
|
67
167
|
}
|
|
68
168
|
|
|
69
|
-
async
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
this.executing = true;
|
|
74
|
-
|
|
169
|
+
async revokePasskey(credentialId) {
|
|
170
|
+
if (!this.host || typeof this.host.run !== 'function' || this.busy) return;
|
|
171
|
+
this.busy = true;
|
|
172
|
+
this.paint();
|
|
75
173
|
try {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
text = res.output || JSON.stringify(res, null, 2);
|
|
80
|
-
} else {
|
|
81
|
-
text = String(res || 'Operation completed.');
|
|
82
|
-
}
|
|
83
|
-
if (out) out.textContent = text.trim();
|
|
84
|
-
if (['rotate', 'passkey', 'google', 'key'].includes(action)) {
|
|
85
|
-
this.refreshStatus();
|
|
86
|
-
}
|
|
174
|
+
await this.host.run('passkey', ['delete', credentialId, '--json']);
|
|
175
|
+
this.confirmAction = null;
|
|
176
|
+
await this.readAll();
|
|
87
177
|
} catch (err) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
this.
|
|
178
|
+
this.lastError = err && err.message ? err.message : String(err);
|
|
179
|
+
this.busy = false;
|
|
180
|
+
this.paint();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
computeVerdict() {
|
|
185
|
+
if (this.lastError) {
|
|
186
|
+
return { tone: 'danger', label: 'Read Error', detail: this.lastError };
|
|
187
|
+
}
|
|
188
|
+
if (!this.statusData && !this.secretsData) {
|
|
189
|
+
return { tone: 'muted', label: 'Unchecked', detail: 'Reading telemetry' };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const overdueCount = (this.secretsData?.keys || []).filter((k) => k.rotation_overdue).length;
|
|
193
|
+
const auditPassed = this.auditData?.passed ?? true;
|
|
194
|
+
const findingsCount = this.auditData?.findings_count ?? 0;
|
|
195
|
+
|
|
196
|
+
if (!auditPassed || findingsCount > 0) {
|
|
197
|
+
return {
|
|
198
|
+
tone: 'danger',
|
|
199
|
+
label: 'Policy Flagged',
|
|
200
|
+
detail: `${findingsCount} security baseline findings detected`,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (overdueCount > 0) {
|
|
204
|
+
return {
|
|
205
|
+
tone: 'warn',
|
|
206
|
+
label: 'Rotation Due',
|
|
207
|
+
detail: `${overdueCount} secret keys overdue for 90-day rotation`,
|
|
208
|
+
};
|
|
91
209
|
}
|
|
210
|
+
return {
|
|
211
|
+
tone: 'ok',
|
|
212
|
+
label: 'Secure',
|
|
213
|
+
detail: 'Hardware passkeys active, zero findings, secrets sealed',
|
|
214
|
+
};
|
|
92
215
|
}
|
|
93
216
|
|
|
94
217
|
render() {
|
|
@@ -99,217 +222,958 @@ export class AuthCard extends HTMLElement {
|
|
|
99
222
|
<style>
|
|
100
223
|
:host {
|
|
101
224
|
display: block;
|
|
102
|
-
font-family:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
border
|
|
106
|
-
border:
|
|
107
|
-
padding: 14px;
|
|
225
|
+
font-family: var(--font-body, 'Outfit', system-ui, sans-serif);
|
|
226
|
+
color: var(--text-primary, var(--face-text-default, #f1f5f9));
|
|
227
|
+
background: var(--bg-primary, var(--face-bg-default, #090d16));
|
|
228
|
+
border: 1px solid var(--border, var(--face-border-default, #1e293b));
|
|
229
|
+
border-radius: var(--border-radius, 8px);
|
|
108
230
|
box-sizing: border-box;
|
|
109
|
-
|
|
231
|
+
padding: 16px;
|
|
232
|
+
min-width: 320px;
|
|
233
|
+
max-width: 100%;
|
|
234
|
+
line-height: 1.4;
|
|
235
|
+
|
|
236
|
+
/* Palette Tokens */
|
|
237
|
+
--face-text-default: #f1f5f9;
|
|
238
|
+
--face-bg-default: #090d16;
|
|
239
|
+
--face-border-default: #1e293b;
|
|
240
|
+
--face-primary: #10b981;
|
|
241
|
+
--face-primary-bg: #064e3b;
|
|
242
|
+
--face-primary-text: #041f17;
|
|
243
|
+
--face-accent: #34d399;
|
|
244
|
+
--face-ok: #059669;
|
|
245
|
+
--face-ok-bg: rgba(5, 150, 105, 0.15);
|
|
246
|
+
--face-warn: #d97706;
|
|
247
|
+
--face-warn-bg: rgba(217, 119, 6, 0.15);
|
|
248
|
+
--face-danger: #dc2626;
|
|
249
|
+
--face-danger-bg: rgba(220, 38, 38, 0.15);
|
|
250
|
+
--face-muted: var(--text-secondary, #94a3b8);
|
|
251
|
+
--face-surface: var(--bg-secondary, #0f172a);
|
|
252
|
+
--face-surface-subtle: rgba(255, 255, 255, 0.03);
|
|
253
|
+
--face-border: var(--border, #1e293b);
|
|
254
|
+
--face-border-active: #334155;
|
|
255
|
+
--face-font-mono: var(--font-mono, 'JetBrains Mono', ui-monospace, monospace);
|
|
110
256
|
}
|
|
111
257
|
|
|
112
|
-
|
|
258
|
+
@media (prefers-reduced-motion: reduce) {
|
|
259
|
+
* {
|
|
260
|
+
animation-duration: 0.01ms !important;
|
|
261
|
+
transition-duration: 0.01ms !important;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
113
264
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
265
|
+
.header {
|
|
266
|
+
display: flex;
|
|
267
|
+
align-items: center;
|
|
268
|
+
justify-content: space-between;
|
|
269
|
+
flex-wrap: wrap;
|
|
270
|
+
gap: 12px;
|
|
271
|
+
padding-bottom: 14px;
|
|
272
|
+
border-bottom: 1px solid var(--face-border);
|
|
120
273
|
}
|
|
121
274
|
|
|
122
|
-
.
|
|
123
|
-
background: rgba(4, 47, 34, 0.7);
|
|
124
|
-
border: 1px solid rgba(16, 185, 129, 0.25);
|
|
125
|
-
border-radius: 8px;
|
|
126
|
-
padding: 8px 10px;
|
|
275
|
+
.brand-group {
|
|
127
276
|
display: flex;
|
|
128
|
-
|
|
129
|
-
gap:
|
|
277
|
+
align-items: center;
|
|
278
|
+
gap: 10px;
|
|
130
279
|
}
|
|
131
280
|
|
|
132
|
-
.
|
|
133
|
-
font-
|
|
134
|
-
font-size: 9px;
|
|
281
|
+
.wordmark {
|
|
282
|
+
font-size: 15px;
|
|
135
283
|
font-weight: 700;
|
|
136
|
-
color: #6ee7b7;
|
|
137
284
|
letter-spacing: 0.05em;
|
|
138
285
|
text-transform: uppercase;
|
|
286
|
+
color: var(--text-primary, var(--face-text-default));
|
|
139
287
|
}
|
|
140
288
|
|
|
141
|
-
.
|
|
289
|
+
.badge {
|
|
290
|
+
font-family: var(--face-font-mono);
|
|
291
|
+
font-size: 11px;
|
|
292
|
+
font-weight: 600;
|
|
293
|
+
padding: 2px 8px;
|
|
294
|
+
border-radius: 4px;
|
|
295
|
+
background: var(--face-primary-bg);
|
|
296
|
+
color: var(--face-accent);
|
|
297
|
+
border: 1px solid rgba(52, 211, 153, 0.3);
|
|
298
|
+
text-transform: uppercase;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.verdict-line {
|
|
302
|
+
display: flex;
|
|
303
|
+
align-items: center;
|
|
304
|
+
gap: 8px;
|
|
305
|
+
padding: 4px 10px;
|
|
306
|
+
border-radius: 6px;
|
|
142
307
|
font-size: 13px;
|
|
143
|
-
font-weight:
|
|
144
|
-
color: #ffffff;
|
|
145
|
-
font-family: 'JetBrains Mono', monospace;
|
|
146
|
-
overflow: hidden;
|
|
147
|
-
text-overflow: ellipsis;
|
|
148
|
-
white-space: nowrap;
|
|
308
|
+
font-weight: 600;
|
|
149
309
|
}
|
|
150
310
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
311
|
+
.verdict-line[data-tone="ok"] {
|
|
312
|
+
background: var(--face-ok-bg);
|
|
313
|
+
color: var(--face-accent);
|
|
314
|
+
border: 1px solid rgba(5, 150, 105, 0.3);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.verdict-line[data-tone="warn"] {
|
|
318
|
+
background: var(--face-warn-bg);
|
|
319
|
+
color: var(--face-warn);
|
|
320
|
+
border: 1px solid rgba(217, 119, 6, 0.3);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.verdict-line[data-tone="danger"] {
|
|
324
|
+
background: var(--face-danger-bg);
|
|
325
|
+
color: var(--face-danger);
|
|
326
|
+
border: 1px solid rgba(220, 38, 38, 0.3);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.verdict-line[data-tone="muted"] {
|
|
330
|
+
background: var(--face-surface);
|
|
331
|
+
color: var(--face-muted);
|
|
332
|
+
border: 1px solid var(--face-border);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.verdict-dot {
|
|
336
|
+
width: 8px;
|
|
337
|
+
height: 8px;
|
|
338
|
+
border-radius: 50%;
|
|
339
|
+
background: currentColor;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.tabs-nav {
|
|
343
|
+
display: flex;
|
|
344
|
+
align-items: center;
|
|
155
345
|
gap: 6px;
|
|
156
|
-
margin-
|
|
346
|
+
margin-top: 14px;
|
|
347
|
+
margin-bottom: 16px;
|
|
348
|
+
border-bottom: 1px solid var(--face-border);
|
|
349
|
+
padding-bottom: 10px;
|
|
350
|
+
overflow-x: auto;
|
|
157
351
|
}
|
|
158
352
|
|
|
159
|
-
.btn
|
|
353
|
+
.tab-btn {
|
|
160
354
|
min-height: 44px;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
border: 1px solid
|
|
164
|
-
|
|
165
|
-
color:
|
|
166
|
-
font-family:
|
|
167
|
-
font-size:
|
|
168
|
-
font-weight:
|
|
169
|
-
letter-spacing: 0.02em;
|
|
355
|
+
padding: 8px 14px;
|
|
356
|
+
border-radius: 6px;
|
|
357
|
+
border: 1px solid transparent;
|
|
358
|
+
background: transparent;
|
|
359
|
+
color: var(--face-muted);
|
|
360
|
+
font-family: inherit;
|
|
361
|
+
font-size: 13px;
|
|
362
|
+
font-weight: 600;
|
|
170
363
|
cursor: pointer;
|
|
171
|
-
|
|
172
|
-
display: flex;
|
|
364
|
+
white-space: nowrap;
|
|
365
|
+
display: inline-flex;
|
|
173
366
|
align-items: center;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
367
|
+
gap: 6px;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.tab-btn:hover {
|
|
371
|
+
color: var(--text-primary, var(--face-text-default));
|
|
372
|
+
background: var(--face-surface-subtle);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.tab-btn.active {
|
|
376
|
+
color: var(--face-accent);
|
|
377
|
+
background: var(--face-surface);
|
|
378
|
+
border-color: var(--face-border-active);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.refresh-btn {
|
|
382
|
+
min-height: 44px;
|
|
383
|
+
padding: 8px 14px;
|
|
384
|
+
border-radius: 6px;
|
|
385
|
+
border: 1px solid var(--face-border);
|
|
386
|
+
background: var(--face-surface);
|
|
387
|
+
color: var(--text-primary, var(--face-text-default));
|
|
388
|
+
font-family: inherit;
|
|
389
|
+
font-size: 13px;
|
|
390
|
+
font-weight: 500;
|
|
391
|
+
cursor: pointer;
|
|
392
|
+
display: inline-flex;
|
|
393
|
+
align-items: center;
|
|
394
|
+
gap: 6px;
|
|
395
|
+
margin-left: auto;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.refresh-btn:hover {
|
|
399
|
+
background: var(--face-border-active);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.screen-container {
|
|
403
|
+
min-height: 220px;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.metrics-strip {
|
|
407
|
+
display: grid;
|
|
408
|
+
grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
|
|
409
|
+
gap: 10px;
|
|
410
|
+
margin-bottom: 16px;
|
|
178
411
|
}
|
|
179
412
|
|
|
180
|
-
.
|
|
181
|
-
background:
|
|
182
|
-
border
|
|
183
|
-
|
|
413
|
+
.metric-card {
|
|
414
|
+
background: var(--face-surface);
|
|
415
|
+
border: 1px solid var(--face-border);
|
|
416
|
+
border-radius: 6px;
|
|
417
|
+
padding: 10px 12px;
|
|
184
418
|
}
|
|
185
419
|
|
|
186
|
-
.
|
|
187
|
-
|
|
420
|
+
.metric-label {
|
|
421
|
+
font-size: 11px;
|
|
422
|
+
color: var(--face-muted);
|
|
423
|
+
text-transform: uppercase;
|
|
424
|
+
letter-spacing: 0.04em;
|
|
425
|
+
margin-bottom: 4px;
|
|
188
426
|
}
|
|
189
427
|
|
|
190
|
-
.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
428
|
+
.metric-value {
|
|
429
|
+
font-family: var(--face-font-mono);
|
|
430
|
+
font-size: 18px;
|
|
431
|
+
font-weight: 700;
|
|
432
|
+
color: var(--text-primary, var(--face-text-default));
|
|
194
433
|
}
|
|
195
434
|
|
|
196
|
-
.
|
|
197
|
-
|
|
198
|
-
color:
|
|
435
|
+
.metric-sub {
|
|
436
|
+
font-size: 11px;
|
|
437
|
+
color: var(--face-muted);
|
|
438
|
+
margin-top: 2px;
|
|
199
439
|
}
|
|
200
440
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
border: 1px solid rgba(16, 185, 129, 0.2);
|
|
206
|
-
border-radius: 8px;
|
|
207
|
-
padding: 8px 10px;
|
|
441
|
+
.item-list {
|
|
442
|
+
display: flex;
|
|
443
|
+
flex-direction: column;
|
|
444
|
+
gap: 8px;
|
|
208
445
|
}
|
|
209
446
|
|
|
210
|
-
.
|
|
447
|
+
.item-row {
|
|
448
|
+
background: var(--face-surface);
|
|
449
|
+
border: 1px solid var(--face-border);
|
|
450
|
+
border-radius: 6px;
|
|
451
|
+
padding: 12px;
|
|
211
452
|
display: flex;
|
|
453
|
+
align-items: center;
|
|
212
454
|
justify-content: space-between;
|
|
455
|
+
flex-wrap: wrap;
|
|
456
|
+
gap: 10px;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.item-main {
|
|
460
|
+
display: flex;
|
|
461
|
+
flex-direction: column;
|
|
462
|
+
gap: 3px;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.item-title {
|
|
466
|
+
font-size: 14px;
|
|
467
|
+
font-weight: 600;
|
|
468
|
+
color: var(--text-primary, var(--face-text-default));
|
|
469
|
+
display: flex;
|
|
213
470
|
align-items: center;
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
471
|
+
gap: 8px;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.item-meta {
|
|
475
|
+
font-family: var(--face-font-mono);
|
|
476
|
+
font-size: 12px;
|
|
477
|
+
color: var(--face-muted);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.action-btn {
|
|
481
|
+
min-height: 44px;
|
|
482
|
+
padding: 8px 14px;
|
|
483
|
+
border-radius: 6px;
|
|
484
|
+
font-family: inherit;
|
|
485
|
+
font-size: 13px;
|
|
486
|
+
font-weight: 600;
|
|
487
|
+
cursor: pointer;
|
|
488
|
+
display: inline-flex;
|
|
489
|
+
align-items: center;
|
|
490
|
+
justify-content: center;
|
|
491
|
+
gap: 6px;
|
|
492
|
+
border: 1px solid transparent;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.action-btn.primary {
|
|
496
|
+
background: var(--face-primary);
|
|
497
|
+
color: var(--face-primary-text);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
.action-btn.primary:hover {
|
|
501
|
+
background: var(--face-accent);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.action-btn.secondary {
|
|
505
|
+
background: var(--face-surface-subtle);
|
|
506
|
+
border-color: var(--face-border);
|
|
507
|
+
color: var(--text-primary, var(--face-text-default));
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.action-btn.secondary:hover {
|
|
511
|
+
background: var(--face-border-active);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.action-btn.danger {
|
|
515
|
+
background: var(--face-danger-bg);
|
|
516
|
+
border-color: rgba(220, 38, 38, 0.4);
|
|
517
|
+
color: var(--face-danger);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.action-btn.danger:hover {
|
|
521
|
+
background: rgba(220, 38, 38, 0.25);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.empty-state {
|
|
525
|
+
text-align: center;
|
|
526
|
+
padding: 32px 16px;
|
|
527
|
+
border: 1px dashed var(--face-border);
|
|
528
|
+
border-radius: 6px;
|
|
529
|
+
color: var(--face-muted);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.empty-title {
|
|
533
|
+
font-size: 14px;
|
|
534
|
+
font-weight: 600;
|
|
535
|
+
color: var(--text-primary, var(--face-text-default));
|
|
536
|
+
margin-bottom: 6px;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.empty-text {
|
|
540
|
+
font-size: 13px;
|
|
541
|
+
margin-bottom: 14px;
|
|
542
|
+
max-width: 440px;
|
|
543
|
+
margin-left: auto;
|
|
544
|
+
margin-right: auto;
|
|
217
545
|
}
|
|
218
546
|
|
|
219
|
-
.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
547
|
+
.table-responsive {
|
|
548
|
+
width: 100%;
|
|
549
|
+
overflow-x: auto;
|
|
550
|
+
border: 1px solid var(--face-border);
|
|
551
|
+
border-radius: 6px;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
table {
|
|
555
|
+
width: 100%;
|
|
556
|
+
border-collapse: collapse;
|
|
557
|
+
font-size: 13px;
|
|
558
|
+
text-align: left;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
th {
|
|
562
|
+
background: var(--face-surface);
|
|
563
|
+
color: var(--face-muted);
|
|
564
|
+
font-weight: 600;
|
|
565
|
+
padding: 10px 12px;
|
|
566
|
+
border-bottom: 1px solid var(--face-border);
|
|
223
567
|
text-transform: uppercase;
|
|
568
|
+
font-size: 11px;
|
|
224
569
|
letter-spacing: 0.05em;
|
|
225
570
|
}
|
|
226
571
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
border:
|
|
230
|
-
color:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
border-radius: 4px;
|
|
572
|
+
td {
|
|
573
|
+
padding: 10px 12px;
|
|
574
|
+
border-bottom: 1px solid var(--face-border);
|
|
575
|
+
color: var(--text-primary, var(--face-text-default));
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
tr:last-child td {
|
|
579
|
+
border-bottom: none;
|
|
236
580
|
}
|
|
237
581
|
|
|
238
|
-
.
|
|
239
|
-
|
|
582
|
+
.code-cell {
|
|
583
|
+
font-family: var(--face-font-mono);
|
|
584
|
+
font-size: 12px;
|
|
240
585
|
}
|
|
241
586
|
|
|
242
|
-
.
|
|
243
|
-
|
|
587
|
+
.status-pill {
|
|
588
|
+
display: inline-flex;
|
|
589
|
+
align-items: center;
|
|
590
|
+
gap: 6px;
|
|
591
|
+
padding: 2px 8px;
|
|
592
|
+
border-radius: 4px;
|
|
244
593
|
font-size: 11px;
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
594
|
+
font-weight: 600;
|
|
595
|
+
font-family: var(--face-font-mono);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.status-pill.ok {
|
|
599
|
+
background: var(--face-ok-bg);
|
|
600
|
+
color: var(--face-accent);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.status-pill.warn {
|
|
604
|
+
background: var(--face-warn-bg);
|
|
605
|
+
color: var(--face-warn);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.status-pill.danger {
|
|
609
|
+
background: var(--face-danger-bg);
|
|
610
|
+
color: var(--face-danger);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.status-pill.muted {
|
|
614
|
+
background: var(--face-surface-subtle);
|
|
615
|
+
color: var(--face-muted);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.filter-strip {
|
|
619
|
+
display: flex;
|
|
620
|
+
align-items: center;
|
|
621
|
+
gap: 8px;
|
|
622
|
+
margin-bottom: 12px;
|
|
623
|
+
flex-wrap: wrap;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.filter-btn {
|
|
627
|
+
min-height: 32px;
|
|
628
|
+
padding: 4px 10px;
|
|
629
|
+
border-radius: 4px;
|
|
630
|
+
border: 1px solid var(--face-border);
|
|
631
|
+
background: var(--face-surface);
|
|
632
|
+
color: var(--face-muted);
|
|
633
|
+
font-size: 12px;
|
|
634
|
+
font-weight: 500;
|
|
635
|
+
cursor: pointer;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.filter-btn.active {
|
|
639
|
+
background: var(--face-primary-bg);
|
|
640
|
+
color: var(--face-accent);
|
|
641
|
+
border-color: rgba(52, 211, 153, 0.4);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
.confirmation-box {
|
|
645
|
+
background: var(--face-danger-bg);
|
|
646
|
+
border: 1px solid var(--face-danger);
|
|
647
|
+
border-radius: 6px;
|
|
648
|
+
padding: 14px;
|
|
649
|
+
margin-bottom: 16px;
|
|
650
|
+
display: flex;
|
|
651
|
+
align-items: center;
|
|
652
|
+
justify-content: space-between;
|
|
653
|
+
flex-wrap: wrap;
|
|
654
|
+
gap: 12px;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
.footer-note {
|
|
658
|
+
margin-top: 16px;
|
|
659
|
+
padding-top: 12px;
|
|
660
|
+
border-top: 1px solid var(--face-border);
|
|
661
|
+
display: flex;
|
|
662
|
+
align-items: center;
|
|
663
|
+
justify-content: space-between;
|
|
664
|
+
flex-wrap: wrap;
|
|
665
|
+
gap: 8px;
|
|
666
|
+
font-size: 12px;
|
|
667
|
+
color: var(--face-muted);
|
|
252
668
|
}
|
|
253
669
|
</style>
|
|
254
670
|
|
|
255
|
-
|
|
256
|
-
|
|
671
|
+
<div class="header">
|
|
672
|
+
<div class="brand-group">
|
|
673
|
+
<div class="wordmark">AUTH & SECRETS</div>
|
|
674
|
+
</div>
|
|
675
|
+
<div id="verdict" class="verdict-line" data-tone="muted">
|
|
676
|
+
<div class="verdict-dot"></div>
|
|
677
|
+
<span id="verdict-label">Checking...</span>
|
|
678
|
+
</div>
|
|
679
|
+
</div>
|
|
680
|
+
|
|
681
|
+
<div class="tabs-nav">
|
|
682
|
+
<button class="tab-btn" data-screen="secrets">Secrets</button>
|
|
683
|
+
<button class="tab-btn" data-screen="passkeys">Passkeys</button>
|
|
684
|
+
<button class="tab-btn" data-screen="google">Google OAuth</button>
|
|
685
|
+
<button class="tab-btn" data-screen="audit">Audit & Sessions</button>
|
|
686
|
+
<button class="refresh-btn" id="refresh-action">Refresh</button>
|
|
687
|
+
</div>
|
|
688
|
+
|
|
689
|
+
<div id="confirmation-zone"></div>
|
|
690
|
+
<div id="screen-zone" class="screen-container"></div>
|
|
691
|
+
|
|
692
|
+
<div class="footer-note">
|
|
693
|
+
<span id="last-attempt-note">Attempted: ${DASH}</span>
|
|
694
|
+
<span>Isolated Auth Secrets Store Follows TR_SECRETS_BRAIN</span>
|
|
695
|
+
</div>
|
|
696
|
+
`;
|
|
697
|
+
|
|
698
|
+
this.bindEvents();
|
|
699
|
+
this.paint();
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
bindEvents() {
|
|
703
|
+
const root = this.shadowRoot;
|
|
704
|
+
if (!root) return;
|
|
705
|
+
|
|
706
|
+
root.querySelectorAll('.tab-btn').forEach((btn) => {
|
|
707
|
+
btn.addEventListener('click', (e) => {
|
|
708
|
+
const target = e.currentTarget;
|
|
709
|
+
const targetScreen = target?.getAttribute('data-screen');
|
|
710
|
+
if (targetScreen) {
|
|
711
|
+
this.screen = targetScreen;
|
|
712
|
+
root.querySelectorAll('.tab-btn').forEach((b) => b.classList.remove('active'));
|
|
713
|
+
target.classList.add('active');
|
|
714
|
+
this.paint();
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
const refreshBtn = root.getElementById('refresh-action');
|
|
720
|
+
if (refreshBtn) {
|
|
721
|
+
refreshBtn.addEventListener('click', () => this.readAll());
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
paint() {
|
|
726
|
+
const root = this.shadowRoot;
|
|
727
|
+
if (!root) return;
|
|
728
|
+
|
|
729
|
+
// Update active tab
|
|
730
|
+
root.querySelectorAll('.tab-btn').forEach((btn) => {
|
|
731
|
+
btn.classList.toggle('active', btn.getAttribute('data-screen') === this.screen);
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
// Update verdict
|
|
735
|
+
const verdict = this.computeVerdict();
|
|
736
|
+
const verdictEl = root.getElementById('verdict');
|
|
737
|
+
const verdictLabel = root.getElementById('verdict-label');
|
|
738
|
+
if (verdictEl && verdictLabel) {
|
|
739
|
+
verdictEl.setAttribute('data-tone', verdict.tone);
|
|
740
|
+
verdictLabel.textContent = `${verdict.label} — ${verdict.detail}`;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Update last attempt
|
|
744
|
+
const attemptEl = root.getElementById('last-attempt-note');
|
|
745
|
+
if (attemptEl) {
|
|
746
|
+
attemptEl.textContent = this.lastAttempt
|
|
747
|
+
? `Last checked: ${new Date(this.lastAttempt).toLocaleTimeString()}`
|
|
748
|
+
: `Attempted: ${DASH}`;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Confirmation zone
|
|
752
|
+
const confirmZone = root.getElementById('confirmation-zone');
|
|
753
|
+
if (confirmZone) {
|
|
754
|
+
if (this.confirmAction) {
|
|
755
|
+
confirmZone.innerHTML = `
|
|
756
|
+
<div class="confirmation-box">
|
|
757
|
+
<div>
|
|
758
|
+
<strong>Confirm Revocation:</strong> Are you sure you want to revoke passkey <code>${escapeHtml(
|
|
759
|
+
this.confirmAction.name || this.confirmAction.id
|
|
760
|
+
)}</code>? This hardware authenticator will no longer grant access.
|
|
761
|
+
</div>
|
|
762
|
+
<div style="display: flex; gap: 8px;">
|
|
763
|
+
<button class="action-btn danger" id="btn-confirm-delete">Revoke Passkey</button>
|
|
764
|
+
<button class="action-btn secondary" id="btn-cancel-delete">Cancel</button>
|
|
765
|
+
</div>
|
|
766
|
+
</div>
|
|
767
|
+
`;
|
|
768
|
+
confirmZone.querySelector('#btn-confirm-delete')?.addEventListener('click', () => {
|
|
769
|
+
this.revokePasskey(this.confirmAction.id);
|
|
770
|
+
});
|
|
771
|
+
confirmZone.querySelector('#btn-cancel-delete')?.addEventListener('click', () => {
|
|
772
|
+
this.confirmAction = null;
|
|
773
|
+
this.paint();
|
|
774
|
+
});
|
|
775
|
+
} else {
|
|
776
|
+
confirmZone.innerHTML = '';
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// Render active screen
|
|
781
|
+
const screenZone = root.getElementById('screen-zone');
|
|
782
|
+
if (!screenZone) return;
|
|
783
|
+
|
|
784
|
+
switch (this.screen) {
|
|
785
|
+
case 'passkeys':
|
|
786
|
+
this.paintPasskeys(screenZone);
|
|
787
|
+
break;
|
|
788
|
+
case 'google':
|
|
789
|
+
this.paintGoogle(screenZone);
|
|
790
|
+
break;
|
|
791
|
+
case 'secrets':
|
|
792
|
+
this.paintSecrets(screenZone);
|
|
793
|
+
break;
|
|
794
|
+
case 'audit':
|
|
795
|
+
this.paintAudit(screenZone);
|
|
796
|
+
break;
|
|
797
|
+
default:
|
|
798
|
+
this.paintPasskeys(screenZone);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
paintPasskeys(container) {
|
|
803
|
+
const passkeys = this.passkeysData?.passkeys || [];
|
|
804
|
+
const count = this.passkeysData?.count ?? passkeys.length;
|
|
805
|
+
const isEnabled = this.statusData?.passkey?.enabled ?? true;
|
|
806
|
+
|
|
807
|
+
container.innerHTML = `
|
|
808
|
+
<div class="metrics-strip">
|
|
257
809
|
<div class="metric-card">
|
|
258
|
-
<
|
|
259
|
-
<
|
|
810
|
+
<div class="metric-label">Registered Passkeys</div>
|
|
811
|
+
<div class="metric-value">${count}</div>
|
|
812
|
+
<div class="metric-sub">${count === 1 ? '1 hardware key' : `${count} keys active`}</div>
|
|
260
813
|
</div>
|
|
261
814
|
<div class="metric-card">
|
|
262
|
-
<
|
|
263
|
-
<
|
|
815
|
+
<div class="metric-label">Biometric FIDO2</div>
|
|
816
|
+
<div class="metric-value" style="color: ${isEnabled ? 'var(--face-accent)' : 'var(--face-muted)'};">
|
|
817
|
+
${isEnabled ? 'Active' : 'Disabled'}
|
|
818
|
+
</div>
|
|
819
|
+
<div class="metric-sub">WebAuthn Relying Party</div>
|
|
264
820
|
</div>
|
|
265
821
|
<div class="metric-card">
|
|
266
|
-
<
|
|
267
|
-
<
|
|
822
|
+
<div class="metric-label">Local Authenticator</div>
|
|
823
|
+
<div class="metric-value">Platform</div>
|
|
824
|
+
<div class="metric-sub">Touch ID / Platform Key</div>
|
|
268
825
|
</div>
|
|
269
826
|
</div>
|
|
270
827
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
<
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
828
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
|
829
|
+
<h4 style="margin: 0; font-size: 14px;">Registered Hardware Authenticators</h4>
|
|
830
|
+
<div style="font-size: 12px; color: var(--face-muted);">
|
|
831
|
+
Protected by Biometric Security Policy
|
|
832
|
+
</div>
|
|
833
|
+
</div>
|
|
834
|
+
|
|
835
|
+
${
|
|
836
|
+
passkeys.length === 0
|
|
837
|
+
? `
|
|
838
|
+
<div class="empty-state">
|
|
839
|
+
<div class="empty-title">Zero Passkeys Registered</div>
|
|
840
|
+
<div class="empty-text">
|
|
841
|
+
No hardware biometric passkeys are registered yet. Register your device Touch ID, Face ID, or YubiKey hardware key for cryptographic sign-in.
|
|
842
|
+
</div>
|
|
843
|
+
<div style="font-size: 12px; font-family: var(--face-font-mono); color: var(--face-muted); background: var(--face-surface); padding: 8px 12px; border-radius: 4px; display: inline-block;">
|
|
844
|
+
total-recall auth passkey register
|
|
845
|
+
</div>
|
|
846
|
+
</div>
|
|
847
|
+
`
|
|
848
|
+
: `
|
|
849
|
+
<div class="item-list">
|
|
850
|
+
${passkeys
|
|
851
|
+
.map(
|
|
852
|
+
(pk) => `
|
|
853
|
+
<div class="item-row">
|
|
854
|
+
<div class="item-main">
|
|
855
|
+
<div class="item-title">
|
|
856
|
+
<span>${escapeHtml(pk.name || 'Hardware Authenticator')}</span>
|
|
857
|
+
<span class="status-pill ok">Active</span>
|
|
858
|
+
</div>
|
|
859
|
+
<div class="item-meta">
|
|
860
|
+
ID: ${escapeHtml(pk.id || DASH)} · Added: ${
|
|
861
|
+
pk.created_at ? new Date(pk.created_at).toLocaleDateString() : DASH
|
|
862
|
+
} · Last Used: ${pk.last_used_at ? new Date(pk.last_used_at).toLocaleDateString() : 'Never'}
|
|
863
|
+
</div>
|
|
864
|
+
</div>
|
|
865
|
+
<div>
|
|
866
|
+
<button class="action-btn danger btn-revoke-passkey" data-id="${escapeHtml(
|
|
867
|
+
pk.id
|
|
868
|
+
)}" data-name="${escapeHtml(pk.name || pk.id)}">
|
|
869
|
+
Revoke
|
|
870
|
+
</button>
|
|
871
|
+
</div>
|
|
872
|
+
</div>
|
|
873
|
+
`
|
|
874
|
+
)
|
|
875
|
+
.join('')}
|
|
876
|
+
</div>
|
|
877
|
+
`
|
|
878
|
+
}
|
|
879
|
+
`;
|
|
880
|
+
|
|
881
|
+
container.querySelectorAll('.btn-revoke-passkey').forEach((btn) => {
|
|
882
|
+
btn.addEventListener('click', (e) => {
|
|
883
|
+
const id = e.currentTarget?.getAttribute('data-id');
|
|
884
|
+
const name = e.currentTarget?.getAttribute('data-name');
|
|
885
|
+
if (id) {
|
|
886
|
+
this.confirmAction = { type: 'passkey', id, name };
|
|
887
|
+
this.paint();
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
paintGoogle(container) {
|
|
894
|
+
const accounts = this.googleData?.accounts || [];
|
|
895
|
+
const isConfigured = this.statusData?.google_oauth2?.configured ?? (accounts.length > 0);
|
|
896
|
+
const clientId = this.statusData?.google_oauth2?.client_id || DASH;
|
|
897
|
+
|
|
898
|
+
container.innerHTML = `
|
|
899
|
+
<div class="metrics-strip">
|
|
900
|
+
<div class="metric-card">
|
|
901
|
+
<div class="metric-label">Google Accounts</div>
|
|
902
|
+
<div class="metric-value">${accounts.length}</div>
|
|
903
|
+
<div class="metric-sub">${accounts.length === 1 ? '1 active connection' : `${accounts.length} connections`}</div>
|
|
904
|
+
</div>
|
|
905
|
+
<div class="metric-card">
|
|
906
|
+
<div class="metric-label">OAuth2 Service</div>
|
|
907
|
+
<div class="metric-value" style="color: ${isConfigured ? 'var(--face-accent)' : 'var(--face-warn)'};">
|
|
908
|
+
${isConfigured ? 'Configured' : 'Missing Credentials'}
|
|
909
|
+
</div>
|
|
910
|
+
<div class="metric-sub">Client ID: ${escapeHtml(clientId.slice(0, 16))}…</div>
|
|
911
|
+
</div>
|
|
912
|
+
<div class="metric-card">
|
|
913
|
+
<div class="metric-label">Session Scopes</div>
|
|
914
|
+
<div class="metric-value">OpenID + API</div>
|
|
915
|
+
<div class="metric-sub">Verified OAuth Callback</div>
|
|
916
|
+
</div>
|
|
279
917
|
</div>
|
|
280
918
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
<div
|
|
284
|
-
|
|
285
|
-
<button class="terminal-copy" id="btn-copy" title="Copy console output">Copy</button>
|
|
919
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
|
920
|
+
<h4 style="margin: 0; font-size: 14px;">Connected Google Cloud Accounts</h4>
|
|
921
|
+
<div style="font-size: 12px; color: var(--face-muted);">
|
|
922
|
+
Encrypted Token Rotation
|
|
286
923
|
</div>
|
|
287
|
-
<div class="terminal-readout" id="card-out">Authenticating enclave... Ready for operations.</div>
|
|
288
924
|
</div>
|
|
925
|
+
|
|
926
|
+
${
|
|
927
|
+
accounts.length === 0
|
|
928
|
+
? `
|
|
929
|
+
<div class="empty-state">
|
|
930
|
+
<div class="empty-title">Zero Google Accounts Connected</div>
|
|
931
|
+
<div class="empty-text">
|
|
932
|
+
No Google OAuth accounts are connected. Connect your Google account to grant API access and authenticate sessions.
|
|
933
|
+
</div>
|
|
934
|
+
<div style="font-size: 12px; font-family: var(--face-font-mono); color: var(--face-muted); background: var(--face-surface); padding: 8px 12px; border-radius: 4px; display: inline-block;">
|
|
935
|
+
total-recall auth google connect
|
|
936
|
+
</div>
|
|
937
|
+
</div>
|
|
938
|
+
`
|
|
939
|
+
: `
|
|
940
|
+
<div class="item-list">
|
|
941
|
+
${accounts
|
|
942
|
+
.map((acc) => {
|
|
943
|
+
const isExpired = acc.expiry ? new Date(acc.expiry).getTime() < Date.now() : false;
|
|
944
|
+
const scopesCount = Array.isArray(acc.scopes) ? acc.scopes.length : 0;
|
|
945
|
+
return `
|
|
946
|
+
<div class="item-row">
|
|
947
|
+
<div class="item-main">
|
|
948
|
+
<div class="item-title">
|
|
949
|
+
<span>${escapeHtml(acc.email || acc.account_id)}</span>
|
|
950
|
+
<span class="status-pill ${isExpired ? 'warn' : 'ok'}">
|
|
951
|
+
${isExpired ? 'Token Expired' : 'Connected'}
|
|
952
|
+
</span>
|
|
953
|
+
</div>
|
|
954
|
+
<div class="item-meta">
|
|
955
|
+
Scopes: ${scopesCount} granted · Expiry: ${
|
|
956
|
+
acc.expiry ? new Date(acc.expiry).toLocaleString() : DASH
|
|
957
|
+
} · Last Refresh: ${acc.updated_at ? new Date(acc.updated_at).toLocaleString() : DASH}
|
|
958
|
+
</div>
|
|
959
|
+
</div>
|
|
960
|
+
<div style="display: flex; gap: 8px;">
|
|
961
|
+
<button class="action-btn secondary btn-refresh-google" data-id="${escapeHtml(acc.account_id)}">
|
|
962
|
+
Refresh Token
|
|
963
|
+
</button>
|
|
964
|
+
</div>
|
|
965
|
+
</div>
|
|
966
|
+
`;
|
|
967
|
+
})
|
|
968
|
+
.join('')}
|
|
969
|
+
</div>
|
|
970
|
+
`
|
|
971
|
+
}
|
|
289
972
|
`;
|
|
290
973
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (cmd) this.execute(cmd, []);
|
|
974
|
+
container.querySelectorAll('.btn-refresh-google').forEach((btn) => {
|
|
975
|
+
btn.addEventListener('click', (e) => {
|
|
976
|
+
const id = e.currentTarget?.getAttribute('data-id');
|
|
977
|
+
if (id) this.refreshGoogleAccount(id);
|
|
296
978
|
});
|
|
297
979
|
});
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
paintSecrets(container) {
|
|
983
|
+
const keys = this.secretsData?.keys || [];
|
|
984
|
+
const storePath = this.secretsData?.store || DASH;
|
|
985
|
+
const overdueCount = keys.filter((k) => k.rotation_overdue).length;
|
|
986
|
+
const sharedCount = keys.filter((k) => k.shared_value).length;
|
|
987
|
+
|
|
988
|
+
let filteredKeys = keys;
|
|
989
|
+
if (this.activeFilter === 'overdue') {
|
|
990
|
+
filteredKeys = keys.filter((k) => k.rotation_overdue);
|
|
991
|
+
} else if (this.activeFilter === 'ai') {
|
|
992
|
+
filteredKeys = keys.filter((k) =>
|
|
993
|
+
['google', 'openai', 'anthropic', 'deepseek', 'openrouter'].includes(k.provider)
|
|
994
|
+
);
|
|
995
|
+
} else if (this.activeFilter === 'infra') {
|
|
996
|
+
filteredKeys = keys.filter((k) =>
|
|
997
|
+
['vercel', 'digitalocean', 'stripe', 'mailcow', 'smtp2go', 'telnyx'].includes(k.provider)
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
container.innerHTML = `
|
|
1002
|
+
<div class="metrics-strip">
|
|
1003
|
+
<div class="metric-card">
|
|
1004
|
+
<div class="metric-label">Active Secrets</div>
|
|
1005
|
+
<div class="metric-value">${keys.length}</div>
|
|
1006
|
+
<div class="metric-sub">Single Secrets Store</div>
|
|
1007
|
+
</div>
|
|
1008
|
+
<div class="metric-card">
|
|
1009
|
+
<div class="metric-label">Overdue Rotations</div>
|
|
1010
|
+
<div class="metric-value" style="color: ${overdueCount > 0 ? 'var(--face-warn)' : 'var(--face-accent)'};">
|
|
1011
|
+
${overdueCount}
|
|
1012
|
+
</div>
|
|
1013
|
+
<div class="metric-sub">${overdueCount === 0 ? 'All keys fresh' : 'Exceeds 90-day cycle'}</div>
|
|
1014
|
+
</div>
|
|
1015
|
+
<div class="metric-card">
|
|
1016
|
+
<div class="metric-label">Shared Bindings</div>
|
|
1017
|
+
<div class="metric-value">${sharedCount}</div>
|
|
1018
|
+
<div class="metric-sub">Multi-repo consumers</div>
|
|
1019
|
+
</div>
|
|
1020
|
+
</div>
|
|
1021
|
+
|
|
1022
|
+
<div class="filter-strip">
|
|
1023
|
+
<button class="filter-btn ${this.activeFilter === 'all' ? 'active' : ''}" data-filter="all">
|
|
1024
|
+
All Keys (${keys.length})
|
|
1025
|
+
</button>
|
|
1026
|
+
<button class="filter-btn ${this.activeFilter === 'overdue' ? 'active' : ''}" data-filter="overdue">
|
|
1027
|
+
Overdue (${overdueCount})
|
|
1028
|
+
</button>
|
|
1029
|
+
<button class="filter-btn ${this.activeFilter === 'ai' ? 'active' : ''}" data-filter="ai">
|
|
1030
|
+
AI & LLM
|
|
1031
|
+
</button>
|
|
1032
|
+
<button class="filter-btn ${this.activeFilter === 'infra' ? 'active' : ''}" data-filter="infra">
|
|
1033
|
+
Infrastructure
|
|
1034
|
+
</button>
|
|
1035
|
+
</div>
|
|
1036
|
+
|
|
1037
|
+
<div class="table-responsive">
|
|
1038
|
+
<table>
|
|
1039
|
+
<thead>
|
|
1040
|
+
<tr>
|
|
1041
|
+
<th>Key Identifier</th>
|
|
1042
|
+
<th>Provider</th>
|
|
1043
|
+
<th>Bound Repositories</th>
|
|
1044
|
+
<th>Fingerprint</th>
|
|
1045
|
+
<th>Rotation Status</th>
|
|
1046
|
+
</tr>
|
|
1047
|
+
</thead>
|
|
1048
|
+
<tbody>
|
|
1049
|
+
${
|
|
1050
|
+
filteredKeys.length === 0
|
|
1051
|
+
? `
|
|
1052
|
+
<tr>
|
|
1053
|
+
<td colspan="5" style="text-align: center; color: var(--face-muted); padding: 24px;">
|
|
1054
|
+
No secret keys match the active filter.
|
|
1055
|
+
</td>
|
|
1056
|
+
</tr>
|
|
1057
|
+
`
|
|
1058
|
+
: filteredKeys
|
|
1059
|
+
.slice(0, 50)
|
|
1060
|
+
.map(
|
|
1061
|
+
(k) => `
|
|
1062
|
+
<tr>
|
|
1063
|
+
<td class="code-cell">
|
|
1064
|
+
<strong>${escapeHtml(k.key)}</strong>
|
|
1065
|
+
</td>
|
|
1066
|
+
<td>${escapeHtml(k.provider_name || k.provider || DASH)}</td>
|
|
1067
|
+
<td class="code-cell" style="font-size: 11px;">
|
|
1068
|
+
${escapeHtml(Array.isArray(k.repos) ? k.repos.join(', ') : k.repo || 'global')}
|
|
1069
|
+
</td>
|
|
1070
|
+
<td class="code-cell">${escapeHtml(k.fingerprint || DASH)}</td>
|
|
1071
|
+
<td>
|
|
1072
|
+
<span class="status-pill ${k.rotation_overdue ? 'warn' : 'ok'}">
|
|
1073
|
+
${k.rotation_overdue ? 'Overdue' : 'Fresh'}
|
|
1074
|
+
</span>
|
|
1075
|
+
</td>
|
|
1076
|
+
</tr>
|
|
1077
|
+
`
|
|
1078
|
+
)
|
|
1079
|
+
.join('')
|
|
1080
|
+
}
|
|
1081
|
+
</tbody>
|
|
1082
|
+
</table>
|
|
1083
|
+
</div>
|
|
298
1084
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
1085
|
+
<div style="font-size: 11px; font-family: var(--face-font-mono); color: var(--face-muted); margin-top: 10px;">
|
|
1086
|
+
Store: ${escapeHtml(storePath)}
|
|
1087
|
+
</div>
|
|
1088
|
+
`;
|
|
1089
|
+
|
|
1090
|
+
container.querySelectorAll('.filter-btn').forEach((btn) => {
|
|
1091
|
+
btn.addEventListener('click', (e) => {
|
|
1092
|
+
const filter = e.currentTarget?.getAttribute('data-filter');
|
|
1093
|
+
if (filter) {
|
|
1094
|
+
this.activeFilter = filter;
|
|
1095
|
+
this.paintSecrets(container);
|
|
307
1096
|
}
|
|
308
1097
|
});
|
|
309
|
-
}
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
paintAudit(container) {
|
|
1102
|
+
const findings = this.auditData?.findings || [];
|
|
1103
|
+
const passed = this.auditData?.passed ?? true;
|
|
1104
|
+
const findingsCount = this.auditData?.findings_count ?? findings.length;
|
|
1105
|
+
const auditTitle = this.auditData?.audit || 'Total Recall Auth Security Baseline';
|
|
1106
|
+
|
|
1107
|
+
container.innerHTML = `
|
|
1108
|
+
<div class="metrics-strip">
|
|
1109
|
+
<div class="metric-card">
|
|
1110
|
+
<div class="metric-label">Security Baseline</div>
|
|
1111
|
+
<div class="metric-value" style="color: ${passed ? 'var(--face-accent)' : 'var(--face-danger)'};">
|
|
1112
|
+
${passed ? 'PASS' : 'FAIL'}
|
|
1113
|
+
</div>
|
|
1114
|
+
<div class="metric-sub">Deterministic Audit</div>
|
|
1115
|
+
</div>
|
|
1116
|
+
<div class="metric-card">
|
|
1117
|
+
<div class="metric-label">Findings Count</div>
|
|
1118
|
+
<div class="metric-value" style="color: ${findingsCount > 0 ? 'var(--face-danger)' : 'var(--face-accent)'};">
|
|
1119
|
+
${findingsCount}
|
|
1120
|
+
</div>
|
|
1121
|
+
<div class="metric-sub">${findingsCount === 0 ? 'Zero vulnerabilities' : 'Issues detected'}</div>
|
|
1122
|
+
</div>
|
|
1123
|
+
<div class="metric-card">
|
|
1124
|
+
<div class="metric-label">Audit Engine</div>
|
|
1125
|
+
<div class="metric-value">Active</div>
|
|
1126
|
+
<div class="metric-sub">Hardware Token Verification</div>
|
|
1127
|
+
</div>
|
|
1128
|
+
</div>
|
|
1129
|
+
|
|
1130
|
+
<div style="margin-bottom: 12px;">
|
|
1131
|
+
<h4 style="margin: 0 0 4px 0; font-size: 14px;">${escapeHtml(auditTitle)}</h4>
|
|
1132
|
+
<div style="font-size: 12px; color: var(--face-muted);">
|
|
1133
|
+
Deterministic security posture verification across credentials, session tokens, and permissions.
|
|
1134
|
+
</div>
|
|
1135
|
+
</div>
|
|
1136
|
+
|
|
1137
|
+
${
|
|
1138
|
+
findings.length === 0
|
|
1139
|
+
? `
|
|
1140
|
+
<div class="empty-state">
|
|
1141
|
+
<div class="empty-title" style="color: var(--face-accent);">Zero Findings Detected</div>
|
|
1142
|
+
<div class="empty-text">
|
|
1143
|
+
All security baselines satisfied. Permissions, session signing keys, and credential stores are compliant with Total Recall security policies.
|
|
1144
|
+
</div>
|
|
1145
|
+
</div>
|
|
1146
|
+
`
|
|
1147
|
+
: `
|
|
1148
|
+
<div class="item-list">
|
|
1149
|
+
${findings
|
|
1150
|
+
.map(
|
|
1151
|
+
(f) => `
|
|
1152
|
+
<div class="item-row">
|
|
1153
|
+
<div class="item-main">
|
|
1154
|
+
<div class="item-title">
|
|
1155
|
+
<span class="status-pill ${f.severity === 'critical' ? 'danger' : 'warn'}">
|
|
1156
|
+
${escapeHtml(f.severity || 'Warning')}
|
|
1157
|
+
</span>
|
|
1158
|
+
<span>${escapeHtml(f.title || f.check || 'Policy Violation')}</span>
|
|
1159
|
+
</div>
|
|
1160
|
+
<div class="item-meta">
|
|
1161
|
+
${escapeHtml(f.detail || f.message || DASH)}
|
|
1162
|
+
</div>
|
|
1163
|
+
</div>
|
|
1164
|
+
</div>
|
|
1165
|
+
`
|
|
1166
|
+
)
|
|
1167
|
+
.join('')}
|
|
1168
|
+
</div>
|
|
1169
|
+
`
|
|
1170
|
+
}
|
|
1171
|
+
`;
|
|
310
1172
|
}
|
|
311
1173
|
}
|
|
312
1174
|
|
|
313
1175
|
if (!customElements.get('tr-auth-card')) {
|
|
314
1176
|
customElements.define('tr-auth-card', AuthCard);
|
|
315
1177
|
}
|
|
1178
|
+
|
|
1179
|
+
export { asObject, escapeHtml };
|