@lifeaitools/clauth 2.1.1 → 2.10.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/.clauth-skill/references/keys-guide.md +270 -270
- package/cli/api.js +238 -238
- package/cli/commands/install.js +413 -396
- package/cli/commands/login.test.js +49 -1
- package/cli/commands/ops-install.js +11 -10
- package/cli/commands/ops.test.js +270 -0
- package/cli/commands/scrub.test.js +70 -0
- package/cli/commands/serve.js +2613 -11219
- package/cli/commands/uninstall.js +164 -164
- package/cli/dashboard/dashboard.js +592 -0
- package/cli/dashboard/global.css +65 -0
- package/cli/dashboard/panel-element.js +79 -0
- package/cli/dashboard/panels/build-status-panel.css +12 -0
- package/cli/dashboard/panels/build-status-panel.js +98 -0
- package/cli/dashboard/panels/panel-registry.js +58 -0
- package/cli/dashboard/panels/services-panel.css +95 -0
- package/cli/dashboard/panels/services-panel.js +872 -0
- package/cli/dashboard/panels/surfaces-panel.css +59 -0
- package/cli/dashboard/panels/surfaces-panel.js +474 -0
- package/cli/dashboard/panels/toolbar-panel.css +42 -0
- package/cli/dashboard/panels/toolbar-panel.js +374 -0
- package/cli/dashboard/panels/tunnel-panel.css +61 -0
- package/cli/dashboard/panels/tunnel-panel.js +688 -0
- package/cli/dashboard/panels/webdav-panel.css +38 -0
- package/cli/dashboard/panels/webdav-panel.js +166 -0
- package/cli/fingerprint.test.js +95 -0
- package/cli/http/components/callagent-terminal-component.js +310 -0
- package/cli/http/components/dashboard-component.js +123 -0
- package/cli/http/components/inbox-component.js +41 -0
- package/cli/http/components/mcp-transport-component.js +275 -0
- package/cli/http/components/oauth-component.js +207 -0
- package/cli/http/components/service-crud-component.js +288 -0
- package/cli/http/components/supervisor-component.js +104 -0
- package/cli/http/components/system-component.js +184 -0
- package/cli/http/components/tunnel-component.js +508 -0
- package/cli/http/components/vault-auth-component.js +264 -0
- package/cli/http/components/watchdog-component.js +28 -0
- package/cli/http/request-utils.js +21 -0
- package/cli/http/servers/ops-server.js +335 -0
- package/cli/index.js +5 -91
- package/cli/lib/fs-git.test.js +467 -0
- package/cli/mcp/providers/call-agent-provider.js +30 -0
- package/cli/mcp/providers/clauth-core-provider.js +461 -0
- package/cli/mcp/providers/ops-provider.js +42 -0
- package/cli/mcp/providers/terminal-provider.js +81 -0
- package/cli/mcp/tool-registry.js +48 -0
- package/cli/mcp/tool-schemas.js +822 -0
- package/cli/mcp/tool-schemas.test.js +49 -0
- package/cli/ops/pm2-preflight.js +54 -0
- package/cli/recovery.js +6 -0
- package/cli/recovery.test.js +179 -0
- package/cli/services/agent-pool-instance.js +38 -0
- package/cli/services/claude-binary.js +29 -0
- package/cli/services/delegation-lane-instance.js +35 -0
- package/cli/services/delegation-lane-instance.test.js +37 -0
- package/cli/services/file-io.js +38 -0
- package/cli/services/file-io.test.js +10 -0
- package/cli/services/fs-mount.js +66 -0
- package/cli/services/fs-mount.test.js +14 -0
- package/cli/services/fs-upload-sessions.js +20 -0
- package/cli/services/fs-upload-sessions.test.js +14 -0
- package/cli/services/git-exec.js +37 -0
- package/cli/services/git-exec.test.js +41 -0
- package/cli/services/git-import-guard.js +28 -0
- package/cli/services/git-import-guard.test.js +18 -0
- package/cli/services/service-status.js +191 -0
- package/cli/services/service-status.test.js +39 -0
- package/cli/services/terminal-dispatch.js +135 -0
- package/cli/services/terminal-dispatch.test.js +9 -0
- package/cli/services/terminal-registry.js +39 -0
- package/cli/services/terminal-registry.test.js +31 -0
- package/cli/services/terminal-sessions.js +100 -0
- package/cli/services/terminal-sessions.test.js +26 -0
- package/cli/services/terminal-window.js +106 -0
- package/cli/services/vault-read.js +14 -0
- package/cli/services/vault-read.test.js +35 -0
- package/cli/supervisor-registry.js +116 -0
- package/cli/supervisor-registry.test.js +165 -0
- package/cli/supervisor-ui.test.js +230 -30
- package/cli/webdav-config.js +84 -0
- package/cli/webdav-config.test.js +79 -0
- package/cli/webdav-obscure.js +23 -0
- package/cli/webdav-obscure.test.js +41 -0
- package/cli/webdav-rclone-discovery.js +44 -0
- package/cli/webdav-service.js +30 -118
- package/cli/webdav-service.test.js +107 -0
- package/install.ps1 +102 -102
- package/install.sh +49 -49
- package/package.json +2 -2
- package/scripts/bootstrap.cjs +121 -121
- package/supabase/functions/auth-vault/index.ts +350 -350
- package/supabase/migrations/001_clauth_schema.sql +94 -94
- package/supabase/migrations/002_vault_helpers.sql +90 -90
- package/supabase/migrations/20260317_lockout.sql +26 -26
- package/cli/assets/codevelop/launcher-active.cmd.template +0 -20
- package/cli/assets/codevelop/launcher-static.cmd.template +0 -7
- package/cli/assets/codevelop/windows-terminal.profiles.json +0 -48
- package/cli/commands/codevelop.js +0 -1190
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// cli/dashboard/panel-element.js
|
|
2
|
+
// The real component contract every clauth panel implements: a native Web
|
|
3
|
+
// Component with its own Shadow DOM. This is what "panels" means from here
|
|
4
|
+
// on -- not a file sharing the page's global CSS/DOM, but a genuinely
|
|
5
|
+
// isolated custom element. An edit inside one panel's shadow root has zero
|
|
6
|
+
// effect on any other panel: the browser enforces this, not a naming
|
|
7
|
+
// convention.
|
|
8
|
+
//
|
|
9
|
+
// Loaded FIRST, before any panel script, as a plain classic <script> (no
|
|
10
|
+
// build step, no bundler -- matches clauth's whole delivery model: every
|
|
11
|
+
// dashboard asset is a static file served verbatim).
|
|
12
|
+
//
|
|
13
|
+
// Contract a panel subclass implements:
|
|
14
|
+
// static tagName -- the custom element name, e.g. "clauth-tunnel-panel"
|
|
15
|
+
// static cssHref -- path to this panel's own stylesheet (served
|
|
16
|
+
// from the SAME static-asset route already
|
|
17
|
+
// registered in serve.js -- no new serving
|
|
18
|
+
// mechanism, just loaded inside the shadow root
|
|
19
|
+
// instead of the light DOM <head>)
|
|
20
|
+
// template() -- returns the panel's own inner HTML (a string)
|
|
21
|
+
// mount() -- called once the shadow root's template is in
|
|
22
|
+
// the DOM; do polling/fetch/init work here
|
|
23
|
+
// actions -- a plain object mapping action names (used via
|
|
24
|
+
// data-action="name" in the template) to methods
|
|
25
|
+
// on the instance; a single delegated listener
|
|
26
|
+
// replaces every inline onclick="globalFn()"
|
|
27
|
+
//
|
|
28
|
+
// this.$(selector) / this.$$(selector) look inside the panel's OWN shadow
|
|
29
|
+
// root, never the page's light DOM -- the isolation boundary in code form.
|
|
30
|
+
class ClauthPanelElement extends HTMLElement {
|
|
31
|
+
// connectedCallback() fires the moment the browser's HTML parser reaches
|
|
32
|
+
// this tag -- which, with every panel's own <script> loaded at the bottom
|
|
33
|
+
// of <body> AFTER the tag itself, is BEFORE dashboard.js (and its globals
|
|
34
|
+
// like apiFetch) has executed. Building the shadow DOM here is safe (no
|
|
35
|
+
// external dependency); calling the panel's real mount() here is NOT --
|
|
36
|
+
// it ran into exactly that ordering bug the first time this was written
|
|
37
|
+
// (a live "apiFetch is not defined" error a vm-based test harness could
|
|
38
|
+
// never catch, since it doesn't emulate the browser's synchronous custom-
|
|
39
|
+
// element-upgrade-during-parse behavior). mount() is instead called
|
|
40
|
+
// explicitly by ClauthPanelRegistry.mountAll(), same as every panel
|
|
41
|
+
// before this one -- the registry's whole job is guaranteeing "all
|
|
42
|
+
// scripts have loaded" before any panel's real init logic runs.
|
|
43
|
+
connectedCallback() {
|
|
44
|
+
if (this.shadowRoot) return; // already built (re-attach is a no-op)
|
|
45
|
+
const root = this.attachShadow({ mode: "open" });
|
|
46
|
+
const link = document.createElement("link");
|
|
47
|
+
link.rel = "stylesheet";
|
|
48
|
+
link.href = this.constructor.cssHref;
|
|
49
|
+
root.appendChild(link);
|
|
50
|
+
const body = document.createElement("div");
|
|
51
|
+
body.innerHTML = this.template();
|
|
52
|
+
root.appendChild(body);
|
|
53
|
+
|
|
54
|
+
// One delegated listener replaces every inline onclick="globalFn()" --
|
|
55
|
+
// clicks anywhere in this shadow root bubble to here (shadow DOM
|
|
56
|
+
// retargets bubbling events to the shadow host's listeners), so a
|
|
57
|
+
// single listener covers the whole panel regardless of how deep the
|
|
58
|
+
// clicked element is nested, including elements added later by re-render.
|
|
59
|
+
root.addEventListener("click", (e) => {
|
|
60
|
+
const el = e.target.closest("[data-action]");
|
|
61
|
+
if (!el || !root.contains(el)) return;
|
|
62
|
+
const name = el.dataset.action;
|
|
63
|
+
const fn = this.actions && this.actions[name];
|
|
64
|
+
if (typeof fn === "function") fn.call(this, e, el);
|
|
65
|
+
});
|
|
66
|
+
root.addEventListener("keydown", (e) => {
|
|
67
|
+
const el = e.target.closest("[data-keydown]");
|
|
68
|
+
if (!el || !root.contains(el)) return;
|
|
69
|
+
const name = el.dataset.keydown;
|
|
70
|
+
const fn = this.actions && this.actions[name];
|
|
71
|
+
if (typeof fn === "function") fn.call(this, e, el);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Shadow-scoped lookup helpers -- the isolation boundary in code form.
|
|
76
|
+
$(selector) { return this.shadowRoot.querySelector(selector); }
|
|
77
|
+
$$(selector) { return this.shadowRoot.querySelectorAll(selector); }
|
|
78
|
+
}
|
|
79
|
+
window.ClauthPanelElement = ClauthPanelElement;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/* cli/dashboard/panels/build-status-panel.css
|
|
2
|
+
The "Deploy" identity badge -- lives inline in the header row
|
|
3
|
+
(data-panel="title-status"), 50/50 alongside the status-bar, not in its
|
|
4
|
+
own section. Owned by build-status-panel.js. Depends on the tokens in
|
|
5
|
+
../global.css, loaded before this file. */
|
|
6
|
+
.deploy-badge{display:flex;align-items:center;gap:8px;background:#0f1a2d;border:1px solid var(--border-dark);border-radius:8px;padding:.5rem .85rem;font-size:.82rem;color:var(--grey)}
|
|
7
|
+
.deploy-dot{width:9px;height:9px;border-radius:50%;flex-shrink:0;background:var(--grey-dim)}
|
|
8
|
+
.deploy-dot.live{background:var(--green-bright)}
|
|
9
|
+
.deploy-dot.test{background:var(--blue)}
|
|
10
|
+
.deploy-dot.local{box-shadow:0 0 0 2px var(--orange) inset}
|
|
11
|
+
#deploy-label{color:var(--text-bright);font-weight:500}
|
|
12
|
+
.deploy-sha{font-family:'Courier New',monospace;font-size:.72rem;color:var(--grey-dim)}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// cli/dashboard/panels/build-status-panel.js
|
|
2
|
+
// The "Deploy" identity badge, as a real Web Component: <clauth-build-status-panel>.
|
|
3
|
+
// Answers ONE question: what am I looking at? Live (port 52437, the daemon
|
|
4
|
+
// Dave uses directly) vs Test (any staged/isolated/other-port instance),
|
|
5
|
+
// crossed with Local (this checkout diverges from origin/develop -- dirty
|
|
6
|
+
// working tree, or a different commit) vs Develop (HEAD matches
|
|
7
|
+
// origin/develop exactly, clean tree -- what's actually published).
|
|
8
|
+
//
|
|
9
|
+
// Previously this panel polled GET /builds, which is regen-root's OWN build
|
|
10
|
+
// pipeline status (borrowed from Supabase's prt_storage.build_status blob) --
|
|
11
|
+
// unrelated to clauth's own runtime identity, and the source of "not sure
|
|
12
|
+
// what that sync message is" confusion. GET /deploy-status (serve.js) is
|
|
13
|
+
// clauth's own answer instead. /builds and its backing poller are untouched
|
|
14
|
+
// in case another consumer still needs regen-root's build status; this panel
|
|
15
|
+
// simply no longer surfaces it.
|
|
16
|
+
//
|
|
17
|
+
// Same conversion shape as every panel before it (tunnel-panel.js is the
|
|
18
|
+
// reference implementation): a class extending window.ClauthPanelElement,
|
|
19
|
+
// its own template()/mount(), instance state instead of module-level
|
|
20
|
+
// globals, and mount() -- not connectedCallback() -- doing the real fetch
|
|
21
|
+
// work. connectedCallback() (panel-element.js) fires during HTML parsing,
|
|
22
|
+
// before dashboard.js (and its BASE global) has loaded; calling real logic
|
|
23
|
+
// there throws a live "BASE is not defined" a vm-based test harness would
|
|
24
|
+
// never catch. mount() is instead called explicitly by
|
|
25
|
+
// ClauthPanelRegistry.mountAll(), after every script has loaded.
|
|
26
|
+
//
|
|
27
|
+
// This panel owns none of WRITE_ACTIONS (it only reads GET /deploy-status),
|
|
28
|
+
// so it needs no write-guard bridge -- unlike tunnel-panel.js/webdav-panel.js.
|
|
29
|
+
//
|
|
30
|
+
// Loaded as a plain classic <script> (not type="module") BEFORE dashboard.js
|
|
31
|
+
// -- same pattern as every other panel component.
|
|
32
|
+
class ClauthBuildStatusPanel extends window.ClauthPanelElement {
|
|
33
|
+
static tagName = "clauth-build-status-panel";
|
|
34
|
+
static cssHref = "/dashboard/panels/build-status-panel.css";
|
|
35
|
+
|
|
36
|
+
template() {
|
|
37
|
+
return `
|
|
38
|
+
<div class="deploy-badge" id="deploy-badge">
|
|
39
|
+
<div class="deploy-dot unknown" id="deploy-dot"></div>
|
|
40
|
+
<span id="deploy-label">checking…</span>
|
|
41
|
+
<span id="deploy-sha" class="deploy-sha"></span>
|
|
42
|
+
</div>
|
|
43
|
+
`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
mount() {
|
|
47
|
+
this.pollTimer = null;
|
|
48
|
+
this.updateDeployStatus();
|
|
49
|
+
// A git-sync check is cheap but not free -- 30s keeps the badge honest
|
|
50
|
+
// (a fresh push or a new local edit shows up within half a minute)
|
|
51
|
+
// without polling as aggressively as a per-request build/test pipeline
|
|
52
|
+
// status would need.
|
|
53
|
+
this.pollTimer = setInterval(() => this.updateDeployStatus(), 30000);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async updateDeployStatus() {
|
|
57
|
+
try {
|
|
58
|
+
const data = await fetch(BASE + "/deploy-status").then(r => r.json());
|
|
59
|
+
const dot = this.$("#deploy-dot");
|
|
60
|
+
const label = this.$("#deploy-label");
|
|
61
|
+
const sha = this.$("#deploy-sha");
|
|
62
|
+
|
|
63
|
+
const envWord = data.is_live ? "Live" : "Test";
|
|
64
|
+
// synced === null means git wasn't available or origin/develop has
|
|
65
|
+
// never been fetched locally -- treat as "Local" (unconfirmed is not
|
|
66
|
+
// the same claim as "matches develop").
|
|
67
|
+
const syncWord = data.synced === true ? "Develop" : "Local";
|
|
68
|
+
const text = envWord + " - " + syncWord;
|
|
69
|
+
|
|
70
|
+
dot.className = "deploy-dot " + (data.is_live ? "live" : "test") + " " + (data.synced === true ? "synced" : "local");
|
|
71
|
+
label.textContent = text;
|
|
72
|
+
sha.textContent = data.head_sha ? data.head_sha : "";
|
|
73
|
+
} catch {
|
|
74
|
+
const label = this.$("#deploy-label");
|
|
75
|
+
if (label) label.textContent = "unknown";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// No data-action elements in this panel's template -- it is pure display,
|
|
80
|
+
// polled on a timer. Declared anyway so the contract is explicit rather
|
|
81
|
+
// than implicit-via-undefined.
|
|
82
|
+
get actions() {
|
|
83
|
+
return {};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
customElements.define(ClauthBuildStatusPanel.tagName, ClauthBuildStatusPanel);
|
|
87
|
+
|
|
88
|
+
// Panel interface registration — see ./panel-registry.js. mount() is called
|
|
89
|
+
// explicitly by ClauthPanelRegistry.mountAll(), after every script has
|
|
90
|
+
// loaded -- connectedCallback() (panel-element.js) only builds the shadow
|
|
91
|
+
// DOM during HTML parsing, before dashboard.js (and BASE) exists.
|
|
92
|
+
window.ClauthPanelRegistry.register({
|
|
93
|
+
id: "build-status",
|
|
94
|
+
mount: () => {
|
|
95
|
+
const el = document.querySelector(ClauthBuildStatusPanel.tagName);
|
|
96
|
+
if (el) el.mount();
|
|
97
|
+
},
|
|
98
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// cli/dashboard/panels/panel-registry.js
|
|
2
|
+
// The one shared interface every dashboard panel implements. Loaded FIRST
|
|
3
|
+
// (before any panel script), as a classic script so ClauthPanelRegistry is a
|
|
4
|
+
// window global every later panel script can call at its own top level.
|
|
5
|
+
//
|
|
6
|
+
// Why this exists: before this file, dashboard.js's showMain()/boot() called
|
|
7
|
+
// each panel's init function BY NAME (pollTunnel(), loadWebdavMounts(),
|
|
8
|
+
// updateBuildStatus(), loadSupervisorCockpit(), loadServices(), ...) — the
|
|
9
|
+
// app shell had hardcoded knowledge of every panel's internals, so adding a
|
|
10
|
+
// new panel meant editing showMain() itself. That is a Dependency Inversion
|
|
11
|
+
// violation: the shell should depend on an abstraction ("a panel can be
|
|
12
|
+
// mounted"), not on each panel's concrete function name.
|
|
13
|
+
//
|
|
14
|
+
// The interface is deliberately tiny (Interface Segregation): a panel is
|
|
15
|
+
// { id: string, mount: function }. mount() is called once, when the
|
|
16
|
+
// dashboard has just unlocked and is about to show. A panel with nothing to
|
|
17
|
+
// do on mount still registers with a no-op mount — every registered panel
|
|
18
|
+
// implements the exact same shape (Liskov: the shell never needs to special-
|
|
19
|
+
// case one panel's registration over another's).
|
|
20
|
+
(() => {
|
|
21
|
+
const registry = [];
|
|
22
|
+
|
|
23
|
+
window.ClauthPanelRegistry = {
|
|
24
|
+
// Called once per panel script, at that script's own top level —
|
|
25
|
+
// e.g. `ClauthPanelRegistry.register({ id: "tunnel", mount: pollTunnel });`
|
|
26
|
+
// at the bottom of tunnel-panel.js.
|
|
27
|
+
register(panel) {
|
|
28
|
+
if (!panel || typeof panel.id !== "string" || typeof panel.mount !== "function") {
|
|
29
|
+
console.error("ClauthPanelRegistry.register: invalid panel definition", panel);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (registry.some((p) => p.id === panel.id)) {
|
|
33
|
+
console.error(`ClauthPanelRegistry.register: panel id "${panel.id}" already registered`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
registry.push(panel);
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// Called once by dashboard.js's showMain(), after unlock — mounts every
|
|
40
|
+
// registered panel. A single panel throwing during mount is logged and
|
|
41
|
+
// does not stop the rest from mounting.
|
|
42
|
+
mountAll() {
|
|
43
|
+
for (const panel of registry) {
|
|
44
|
+
try {
|
|
45
|
+
panel.mount();
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error(`ClauthPanelRegistry: panel "${panel.id}" mount() threw:`, err);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Introspection — the dashboard shell (or a future admin panel) can list
|
|
53
|
+
// what's registered without needing to know panel internals.
|
|
54
|
+
list() {
|
|
55
|
+
return registry.map((p) => p.id);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
})();
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/* cli/dashboard/panels/services-panel.css
|
|
2
|
+
The credential grid (data-panel="search-credentials"): project tabs,
|
|
3
|
+
search bar, the credential cards themselves, the Set/Add-Service modals,
|
|
4
|
+
and the OAuth import fields the Set modal uses for gmail/gcal. Shared
|
|
5
|
+
across services-panel.js, services-key-lifecycle.js, and
|
|
6
|
+
services-metadata-crud.js -- none of those three register an independent
|
|
7
|
+
panel identity (see services-metadata-crud.js's own header comment), so
|
|
8
|
+
one stylesheet for all three is the correct mirror of the JS split, not
|
|
9
|
+
an inconsistency. Depends on the tokens in ../global.css, loaded before
|
|
10
|
+
this file. */
|
|
11
|
+
.project-tabs{display:flex;gap:0;margin-bottom:1rem;border-bottom:1px solid var(--bg-card);overflow-x:auto}
|
|
12
|
+
.project-tab{background:none;border:none;border-bottom:2px solid transparent;color:var(--grey-dim);padding:8px 16px;font-size:.82rem;font-weight:500;cursor:pointer;white-space:nowrap;transition:all .15s}
|
|
13
|
+
.project-tab:hover{color:var(--grey);background:rgba(59,130,246,.05)}
|
|
14
|
+
.project-tab.active{color:var(--blue-light);border-bottom-color:var(--blue);background:rgba(59,130,246,.08)}
|
|
15
|
+
.project-tab .tab-count{font-size:.7rem;color:var(--amber);margin-left:4px;font-weight:400}
|
|
16
|
+
.project-tab.active .tab-count{color:var(--blue)}
|
|
17
|
+
.service-search{display:flex;align-items:center;gap:10px;margin:-.35rem 0 1rem;background:var(--bg-dark);border:1px solid var(--bg-card);border-radius:8px;padding:9px 12px}
|
|
18
|
+
.service-search-label{font-size:.76rem;color:var(--amber);font-weight:600;letter-spacing:.02em;text-transform:uppercase;white-space:nowrap}
|
|
19
|
+
.service-search-input{flex:1;min-width:180px;background:var(--bg-darker);border:1px solid var(--border);border-radius:6px;color:var(--text-bright);font-family:'Courier New',monospace;font-size:.88rem;padding:8px 11px;outline:none;transition:border-color .15s}
|
|
20
|
+
.service-search-input:focus{border-color:var(--blue)}
|
|
21
|
+
.service-search-count{font-size:.78rem;color:var(--amber);white-space:nowrap}
|
|
22
|
+
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1rem}
|
|
23
|
+
.card{background:var(--bg-card);border:1px solid var(--border);border-radius:8px;padding:1.25rem;transition:border-color .2s}
|
|
24
|
+
.card:hover{border-color:var(--blue)}
|
|
25
|
+
.card-top{display:flex;align-items:flex-start;justify-content:space-between}
|
|
26
|
+
.card-main{flex:1;min-width:0}
|
|
27
|
+
.card-title-row{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}
|
|
28
|
+
.card-name{font-size:1rem;font-weight:600;color:var(--text-white);margin-bottom:3px}
|
|
29
|
+
.card-slug{font-family:'Courier New',monospace;font-size:.75rem;color:var(--amber);background:rgba(217,168,108,.1);padding:1px 6px;border-radius:3px;letter-spacing:.3px}
|
|
30
|
+
.card-meta-row{display:flex;align-items:center;gap:6px;margin-top:2px;flex-wrap:wrap}
|
|
31
|
+
.card-type{font-size:.78rem;color:var(--amber);text-transform:uppercase;letter-spacing:.5px}
|
|
32
|
+
.card-desc{font-size:.78rem;color:var(--amber);margin-top:4px;line-height:1.3}
|
|
33
|
+
.expiry-badge{font-size:.65rem;border-radius:3px;padding:1px 6px}
|
|
34
|
+
.btn-rotate{background:#0e7490;border:1px solid #06b6d4;color:var(--teal-light);font-size:.75rem;padding:3px 8px;border-radius:4px;cursor:pointer}
|
|
35
|
+
.card-getkey{font-size:.75rem;color:var(--blue-light);text-decoration:none;transition:color .15s}
|
|
36
|
+
.card-getkey:hover{color:#93c5fd;text-decoration:underline}
|
|
37
|
+
.card-corner{display:flex;align-items:center;gap:8px;flex-shrink:0}
|
|
38
|
+
.card-edit-corner{background:none;border:1px solid var(--border);color:var(--amber);border-radius:4px;padding:2px 6px;font-size:.85rem;cursor:pointer;transition:all .15s;line-height:1}
|
|
39
|
+
.card-edit-corner:hover{border-color:var(--blue-light);color:var(--blue-light)}
|
|
40
|
+
.card-project{display:inline-flex;align-items:center;gap:3px;font-size:.68rem;color:var(--blue);background:rgba(59,130,246,.1);border:1px solid rgba(59,130,246,.2);border-radius:3px;padding:1px 4px 1px 6px}
|
|
41
|
+
.card-project-name{color:var(--blue)}
|
|
42
|
+
.card-project-pencil{background:none;border:none;color:var(--blue);cursor:pointer;font-size:.75rem;padding:0 2px;opacity:.75;transition:opacity .15s}
|
|
43
|
+
.card-project-pencil:hover{opacity:1}
|
|
44
|
+
.card-value{font-family:'Courier New',monospace;font-size:.82rem;color:var(--green-bright);background:var(--bg-dark);border-radius:4px;padding:8px 10px;margin-top:10px;word-break:break-all;max-height:80px;overflow:auto;display:none}
|
|
45
|
+
.card-actions{margin-top:10px;display:flex;gap:7px;flex-wrap:wrap}
|
|
46
|
+
.btn-delete{background:var(--red-darker);color:var(--pink);border:1px solid var(--red-dark);padding:3px 10px;font-size:.75rem;font-weight:600;border-radius:4px;cursor:pointer;transition:background .15s,border-color .15s}
|
|
47
|
+
.btn-delete:hover{background:var(--red-dark);border-color:var(--red-bright)}
|
|
48
|
+
.card-edit-panel{display:none;margin-top:8px;background:var(--bg-dark);border-radius:6px;padding:10px;border:1px solid var(--border-dark)}
|
|
49
|
+
.card-edit-panel .ce-head{display:flex;align-items:center;gap:8px;margin-bottom:8px;font-size:.72rem;color:var(--amber)}
|
|
50
|
+
.card-edit-panel .ce-hint{font-size:.68rem;color:var(--amber);opacity:.7}
|
|
51
|
+
.ce-row{display:flex;align-items:center;gap:8px;margin-bottom:6px}
|
|
52
|
+
.ce-row label{font-size:.75rem;color:var(--amber);width:88px;flex-shrink:0}
|
|
53
|
+
.ce-input{flex:1;background:var(--bg-darker);border:1px solid var(--border-dark);border-radius:4px;color:var(--text-bright);font-size:.85rem;padding:5px 8px;outline:none;font-family:'Courier New',monospace;transition:border-color .15s}
|
|
54
|
+
.ce-input:focus{border-color:var(--blue)}
|
|
55
|
+
.ce-foot{display:flex;gap:8px;align-items:center;margin-top:4px}
|
|
56
|
+
.ce-msg{font-size:.75rem}
|
|
57
|
+
.set-panel{display:none;margin-top:10px;background:var(--bg-dark);border-radius:6px;padding:10px;border:1px solid var(--border-dark)}
|
|
58
|
+
.set-panel label{font-size:.75rem;color:var(--amber);display:block;margin-bottom:6px}
|
|
59
|
+
.set-input{width:100%;background:var(--bg-darker);border:1px solid var(--border-dark);border-radius:4px;color:var(--text-bright);font-family:'Courier New',monospace;font-size:.85rem;padding:7px 10px;outline:none;resize:vertical;min-height:58px;transition:border-color .2s}
|
|
60
|
+
.set-input:focus{border-color:var(--blue)}
|
|
61
|
+
.set-foot{margin-top:8px;display:flex;gap:8px;align-items:center}
|
|
62
|
+
.set-msg{font-size:.8rem}
|
|
63
|
+
.set-msg.ok{color:var(--green)} .set-msg.fail{color:var(--red)}
|
|
64
|
+
.btn-reveal{background:var(--border-dark);color:var(--blue-light)}.btn-reveal:hover{background:var(--blue-hover-bg)}
|
|
65
|
+
.btn-copy{background:#1a3328;color:var(--green)}.btn-copy:hover{background:#1a4338}
|
|
66
|
+
.btn-set{background:#2d1f4a;color:#a78bfa}.btn-set:hover{background:#3d2f5a}
|
|
67
|
+
.btn-generate{background:#3b2f0d;color:var(--gold);padding:6px 12px}.btn-generate:hover{background:#56420d}
|
|
68
|
+
.btn-enable{background:var(--green-bg);color:var(--green);border:1px solid var(--green-dark)}.btn-enable:hover{background:#1a3d22;border-color:var(--green)}
|
|
69
|
+
.btn-disable{background:#2d1f1f;color:var(--red);border:1px solid var(--red-darker)}.btn-disable:hover{background:#3d2525;border-color:var(--red)}
|
|
70
|
+
.svc-badge{font-size:.7rem;font-weight:600;padding:2px 7px;border-radius:4px;letter-spacing:.4px;text-transform:uppercase}
|
|
71
|
+
.svc-badge.on{background:rgba(74,222,128,.12);color:var(--green);border:1px solid rgba(74,222,128,.25)}
|
|
72
|
+
.svc-badge.off{background:rgba(251,146,60,.12);color:var(--orange-bright);border:1px solid rgba(251,146,60,.25)}
|
|
73
|
+
.status-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;opacity:0;transition:opacity .3s;margin-top:4px;cursor:default}
|
|
74
|
+
.status-dot.checking{background:var(--orange);opacity:1;animation:pulse 1s infinite}
|
|
75
|
+
.status-dot.ok{background:var(--green-bright);opacity:1}
|
|
76
|
+
.status-dot.fail{background:var(--red-bright);opacity:1}
|
|
77
|
+
@keyframes sdot-fade{to{opacity:0}} .status-dot.fading{animation:sdot-fade 1.5s forwards}
|
|
78
|
+
.add-panel{display:none;background:var(--bg-modal);border:1px solid var(--border);border-radius:8px;padding:1.25rem;margin-bottom:1.5rem}
|
|
79
|
+
.add-panel h3{font-size:.9rem;font-weight:600;color:var(--text-white);margin-bottom:1rem}
|
|
80
|
+
.add-row{display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end;margin-bottom:.75rem}
|
|
81
|
+
.add-field{display:flex;flex-direction:column;gap:4px}
|
|
82
|
+
.add-field label{font-size:.75rem;color:var(--amber)}
|
|
83
|
+
.add-input{background:var(--bg-dark);border:1px solid var(--border);border-radius:6px;color:var(--text-bright);font-family:'Courier New',monospace;font-size:.88rem;padding:7px 12px;outline:none;width:200px;transition:border-color .2s}
|
|
84
|
+
.add-input:focus{border-color:var(--blue)}
|
|
85
|
+
.add-select{background:var(--bg-dark);border:1px solid var(--border);border-radius:6px;color:var(--text-bright);font-size:.88rem;padding:7px 12px;outline:none;width:200px;transition:border-color .2s;cursor:pointer}
|
|
86
|
+
.add-select:focus{border-color:var(--blue)}
|
|
87
|
+
.add-foot{display:flex;gap:8px;align-items:center}
|
|
88
|
+
.add-msg{font-size:.82rem}
|
|
89
|
+
.add-msg.ok{color:var(--green)} .add-msg.fail{color:var(--red)}
|
|
90
|
+
.oauth-fields{display:flex;flex-direction:column;gap:8px;margin-bottom:8px}
|
|
91
|
+
.oauth-field{display:flex;flex-direction:column;gap:3px}
|
|
92
|
+
.oauth-label{font-size:.75rem;color:var(--grey);font-weight:500}
|
|
93
|
+
.oauth-hint{font-size:.71rem;color:var(--amber);font-style:italic}
|
|
94
|
+
.oauth-input{width:100%;background:var(--bg-darker);border:1px solid var(--border-dark);border-radius:4px;color:var(--text-bright);font-family:'Courier New',monospace;font-size:.85rem;padding:7px 10px;outline:none;transition:border-color .2s}
|
|
95
|
+
.oauth-input:focus{border-color:var(--blue)}
|