ltcai 4.0.1 → 4.1.0
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/README.md +28 -23
- package/desktop/electron/main.cjs +44 -0
- package/docs/CHANGELOG.md +42 -0
- package/docs/V4_1_FRONTEND_ARCHITECTURE_REVIEW.md +65 -0
- package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +70 -0
- package/docs/V4_1_VALIDATION_REPORT.md +47 -0
- package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +26 -19
- package/frontend/index.html +24 -0
- package/frontend/openapi.json +14190 -0
- package/frontend/src/App.tsx +184 -0
- package/frontend/src/api/client.ts +317 -0
- package/frontend/src/api/openapi.ts +16637 -0
- package/frontend/src/components/primitives.tsx +204 -0
- package/frontend/src/components/ui/badge.tsx +27 -0
- package/frontend/src/components/ui/button.tsx +37 -0
- package/frontend/src/components/ui/card.tsx +22 -0
- package/frontend/src/components/ui/input.tsx +16 -0
- package/frontend/src/components/ui/textarea.tsx +16 -0
- package/frontend/src/lib/utils.ts +33 -0
- package/frontend/src/main.tsx +23 -0
- package/frontend/src/pages/Act.tsx +245 -0
- package/frontend/src/pages/Ask.tsx +200 -0
- package/frontend/src/pages/Brain.tsx +267 -0
- package/frontend/src/pages/Capture.tsx +158 -0
- package/frontend/src/pages/Library.tsx +187 -0
- package/frontend/src/pages/System.tsx +344 -0
- package/frontend/src/routes.ts +85 -0
- package/frontend/src/store/appStore.ts +54 -0
- package/frontend/src/styles.css +107 -0
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/setup.py +5 -4
- package/latticeai/api/static_routes.py +4 -4
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/multi_agent.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/package.json +54 -15
- package/scripts/build_frontend_assets.mjs +38 -0
- package/scripts/bump_version.py +1 -1
- package/scripts/export_openapi.py +31 -0
- package/scripts/lint_frontend.mjs +86 -0
- package/scripts/run_python.mjs +47 -0
- package/src-tauri/Cargo.lock +4833 -0
- package/src-tauri/Cargo.toml +19 -0
- package/src-tauri/build.rs +3 -0
- package/src-tauri/capabilities/default.json +7 -0
- package/src-tauri/src/main.rs +78 -0
- package/src-tauri/tauri.conf.json +36 -0
- package/static/app/asset-manifest.json +32 -0
- package/static/app/assets/core-CwxXejkd.js +2 -0
- package/static/app/assets/core-CwxXejkd.js.map +1 -0
- package/static/app/assets/index-CJRAzNnf.js +333 -0
- package/static/app/assets/index-CJRAzNnf.js.map +1 -0
- package/static/app/assets/index-CSwBBgf4.css +2 -0
- package/static/app/index.html +25 -0
- package/static/manifest.json +2 -2
- package/static/sw.js +4 -4
- package/scripts/build_v3_assets.mjs +0 -170
- package/scripts/lint_v3.mjs +0 -120
- package/static/v3/asset-manifest.json +0 -63
- package/static/v3/css/lattice.base.49deefb5.css +0 -128
- package/static/v3/css/lattice.base.css +0 -128
- package/static/v3/css/lattice.components.cde18231.css +0 -472
- package/static/v3/css/lattice.components.css +0 -472
- package/static/v3/css/lattice.shell.29d36d85.css +0 -452
- package/static/v3/css/lattice.shell.css +0 -452
- package/static/v3/css/lattice.tokens.304cbc40.css +0 -135
- package/static/v3/css/lattice.tokens.css +0 -135
- package/static/v3/css/lattice.views.0a18b6c5.css +0 -360
- package/static/v3/css/lattice.views.css +0 -360
- package/static/v3/index.html +0 -68
- package/static/v3/js/app.c5c80c46.js +0 -26
- package/static/v3/js/app.js +0 -26
- package/static/v3/js/core/api.ba0fbf14.js +0 -625
- package/static/v3/js/core/api.js +0 -625
- package/static/v3/js/core/components.f25b3b93.js +0 -230
- package/static/v3/js/core/components.js +0 -230
- package/static/v3/js/core/dom.a2773eb0.js +0 -148
- package/static/v3/js/core/dom.js +0 -148
- package/static/v3/js/core/i18n.880e1fec.js +0 -575
- package/static/v3/js/core/i18n.js +0 -575
- package/static/v3/js/core/router.584570f2.js +0 -37
- package/static/v3/js/core/router.js +0 -37
- package/static/v3/js/core/routes.37522821.js +0 -101
- package/static/v3/js/core/routes.js +0 -101
- package/static/v3/js/core/shell.e3f6bbfa.js +0 -420
- package/static/v3/js/core/shell.js +0 -420
- package/static/v3/js/core/store.7b2aa044.js +0 -123
- package/static/v3/js/core/store.js +0 -123
- package/static/v3/js/views/account.eff40715.js +0 -143
- package/static/v3/js/views/account.js +0 -143
- package/static/v3/js/views/activity.0d271ef9.js +0 -67
- package/static/v3/js/views/activity.js +0 -67
- package/static/v3/js/views/admin-audit.660a1fb1.js +0 -185
- package/static/v3/js/views/admin-audit.js +0 -185
- package/static/v3/js/views/admin-permissions.a7ae5f09.js +0 -177
- package/static/v3/js/views/admin-permissions.js +0 -177
- package/static/v3/js/views/admin-policies.3658fd86.js +0 -102
- package/static/v3/js/views/admin-policies.js +0 -102
- package/static/v3/js/views/admin-private-vpc.7d342d36.js +0 -135
- package/static/v3/js/views/admin-private-vpc.js +0 -135
- package/static/v3/js/views/admin-security.07c66b72.js +0 -180
- package/static/v3/js/views/admin-security.js +0 -180
- package/static/v3/js/views/admin-users.f7ac7b43.js +0 -166
- package/static/v3/js/views/admin-users.js +0 -166
- package/static/v3/js/views/agents.17c5288d.js +0 -564
- package/static/v3/js/views/agents.js +0 -564
- package/static/v3/js/views/chat.e250e2cc.js +0 -624
- package/static/v3/js/views/chat.js +0 -624
- package/static/v3/js/views/files.adad14c1.js +0 -365
- package/static/v3/js/views/files.js +0 -365
- package/static/v3/js/views/graph-canvas.17c15d65.js +0 -509
- package/static/v3/js/views/graph-canvas.js +0 -509
- package/static/v3/js/views/home.24f8b8ae.js +0 -200
- package/static/v3/js/views/home.js +0 -200
- package/static/v3/js/views/hooks.37895880.js +0 -220
- package/static/v3/js/views/hooks.js +0 -220
- package/static/v3/js/views/hybrid-search.2fb63ed9.js +0 -194
- package/static/v3/js/views/hybrid-search.js +0 -194
- package/static/v3/js/views/knowledge-graph.4d09c537.js +0 -529
- package/static/v3/js/views/knowledge-graph.js +0 -529
- package/static/v3/js/views/marketplace.ab0583d4.js +0 -141
- package/static/v3/js/views/marketplace.js +0 -141
- package/static/v3/js/views/mcp.99b5c6a7.js +0 -114
- package/static/v3/js/views/mcp.js +0 -114
- package/static/v3/js/views/memory.4ebdf474.js +0 -147
- package/static/v3/js/views/memory.js +0 -147
- package/static/v3/js/views/models.a1ffa147.js +0 -256
- package/static/v3/js/views/models.js +0 -256
- package/static/v3/js/views/my-computer.d9d9ae1c.js +0 -463
- package/static/v3/js/views/my-computer.js +0 -463
- package/static/v3/js/views/network.52a4f181.js +0 -97
- package/static/v3/js/views/network.js +0 -97
- package/static/v3/js/views/pipeline.c522f1ce.js +0 -157
- package/static/v3/js/views/pipeline.js +0 -157
- package/static/v3/js/views/planning.4876fd77.js +0 -174
- package/static/v3/js/views/planning.js +0 -174
- package/static/v3/js/views/runs.b63b2afa.js +0 -144
- package/static/v3/js/views/runs.js +0 -144
- package/static/v3/js/views/settings.b7140634.js +0 -317
- package/static/v3/js/views/settings.js +0 -317
- package/static/v3/js/views/skills.c6c2f965.js +0 -109
- package/static/v3/js/views/skills.js +0 -109
- package/static/v3/js/views/snapshots.6f5db095.js +0 -135
- package/static/v3/js/views/snapshots.js +0 -135
- package/static/v3/js/views/tools.e4f11276.js +0 -108
- package/static/v3/js/views/tools.js +0 -108
- package/static/v3/js/views/workflows.7752225a.js +0 -213
- package/static/v3/js/views/workflows.js +0 -213
- package/static/v3/js/views/workspace-admin.c466029b.js +0 -156
- package/static/v3/js/views/workspace-admin.js +0 -156
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { t } from "../core/i18n.880e1fec.js";
|
|
2
|
-
|
|
3
|
-
export async function render(ctx) {
|
|
4
|
-
const { h, icon, api, store, c, toast } = ctx;
|
|
5
|
-
const host = h("div.lt3-stack-6", c.loading({ lines: 4 }));
|
|
6
|
-
|
|
7
|
-
async function load() {
|
|
8
|
-
const [profile, sso] = await Promise.all([api.profile(), api.ssoConfig()]);
|
|
9
|
-
host.replaceChildren(
|
|
10
|
-
c.viewHeader({
|
|
11
|
-
eyebrow: t("account.eyebrow"),
|
|
12
|
-
title: t("account.title"),
|
|
13
|
-
sub: t("account.sub"),
|
|
14
|
-
actions: [c.sourceBadge(profile.ok ? "live" : "unavailable")],
|
|
15
|
-
}),
|
|
16
|
-
profile.ok && profile.data ? signedInPanel(profile.data, sso.data || {}) : authPanel(sso.data || {}),
|
|
17
|
-
);
|
|
18
|
-
if (profile.ok && profile.data) {
|
|
19
|
-
store.setUser({
|
|
20
|
-
email: profile.data.email || "",
|
|
21
|
-
nickname: profile.data.nickname || profile.data.name || profile.data.email || t("shell.you"),
|
|
22
|
-
role: profile.data.role || "user",
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function authPanel(sso) {
|
|
28
|
-
const email = input("email", t("account.email"));
|
|
29
|
-
const password = input("password", t("account.password"));
|
|
30
|
-
const regEmail = input("email", t("account.email"));
|
|
31
|
-
const regName = input("text", t("account.name"));
|
|
32
|
-
const regNick = input("text", t("account.nickname"));
|
|
33
|
-
const regPassword = input("password", t("account.password"));
|
|
34
|
-
|
|
35
|
-
async function doLogin() {
|
|
36
|
-
const res = await api.login(email.value.trim(), password.value);
|
|
37
|
-
toast(resultText(res, t("account.loginOk")), res.ok ? "ok" : "err");
|
|
38
|
-
if (res.ok) load();
|
|
39
|
-
}
|
|
40
|
-
async function doRegister() {
|
|
41
|
-
const res = await api.register({
|
|
42
|
-
email: regEmail.value.trim(),
|
|
43
|
-
password: regPassword.value,
|
|
44
|
-
name: regName.value.trim(),
|
|
45
|
-
nickname: regNick.value.trim(),
|
|
46
|
-
});
|
|
47
|
-
toast(resultText(res, t("account.registerOk")), res.ok ? "ok" : "err");
|
|
48
|
-
if (res.ok) load();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return h("div.lt3-grid-2",
|
|
52
|
-
c.panel({
|
|
53
|
-
title: t("account.login"),
|
|
54
|
-
children: h("div.lt3-stack-4",
|
|
55
|
-
field(ctx, t("account.email"), email),
|
|
56
|
-
field(ctx, t("account.password"), password),
|
|
57
|
-
h("button.lt3-btn.lt3-btn--primary", { on: { click: doLogin } }, icon("login"), t("account.login")),
|
|
58
|
-
),
|
|
59
|
-
}),
|
|
60
|
-
c.panel({
|
|
61
|
-
title: t("account.register"),
|
|
62
|
-
sub: t("account.passwordRule"),
|
|
63
|
-
children: h("div.lt3-stack-4",
|
|
64
|
-
field(ctx, t("account.email"), regEmail),
|
|
65
|
-
field(ctx, t("account.name"), regName),
|
|
66
|
-
field(ctx, t("account.nickname"), regNick),
|
|
67
|
-
field(ctx, t("account.password"), regPassword),
|
|
68
|
-
h("button.lt3-btn.lt3-btn--primary", { on: { click: doRegister } }, icon("user-plus"), t("account.register")),
|
|
69
|
-
sso && sso.enabled ? c.banner(t("account.sso"), "info") : null,
|
|
70
|
-
),
|
|
71
|
-
}),
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function signedInPanel(profile, sso) {
|
|
76
|
-
const name = input("text", t("account.name"), profile.name || "");
|
|
77
|
-
const nick = input("text", t("account.nickname"), profile.nickname || "");
|
|
78
|
-
const current = input("password", t("account.currentPassword"));
|
|
79
|
-
const next = input("password", t("account.newPassword"));
|
|
80
|
-
|
|
81
|
-
async function saveProfile() {
|
|
82
|
-
const res = await api.updateProfile({ name: name.value.trim(), nickname: nick.value.trim() });
|
|
83
|
-
toast(resultText(res, t("account.profileOk")), res.ok ? "ok" : "err");
|
|
84
|
-
if (res.ok) load();
|
|
85
|
-
}
|
|
86
|
-
async function savePassword() {
|
|
87
|
-
const res = await api.changePassword(current.value, next.value);
|
|
88
|
-
toast(resultText(res, t("account.passwordOk")), res.ok ? "ok" : "err");
|
|
89
|
-
if (res.ok) { current.value = ""; next.value = ""; }
|
|
90
|
-
}
|
|
91
|
-
async function doLogout() {
|
|
92
|
-
const res = await api.logout();
|
|
93
|
-
toast(resultText(res, t("account.logoutOk")), res.ok ? "ok" : "err");
|
|
94
|
-
if (res.ok) load();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return h("div.lt3-grid-2",
|
|
98
|
-
c.panel({
|
|
99
|
-
title: t("account.profile"),
|
|
100
|
-
actions: [c.statePill(t("account.signedIn"))],
|
|
101
|
-
children: h("div.lt3-stack-4",
|
|
102
|
-
h("dl.lt3-keyval",
|
|
103
|
-
h("dt", t("account.email")), h("dd", h("span.lt3-mono", profile.email || "—")),
|
|
104
|
-
h("dt", t("common.role")), h("dd", c.pill(profile.role || "user", "info")),
|
|
105
|
-
h("dt", t("account.sso")), h("dd", c.statePill(sso && sso.enabled ? "ready" : "idle")),
|
|
106
|
-
),
|
|
107
|
-
field(ctx, t("account.name"), name),
|
|
108
|
-
field(ctx, t("account.nickname"), nick),
|
|
109
|
-
h("div.lt3-row-2",
|
|
110
|
-
h("button.lt3-btn.lt3-btn--primary", { on: { click: saveProfile } }, icon("device-floppy"), t("common.save")),
|
|
111
|
-
h("button.lt3-btn.lt3-btn--ghost", { on: { click: doLogout } }, icon("logout"), t("account.logout")),
|
|
112
|
-
),
|
|
113
|
-
),
|
|
114
|
-
}),
|
|
115
|
-
c.panel({
|
|
116
|
-
title: t("account.changePassword"),
|
|
117
|
-
sub: t("account.passwordRule"),
|
|
118
|
-
children: h("div.lt3-stack-4",
|
|
119
|
-
field(ctx, t("account.currentPassword"), current),
|
|
120
|
-
field(ctx, t("account.newPassword"), next),
|
|
121
|
-
h("button.lt3-btn.lt3-btn--primary", { on: { click: savePassword } }, icon("key"), t("account.changePassword")),
|
|
122
|
-
),
|
|
123
|
-
}),
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
await load();
|
|
128
|
-
return host;
|
|
129
|
-
|
|
130
|
-
function input(type, label, value = "") {
|
|
131
|
-
return h("input.lt3-input", { type, value, autocomplete: type === "password" ? "current-password" : "on", "aria-label": label });
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function field({ h }, label, control) {
|
|
136
|
-
return h("div.lt3-field", h("label.lt3-label", label), control);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function resultText(res, okText) {
|
|
140
|
-
if (res && res.ok) return okText;
|
|
141
|
-
const data = (res && res.data) || {};
|
|
142
|
-
return String(data.detail || data.error || res?.error || t("common.unavailable"));
|
|
143
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { t } from "../core/i18n.js";
|
|
2
|
-
|
|
3
|
-
export async function render(ctx) {
|
|
4
|
-
const { h, icon, api, store, c, toast } = ctx;
|
|
5
|
-
const host = h("div.lt3-stack-6", c.loading({ lines: 4 }));
|
|
6
|
-
|
|
7
|
-
async function load() {
|
|
8
|
-
const [profile, sso] = await Promise.all([api.profile(), api.ssoConfig()]);
|
|
9
|
-
host.replaceChildren(
|
|
10
|
-
c.viewHeader({
|
|
11
|
-
eyebrow: t("account.eyebrow"),
|
|
12
|
-
title: t("account.title"),
|
|
13
|
-
sub: t("account.sub"),
|
|
14
|
-
actions: [c.sourceBadge(profile.ok ? "live" : "unavailable")],
|
|
15
|
-
}),
|
|
16
|
-
profile.ok && profile.data ? signedInPanel(profile.data, sso.data || {}) : authPanel(sso.data || {}),
|
|
17
|
-
);
|
|
18
|
-
if (profile.ok && profile.data) {
|
|
19
|
-
store.setUser({
|
|
20
|
-
email: profile.data.email || "",
|
|
21
|
-
nickname: profile.data.nickname || profile.data.name || profile.data.email || t("shell.you"),
|
|
22
|
-
role: profile.data.role || "user",
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function authPanel(sso) {
|
|
28
|
-
const email = input("email", t("account.email"));
|
|
29
|
-
const password = input("password", t("account.password"));
|
|
30
|
-
const regEmail = input("email", t("account.email"));
|
|
31
|
-
const regName = input("text", t("account.name"));
|
|
32
|
-
const regNick = input("text", t("account.nickname"));
|
|
33
|
-
const regPassword = input("password", t("account.password"));
|
|
34
|
-
|
|
35
|
-
async function doLogin() {
|
|
36
|
-
const res = await api.login(email.value.trim(), password.value);
|
|
37
|
-
toast(resultText(res, t("account.loginOk")), res.ok ? "ok" : "err");
|
|
38
|
-
if (res.ok) load();
|
|
39
|
-
}
|
|
40
|
-
async function doRegister() {
|
|
41
|
-
const res = await api.register({
|
|
42
|
-
email: regEmail.value.trim(),
|
|
43
|
-
password: regPassword.value,
|
|
44
|
-
name: regName.value.trim(),
|
|
45
|
-
nickname: regNick.value.trim(),
|
|
46
|
-
});
|
|
47
|
-
toast(resultText(res, t("account.registerOk")), res.ok ? "ok" : "err");
|
|
48
|
-
if (res.ok) load();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return h("div.lt3-grid-2",
|
|
52
|
-
c.panel({
|
|
53
|
-
title: t("account.login"),
|
|
54
|
-
children: h("div.lt3-stack-4",
|
|
55
|
-
field(ctx, t("account.email"), email),
|
|
56
|
-
field(ctx, t("account.password"), password),
|
|
57
|
-
h("button.lt3-btn.lt3-btn--primary", { on: { click: doLogin } }, icon("login"), t("account.login")),
|
|
58
|
-
),
|
|
59
|
-
}),
|
|
60
|
-
c.panel({
|
|
61
|
-
title: t("account.register"),
|
|
62
|
-
sub: t("account.passwordRule"),
|
|
63
|
-
children: h("div.lt3-stack-4",
|
|
64
|
-
field(ctx, t("account.email"), regEmail),
|
|
65
|
-
field(ctx, t("account.name"), regName),
|
|
66
|
-
field(ctx, t("account.nickname"), regNick),
|
|
67
|
-
field(ctx, t("account.password"), regPassword),
|
|
68
|
-
h("button.lt3-btn.lt3-btn--primary", { on: { click: doRegister } }, icon("user-plus"), t("account.register")),
|
|
69
|
-
sso && sso.enabled ? c.banner(t("account.sso"), "info") : null,
|
|
70
|
-
),
|
|
71
|
-
}),
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function signedInPanel(profile, sso) {
|
|
76
|
-
const name = input("text", t("account.name"), profile.name || "");
|
|
77
|
-
const nick = input("text", t("account.nickname"), profile.nickname || "");
|
|
78
|
-
const current = input("password", t("account.currentPassword"));
|
|
79
|
-
const next = input("password", t("account.newPassword"));
|
|
80
|
-
|
|
81
|
-
async function saveProfile() {
|
|
82
|
-
const res = await api.updateProfile({ name: name.value.trim(), nickname: nick.value.trim() });
|
|
83
|
-
toast(resultText(res, t("account.profileOk")), res.ok ? "ok" : "err");
|
|
84
|
-
if (res.ok) load();
|
|
85
|
-
}
|
|
86
|
-
async function savePassword() {
|
|
87
|
-
const res = await api.changePassword(current.value, next.value);
|
|
88
|
-
toast(resultText(res, t("account.passwordOk")), res.ok ? "ok" : "err");
|
|
89
|
-
if (res.ok) { current.value = ""; next.value = ""; }
|
|
90
|
-
}
|
|
91
|
-
async function doLogout() {
|
|
92
|
-
const res = await api.logout();
|
|
93
|
-
toast(resultText(res, t("account.logoutOk")), res.ok ? "ok" : "err");
|
|
94
|
-
if (res.ok) load();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return h("div.lt3-grid-2",
|
|
98
|
-
c.panel({
|
|
99
|
-
title: t("account.profile"),
|
|
100
|
-
actions: [c.statePill(t("account.signedIn"))],
|
|
101
|
-
children: h("div.lt3-stack-4",
|
|
102
|
-
h("dl.lt3-keyval",
|
|
103
|
-
h("dt", t("account.email")), h("dd", h("span.lt3-mono", profile.email || "—")),
|
|
104
|
-
h("dt", t("common.role")), h("dd", c.pill(profile.role || "user", "info")),
|
|
105
|
-
h("dt", t("account.sso")), h("dd", c.statePill(sso && sso.enabled ? "ready" : "idle")),
|
|
106
|
-
),
|
|
107
|
-
field(ctx, t("account.name"), name),
|
|
108
|
-
field(ctx, t("account.nickname"), nick),
|
|
109
|
-
h("div.lt3-row-2",
|
|
110
|
-
h("button.lt3-btn.lt3-btn--primary", { on: { click: saveProfile } }, icon("device-floppy"), t("common.save")),
|
|
111
|
-
h("button.lt3-btn.lt3-btn--ghost", { on: { click: doLogout } }, icon("logout"), t("account.logout")),
|
|
112
|
-
),
|
|
113
|
-
),
|
|
114
|
-
}),
|
|
115
|
-
c.panel({
|
|
116
|
-
title: t("account.changePassword"),
|
|
117
|
-
sub: t("account.passwordRule"),
|
|
118
|
-
children: h("div.lt3-stack-4",
|
|
119
|
-
field(ctx, t("account.currentPassword"), current),
|
|
120
|
-
field(ctx, t("account.newPassword"), next),
|
|
121
|
-
h("button.lt3-btn.lt3-btn--primary", { on: { click: savePassword } }, icon("key"), t("account.changePassword")),
|
|
122
|
-
),
|
|
123
|
-
}),
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
await load();
|
|
128
|
-
return host;
|
|
129
|
-
|
|
130
|
-
function input(type, label, value = "") {
|
|
131
|
-
return h("input.lt3-input", { type, value, autocomplete: type === "password" ? "current-password" : "on", "aria-label": label });
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function field({ h }, label, control) {
|
|
136
|
-
return h("div.lt3-field", h("label.lt3-label", label), control);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function resultText(res, okText) {
|
|
140
|
-
if (res && res.ok) return okText;
|
|
141
|
-
const data = (res && res.data) || {};
|
|
142
|
-
return String(data.detail || data.error || res?.error || t("common.unavailable"));
|
|
143
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { t } from "../core/i18n.880e1fec.js";
|
|
2
|
-
|
|
3
|
-
export async function render(ctx) {
|
|
4
|
-
const { h, api, c } = ctx;
|
|
5
|
-
const feedHost = h("div", c.loading({ lines: 4 }));
|
|
6
|
-
const presenceHost = h("div", c.loading({ lines: 2 }));
|
|
7
|
-
const timelineHost = h("div", c.loading({ lines: 4 }));
|
|
8
|
-
|
|
9
|
-
const root = h("div.lt3-stack-6",
|
|
10
|
-
c.viewHeader({ eyebrow: t("activity.eyebrow"), title: t("activity.title"), sub: t("activity.sub") }),
|
|
11
|
-
c.panel({ title: t("activity.feed"), children: feedHost }),
|
|
12
|
-
c.panel({ title: t("activity.presence"), children: presenceHost }),
|
|
13
|
-
c.panel({ title: t("activity.timeMachine"), children: timelineHost }),
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
await load();
|
|
17
|
-
wireLiveFeed();
|
|
18
|
-
return root;
|
|
19
|
-
|
|
20
|
-
async function load() {
|
|
21
|
-
const [feed, presence, timeline] = await Promise.all([api.realtimeFeed(80), api.presence(), api.timeMachine(80)]);
|
|
22
|
-
feedHost.replaceChildren(listEvents(ctx, feed.data?.events || [], feed.source));
|
|
23
|
-
presenceHost.replaceChildren(listPresence(ctx, presence.data?.presence || [], presence.source));
|
|
24
|
-
timelineHost.replaceChildren(listEvents(ctx, timeline.data?.events || [], timeline.source));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function wireLiveFeed() {
|
|
28
|
-
if (!window.EventSource) return;
|
|
29
|
-
try {
|
|
30
|
-
const stream = new EventSource("/realtime/stream");
|
|
31
|
-
stream.onmessage = () => load();
|
|
32
|
-
setTimeout(() => stream.close(), 120000);
|
|
33
|
-
} catch {}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function listEvents(ctx, events, source) {
|
|
38
|
-
const { h, c } = ctx;
|
|
39
|
-
return h("div.lt3-stack-3",
|
|
40
|
-
h("div.lt3-row-2", c.sourceBadge(source)),
|
|
41
|
-
events.length ? c.table([
|
|
42
|
-
{ key: "event", label: t("common.status"), render: (e) => h("div", h("b", e.event_type || e.type || e.area || "event"), h("div.lt3-faint", { style: { "font-size": "var(--lt3-text-2xs)" } }, e.payload?.run_id || e.payload?.workflow_id || e.id || "")) },
|
|
43
|
-
{ key: "area", label: t("common.type"), width: "1%", render: (e) => c.pill(e.area || e.kind || "system") },
|
|
44
|
-
{ key: "when", label: t("common.created"), width: "1%", render: (e) => h("span.lt3-faint", { style: { "white-space": "nowrap" } }, fmt(e.timestamp || e.created_at || e.at)) },
|
|
45
|
-
], events.slice(0, 50)) : c.emptyState({ icon: "activity", title: t("activity.feed"), body: t("common.none") }),
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function listPresence(ctx, rows, source) {
|
|
50
|
-
const { h, c } = ctx;
|
|
51
|
-
return h("div.lt3-stack-3",
|
|
52
|
-
h("div.lt3-row-2", c.sourceBadge(source)),
|
|
53
|
-
rows.length ? c.table([
|
|
54
|
-
{ key: "user", label: t("account.email"), render: (p) => p.user || p.email || p.client_id || "local" },
|
|
55
|
-
{ key: "workspace", label: "workspace_id", render: (p) => h("span.lt3-mono", p.workspace_id || "personal") },
|
|
56
|
-
{ key: "when", label: t("common.updated"), width: "1%", render: (p) => fmt(p.last_seen || p.joined_at) },
|
|
57
|
-
], rows) : c.emptyState({ icon: "users", title: t("activity.presence"), body: t("common.none") }),
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function fmt(ts) {
|
|
62
|
-
if (!ts) return "—";
|
|
63
|
-
try {
|
|
64
|
-
const d = typeof ts === "number" ? new Date(ts * 1000) : new Date(ts);
|
|
65
|
-
return Number.isNaN(d.getTime()) ? String(ts) : d.toLocaleString();
|
|
66
|
-
} catch { return String(ts); }
|
|
67
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { t } from "../core/i18n.js";
|
|
2
|
-
|
|
3
|
-
export async function render(ctx) {
|
|
4
|
-
const { h, api, c } = ctx;
|
|
5
|
-
const feedHost = h("div", c.loading({ lines: 4 }));
|
|
6
|
-
const presenceHost = h("div", c.loading({ lines: 2 }));
|
|
7
|
-
const timelineHost = h("div", c.loading({ lines: 4 }));
|
|
8
|
-
|
|
9
|
-
const root = h("div.lt3-stack-6",
|
|
10
|
-
c.viewHeader({ eyebrow: t("activity.eyebrow"), title: t("activity.title"), sub: t("activity.sub") }),
|
|
11
|
-
c.panel({ title: t("activity.feed"), children: feedHost }),
|
|
12
|
-
c.panel({ title: t("activity.presence"), children: presenceHost }),
|
|
13
|
-
c.panel({ title: t("activity.timeMachine"), children: timelineHost }),
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
await load();
|
|
17
|
-
wireLiveFeed();
|
|
18
|
-
return root;
|
|
19
|
-
|
|
20
|
-
async function load() {
|
|
21
|
-
const [feed, presence, timeline] = await Promise.all([api.realtimeFeed(80), api.presence(), api.timeMachine(80)]);
|
|
22
|
-
feedHost.replaceChildren(listEvents(ctx, feed.data?.events || [], feed.source));
|
|
23
|
-
presenceHost.replaceChildren(listPresence(ctx, presence.data?.presence || [], presence.source));
|
|
24
|
-
timelineHost.replaceChildren(listEvents(ctx, timeline.data?.events || [], timeline.source));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function wireLiveFeed() {
|
|
28
|
-
if (!window.EventSource) return;
|
|
29
|
-
try {
|
|
30
|
-
const stream = new EventSource("/realtime/stream");
|
|
31
|
-
stream.onmessage = () => load();
|
|
32
|
-
setTimeout(() => stream.close(), 120000);
|
|
33
|
-
} catch {}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function listEvents(ctx, events, source) {
|
|
38
|
-
const { h, c } = ctx;
|
|
39
|
-
return h("div.lt3-stack-3",
|
|
40
|
-
h("div.lt3-row-2", c.sourceBadge(source)),
|
|
41
|
-
events.length ? c.table([
|
|
42
|
-
{ key: "event", label: t("common.status"), render: (e) => h("div", h("b", e.event_type || e.type || e.area || "event"), h("div.lt3-faint", { style: { "font-size": "var(--lt3-text-2xs)" } }, e.payload?.run_id || e.payload?.workflow_id || e.id || "")) },
|
|
43
|
-
{ key: "area", label: t("common.type"), width: "1%", render: (e) => c.pill(e.area || e.kind || "system") },
|
|
44
|
-
{ key: "when", label: t("common.created"), width: "1%", render: (e) => h("span.lt3-faint", { style: { "white-space": "nowrap" } }, fmt(e.timestamp || e.created_at || e.at)) },
|
|
45
|
-
], events.slice(0, 50)) : c.emptyState({ icon: "activity", title: t("activity.feed"), body: t("common.none") }),
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function listPresence(ctx, rows, source) {
|
|
50
|
-
const { h, c } = ctx;
|
|
51
|
-
return h("div.lt3-stack-3",
|
|
52
|
-
h("div.lt3-row-2", c.sourceBadge(source)),
|
|
53
|
-
rows.length ? c.table([
|
|
54
|
-
{ key: "user", label: t("account.email"), render: (p) => p.user || p.email || p.client_id || "local" },
|
|
55
|
-
{ key: "workspace", label: "workspace_id", render: (p) => h("span.lt3-mono", p.workspace_id || "personal") },
|
|
56
|
-
{ key: "when", label: t("common.updated"), width: "1%", render: (p) => fmt(p.last_seen || p.joined_at) },
|
|
57
|
-
], rows) : c.emptyState({ icon: "users", title: t("activity.presence"), body: t("common.none") }),
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function fmt(ts) {
|
|
62
|
-
if (!ts) return "—";
|
|
63
|
-
try {
|
|
64
|
-
const d = typeof ts === "number" ? new Date(ts * 1000) : new Date(ts);
|
|
65
|
-
return Number.isNaN(d.getTime()) ? String(ts) : d.toLocaleString();
|
|
66
|
-
} catch { return String(ts); }
|
|
67
|
-
}
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
/* ============================================================================
|
|
2
|
-
* View: Audit Logs — Administration · activity and access trail.
|
|
3
|
-
* Reads /admin/audit (live) and renders unavailable state when it cannot load.
|
|
4
|
-
* Severity filter narrows the rendered events; a compact stat row summarizes
|
|
5
|
-
* actors, volume and risk at a glance.
|
|
6
|
-
* ========================================================================== */
|
|
7
|
-
|
|
8
|
-
import { timeAgo } from "../core/dom.a2773eb0.js";
|
|
9
|
-
|
|
10
|
-
const SEVERITY = {
|
|
11
|
-
warning: { variant: "warn", label: "Warning", icon: "alert-triangle" },
|
|
12
|
-
notice: { variant: "info", label: "Notice", icon: "info-circle" },
|
|
13
|
-
informational: { variant: "", label: "Informational", icon: "point" },
|
|
14
|
-
};
|
|
15
|
-
function severityMeta(s) {
|
|
16
|
-
return SEVERITY[String(s || "").toLowerCase()] || { variant: "", label: titleCase(s) || "Event", icon: "point" };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const FILTERS = [
|
|
20
|
-
{ key: "all", label: "All" },
|
|
21
|
-
{ key: "informational", label: "Informational" },
|
|
22
|
-
{ key: "notice", label: "Notice" },
|
|
23
|
-
{ key: "warning", label: "Warning" },
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
export async function render(ctx) {
|
|
27
|
-
const { h, icon, api, c } = ctx;
|
|
28
|
-
|
|
29
|
-
const state = { events: [], source: "pending", filter: "all", loaded: false };
|
|
30
|
-
|
|
31
|
-
const srcSlot = h("span", c.sourceBadge("pending"));
|
|
32
|
-
const filterHost = h("div", buildTabs());
|
|
33
|
-
const statHost = h("div.lt3-statrow", c.loading({ lines: 1 }));
|
|
34
|
-
const tableHost = h("div", c.loading({ lines: 6 }));
|
|
35
|
-
|
|
36
|
-
const root = h("div.lt3-stack-6",
|
|
37
|
-
c.viewHeader({
|
|
38
|
-
eyebrow: "Administration",
|
|
39
|
-
title: "Audit Logs",
|
|
40
|
-
sub: "Activity and access trail",
|
|
41
|
-
actions: [
|
|
42
|
-
srcSlot,
|
|
43
|
-
h("button.lt3-btn.lt3-btn--ghost", {
|
|
44
|
-
on: { click: () => ctx.toast("Audit export is not available in this build (SIEM export is an Enterprise feature).", "warn") },
|
|
45
|
-
}, icon("download"), "Export"),
|
|
46
|
-
],
|
|
47
|
-
}),
|
|
48
|
-
statHost,
|
|
49
|
-
c.panel({
|
|
50
|
-
eyebrow: "Trail",
|
|
51
|
-
title: "Recent events",
|
|
52
|
-
head: h("div.lt3-row", { style: { "justify-content": "space-between", flex: "1 1 auto", gap: "var(--lt3-space-3)" } },
|
|
53
|
-
h("div", h("div.lt3-eyebrow", "Trail"), h("h3.lt3-panel__title", "Recent events")),
|
|
54
|
-
filterHost,
|
|
55
|
-
),
|
|
56
|
-
children: tableHost,
|
|
57
|
-
}),
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
function buildTabs() {
|
|
61
|
-
return c.tabs(FILTERS, state.filter, (key) => {
|
|
62
|
-
state.filter = key;
|
|
63
|
-
filterHost.replaceChildren(buildTabs());
|
|
64
|
-
renderTable();
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function visibleEvents() {
|
|
69
|
-
if (state.filter === "all") return state.events;
|
|
70
|
-
return state.events.filter((e) => String(e.severity || "").toLowerCase() === state.filter);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function renderStats() {
|
|
74
|
-
const events = state.events;
|
|
75
|
-
const actors = new Set(events.map((e) => e.actor).filter(Boolean)).size;
|
|
76
|
-
const startOfDay = new Date(); startOfDay.setHours(0, 0, 0, 0);
|
|
77
|
-
const today = events.filter((e) => {
|
|
78
|
-
const t = e.ts ? new Date(e.ts).getTime() : NaN;
|
|
79
|
-
return !Number.isNaN(t) && t >= startOfDay.getTime();
|
|
80
|
-
}).length;
|
|
81
|
-
const high = events.filter((e) => ["warning", "high", "critical"].includes(String(e.severity || "").toLowerCase())).length;
|
|
82
|
-
statHost.replaceChildren(
|
|
83
|
-
c.stat({ label: "Total events", value: c.fmtNum(events.length), icon: "list-details" }),
|
|
84
|
-
c.stat({ label: "Actors", value: c.fmtNum(actors), icon: "users" }),
|
|
85
|
-
c.stat({ label: "Today", value: c.fmtNum(today), icon: "calendar-event" }),
|
|
86
|
-
c.stat({ label: "High-severity", value: c.fmtNum(high), icon: "shield-exclamation" }),
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function renderTable() {
|
|
91
|
-
const rows = visibleEvents();
|
|
92
|
-
if (!rows.length) {
|
|
93
|
-
tableHost.replaceChildren(state.loaded
|
|
94
|
-
? c.emptyState({
|
|
95
|
-
icon: "history-off",
|
|
96
|
-
title: state.filter === "all" ? "No audit events" : "No matching events",
|
|
97
|
-
body: state.filter === "all"
|
|
98
|
-
? "Activity will appear here as users act in the workspace."
|
|
99
|
-
: "No events match this severity. Try a broader filter.",
|
|
100
|
-
action: state.filter === "all" ? null : h("button.lt3-btn.lt3-btn--subtle.lt3-btn--sm", {
|
|
101
|
-
on: { click: () => { state.filter = "all"; filterHost.replaceChildren(buildTabs()); renderTable(); } },
|
|
102
|
-
}, icon("filter-off"), "Clear filter"),
|
|
103
|
-
})
|
|
104
|
-
: c.loading({ lines: 6 }));
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
tableHost.replaceChildren(c.table(columns(ctx), rows));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async function load() {
|
|
111
|
-
const res = await api.adminAudit();
|
|
112
|
-
state.events = normalize(res.data);
|
|
113
|
-
state.source = res.source;
|
|
114
|
-
state.loaded = true;
|
|
115
|
-
srcSlot.replaceChildren(c.sourceBadge(res.source));
|
|
116
|
-
renderStats();
|
|
117
|
-
renderTable();
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
load();
|
|
121
|
-
return root;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/* ── table ───────────────────────────────────────────────────────────────── */
|
|
125
|
-
function columns({ h, icon, c }) {
|
|
126
|
-
return [
|
|
127
|
-
{
|
|
128
|
-
key: "ts", label: "Time", width: "1%",
|
|
129
|
-
render: (e) => h("span.lt3-mono.lt3-faint", { style: { "white-space": "nowrap", "font-size": "var(--lt3-text-2xs)" } },
|
|
130
|
-
e.ts ? timeAgo(e.ts) : "—"),
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
key: "actor", label: "Actor",
|
|
134
|
-
render: (e) => h("div.lt3-row-2",
|
|
135
|
-
h("span.lt3-avatar", { style: { width: "26px", height: "26px" } }, initials(e.actor)),
|
|
136
|
-
h("span", { style: { "font-size": "var(--lt3-text-sm)", "white-space": "nowrap" } }, e.actor || "system"),
|
|
137
|
-
),
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
key: "action", label: "Action", width: "1%",
|
|
141
|
-
render: (e) => h("span.lt3-pill.lt3-mono", { style: { "white-space": "nowrap" } }, e.action || "event"),
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
key: "target", label: "Target",
|
|
145
|
-
render: (e) => h("span.lt3-faint", { style: { "font-size": "var(--lt3-text-sm)" } }, e.target || "—"),
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
key: "severity", label: "Severity", width: "1%",
|
|
149
|
-
render: (e) => {
|
|
150
|
-
const m = severityMeta(e.severity);
|
|
151
|
-
return c.pill(m.label, m.variant, { dot: true });
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
];
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/* ── helpers ─────────────────────────────────────────────────────────────── */
|
|
158
|
-
function normalize(data) {
|
|
159
|
-
const list = Array.isArray(data) ? data
|
|
160
|
-
: Array.isArray(data && data.recent_events) ? data.recent_events
|
|
161
|
-
: Array.isArray(data && data.events) ? data.events
|
|
162
|
-
: [];
|
|
163
|
-
return list.map((e) => ({
|
|
164
|
-
ts: e.ts || e.timestamp || e.time || null,
|
|
165
|
-
actor: e.actor || e.user || e.email || "system",
|
|
166
|
-
action: e.action || e.event || "event",
|
|
167
|
-
target: e.target || e.resource || "",
|
|
168
|
-
severity: e.severity || e.level || "informational",
|
|
169
|
-
}));
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function initials(name) {
|
|
173
|
-
const s = String(name || "·").trim();
|
|
174
|
-
if (!s || s === "system") return "SY";
|
|
175
|
-
const at = s.indexOf("@");
|
|
176
|
-
const base = at > 0 ? s.slice(0, at) : s;
|
|
177
|
-
const parts = base.split(/[\s._-]+/).filter(Boolean);
|
|
178
|
-
if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();
|
|
179
|
-
return base.slice(0, 2).toUpperCase();
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function titleCase(s) {
|
|
183
|
-
s = String(s || "").trim();
|
|
184
|
-
return s ? s[0].toUpperCase() + s.slice(1).toLowerCase() : "";
|
|
185
|
-
}
|