aws-runtime-bridge 1.9.175 → 1.9.180
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/CHANGELOG.md +223 -0
- package/README.md +61 -2
- package/dist/adapter/AcodeSdkAdapter.d.ts +382 -0
- package/dist/adapter/AcodeSdkAdapter.js +3 -3
- package/dist/adapter/AdapterRegistry.d.ts +28 -0
- package/dist/adapter/ClaudeSdkAdapter.d.ts +71 -0
- package/dist/adapter/CodexSdkAdapter.d.ts +14 -0
- package/dist/adapter/OpencodeSdkAdapter.d.ts +44 -0
- package/dist/adapter/acode-tool-names.d.ts +9 -0
- package/dist/adapter/idle-poll-loop.d.ts +20 -0
- package/dist/adapter/index.d.ts +3 -0
- package/dist/adapter/instance-ai-config-resolver.d.ts +20 -0
- package/dist/adapter/prompts.d.ts +12 -0
- package/dist/adapter/types.d.ts +201 -0
- package/dist/browser/browser-bootstrap.d.ts +15 -0
- package/dist/browser/browser-connect-token.d.ts +11 -0
- package/dist/browser/browser-frame-encode.d.ts +32 -0
- package/dist/browser/browser-pool.d.ts +129 -0
- package/dist/browser/browser-profile-store.d.ts +41 -0
- package/dist/browser/browser-ws-bridge.d.ts +20 -0
- package/dist/browser/built-in-browser-tools.d.ts +44 -0
- package/dist/browser/element-locator.d.ts +25 -0
- package/dist/browser/element-picker.d.ts +30 -0
- package/dist/browser/frame-encode.d.ts +19 -0
- package/dist/browser/input-dispatcher.d.ts +35 -0
- package/dist/browser/listener-store.d.ts +44 -0
- package/dist/browser/page-listener-manager.d.ts +88 -0
- package/dist/browser/page-listener-types.d.ts +38 -0
- package/dist/browser/screencast-differ.d.ts +10 -0
- package/dist/browser/screencast-manager.d.ts +94 -0
- package/dist/browser/snapshot-serializer.d.ts +56 -0
- package/dist/browser/ssrf-guard.d.ts +44 -0
- package/dist/browser/stealth.d.ts +21 -0
- package/dist/browser/step-archive.d.ts +13 -0
- package/dist/browser/types.d.ts +100 -0
- package/dist/browser/ws-server.d.ts +54 -0
- package/dist/config.d.ts +138 -2
- package/dist/config.js +2 -2
- package/dist/desktop/built-in-desktop-tools.d.ts +68 -0
- package/dist/desktop/built-in-desktop-tools.js +2 -1
- package/dist/desktop/desktop-control.d.ts +142 -0
- package/dist/desktop/desktop-control.js +2 -0
- package/dist/desktop/desktop-mcp-client.d.ts +28 -0
- package/dist/desktop/desktop-staleness.d.ts +66 -0
- package/dist/desktop/desktop-staleness.js +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -1
- package/dist/middleware/auth.d.ts +26 -0
- package/dist/middleware/auth.js +1 -1
- package/dist/remote-desktop/computer-control.d.ts +3 -0
- package/dist/remote-desktop/coords.d.ts +6 -0
- package/dist/remote-desktop/frame-backpressure.d.ts +10 -0
- package/dist/remote-desktop/frame-encode.d.ts +3 -0
- package/dist/remote-desktop/index.js +2 -2
- package/dist/remote-desktop/input-dispatcher.d.ts +4 -0
- package/dist/remote-desktop/permission-manager.d.ts +3 -0
- package/dist/remote-desktop/screen-capture.d.ts +91 -0
- package/dist/remote-desktop/session-manager.d.ts +47 -0
- package/dist/remote-desktop/session-manager.js +1 -1
- package/dist/remote-desktop/system-backend.d.ts +64 -0
- package/dist/remote-desktop/types.d.ts +25 -0
- package/dist/remote-desktop/ui-automation.d.ts +11 -0
- package/dist/remote-desktop/win32-api.d.ts +10 -0
- package/dist/routes/acode-todos.d.ts +13 -0
- package/dist/routes/agent-state.d.ts +15 -0
- package/dist/routes/ai-sources.js +1 -1
- package/dist/routes/background-tasks.d.ts +18 -0
- package/dist/routes/background-tasks.js +2 -2
- package/dist/routes/browser-listeners.d.ts +17 -0
- package/dist/routes/browser-sessions.d.ts +10 -0
- package/dist/routes/capabilities.js +1 -1
- package/dist/routes/dashboard-icon.d.ts +9 -0
- package/dist/routes/dashboard-icon.js +2 -0
- package/dist/routes/dashboard-theme.d.ts +24 -0
- package/dist/routes/dashboard-theme.js +157 -0
- package/dist/routes/dashboard.d.ts +9 -0
- package/dist/routes/dashboard.js +1988 -625
- package/dist/routes/debug-mount.d.ts +16 -0
- package/dist/routes/desktop-control.d.ts +15 -0
- package/dist/routes/desktop-control.js +2 -0
- package/dist/routes/events.d.ts +4 -0
- package/dist/routes/file-browser.d.ts +11 -0
- package/dist/routes/file-change-diff.d.ts +25 -0
- package/dist/routes/file-diff-rollback.d.ts +28 -0
- package/dist/routes/file-snapshot.d.ts +7 -0
- package/dist/routes/git.d.ts +143 -0
- package/dist/routes/instance-tool-status.d.ts +5 -0
- package/dist/routes/instance.d.ts +8 -0
- package/dist/routes/instance.js +1 -1
- package/dist/routes/jdks.d.ts +4 -0
- package/dist/routes/launch.d.ts +5 -0
- package/dist/routes/lsp-providers.d.ts +11 -0
- package/dist/routes/lsp-providers.js +2 -0
- package/dist/routes/lsp.d.ts +41 -0
- package/dist/routes/lsp.js +4 -2
- package/dist/routes/mcp-extra.d.ts +15 -0
- package/dist/routes/mcp.d.ts +16 -0
- package/dist/routes/mcp.js +1 -1
- package/dist/routes/plugins.d.ts +19 -0
- package/dist/routes/plugins.js +1 -1
- package/dist/routes/processes.d.ts +4 -0
- package/dist/routes/properties.d.ts +4 -0
- package/dist/routes/pty.d.ts +29 -0
- package/dist/routes/runtime-binding.d.ts +3 -0
- package/dist/routes/sessions.d.ts +22 -0
- package/dist/routes/skills.d.ts +15 -0
- package/dist/routes/skills.js +1 -1
- package/dist/routes/sub-agents.d.ts +11 -0
- package/dist/routes/sub-agents.js +1 -1
- package/dist/routes/task-context.d.ts +5 -0
- package/dist/routes/tech-stack.d.ts +4 -0
- package/dist/routes/terminal.d.ts +131 -0
- package/dist/routes/terminal.js +25 -25
- package/dist/routes/workspace-index.d.ts +12 -0
- package/dist/routes/workspace-index.js +2 -0
- package/dist/routes/yml.d.ts +4 -0
- package/dist/scripts/migrate-user-scopes.d.ts +26 -0
- package/dist/scripts/migrate-user-scopes.js +2 -0
- package/dist/services/access-control.d.ts +21 -0
- package/dist/services/acode-bg-manager.d.ts +21 -0
- package/dist/services/acode-package.d.ts +10 -0
- package/dist/services/acode-package.js +1 -1
- package/dist/services/agent-capability-sync.d.ts +26 -0
- package/dist/services/agent-capability-sync.js +1 -1
- package/dist/services/agent-event-store.d.ts +20 -0
- package/dist/services/agent-ownership.d.ts +58 -0
- package/dist/services/agent-ownership.js +3 -0
- package/dist/services/agent-process-manager.d.ts +154 -0
- package/dist/services/agent-pty-manager.d.ts +37 -0
- package/dist/services/agent-startup-memory.d.ts +34 -0
- package/dist/services/agent-startup-memory.js +1 -1
- package/dist/services/ai-config-service.d.ts +26 -52
- package/dist/services/ai-config-service.js +1 -3
- package/dist/services/ai-source-apply.d.ts +7 -15
- package/dist/services/ai-source-apply.js +1 -1
- package/dist/services/auto-register.d.ts +165 -1
- package/dist/services/auto-register.js +1 -1
- package/dist/services/aws-client-agent-mcp.d.ts +14 -0
- package/dist/services/aws-client-agent-mcp.js +2 -2
- package/dist/services/bridge-package-publish-hooks.d.ts +14 -0
- package/dist/services/bridge-pairing.d.ts +82 -0
- package/dist/services/bridge-pairing.js +2 -0
- package/dist/services/bridge-temp-files.d.ts +29 -0
- package/dist/services/browser-session-manager.d.ts +71 -0
- package/dist/services/capability-config.d.ts +60 -15
- package/dist/services/capability-config.js +2 -2
- package/dist/services/change-cache-retention.d.ts +9 -0
- package/dist/services/changelog.d.ts +9 -0
- package/dist/services/cli-commands.d.ts +82 -0
- package/dist/services/cli-commands.js +6 -6
- package/dist/services/connection-key-rotation.d.ts +27 -0
- package/dist/services/connection-key-rotation.js +2 -0
- package/dist/services/debug-mount.d.ts +49 -0
- package/dist/services/desktop-mcp-manager.d.ts +103 -1
- package/dist/services/desktop-mcp-manager.js +1 -1
- package/dist/services/event-delta-pusher.d.ts +15 -0
- package/dist/services/fileSnapshotStore.d.ts +33 -0
- package/dist/services/gitignore.d.ts +9 -0
- package/dist/services/instance-ai-config-service.d.ts +9 -0
- package/dist/services/instance-ai-config-service.js +1 -1
- package/dist/services/instance-features.d.ts +127 -0
- package/dist/services/instance-features.js +1 -1
- package/dist/services/instance-init-service.d.ts +3 -0
- package/dist/services/instance-init-service.js +1 -1
- package/dist/services/instance-state.d.ts +23 -0
- package/dist/services/jdk-scanner.d.ts +15 -0
- package/dist/services/launch-manager.d.ts +138 -0
- package/dist/services/launch-metrics.d.ts +42 -0
- package/dist/services/launch-streams.d.ts +19 -0
- package/dist/services/lifecycle-state.d.ts +5 -0
- package/dist/services/log-retention.d.ts +38 -0
- package/dist/services/lsp/install-manager.d.ts +55 -0
- package/dist/services/lsp/install-manager.js +2 -0
- package/dist/services/lsp/provider-registry.d.ts +108 -0
- package/dist/services/lsp/provider-registry.js +2 -0
- package/dist/services/lsp/uri-rewrite.d.ts +44 -0
- package/dist/services/lsp/uri-rewrite.js +2 -0
- package/dist/services/lsp-stdio-bridge.d.ts +41 -4
- package/dist/services/lsp-stdio-bridge.js +3 -3
- package/dist/services/mcp-deploy.d.ts +45 -4
- package/dist/services/mcp-deploy.js +4 -4
- package/dist/services/mcp-env-injector.d.ts +52 -0
- package/dist/services/mcp-launch-binding-queue.d.ts +12 -0
- package/dist/services/mcp-package.d.ts +22 -0
- package/dist/services/mcp-package.js +1 -1
- package/dist/services/mcp-probe-client.d.ts +19 -0
- package/dist/services/mcp-probe-client.js +1 -1
- package/dist/services/open-doc-events.d.ts +22 -0
- package/dist/services/open-doc-registry.d.ts +35 -0
- package/dist/services/orphan-monitor.d.ts +43 -0
- package/dist/services/panel-auth.d.ts +24 -0
- package/dist/services/panel-env.d.ts +28 -0
- package/dist/services/plugin-deployment.d.ts +34 -1
- package/dist/services/plugin-deployment.js +2 -2
- package/dist/services/plugin-event-buffer.d.ts +21 -0
- package/dist/services/plugin-fs.d.ts +11 -0
- package/dist/services/plugin-handler-adapter.d.ts +13 -0
- package/dist/services/plugin-listener-runner.d.ts +10 -0
- package/dist/services/plugin-loader.d.ts +15 -0
- package/dist/services/plugin-manager.d.ts +95 -9
- package/dist/services/plugin-manager.js +4 -4
- package/dist/services/plugin-manifest.d.ts +14 -0
- package/dist/services/plugin-package.d.ts +13 -0
- package/dist/services/plugin-runtime.d.ts +15 -0
- package/dist/services/port-detector.d.ts +15 -0
- package/dist/services/process-detail.d.ts +3 -0
- package/dist/services/process-detector.d.ts +132 -0
- package/dist/services/process-registry.d.ts +120 -0
- package/dist/services/process-top.d.ts +6 -0
- package/dist/services/request-identity.d.ts +77 -0
- package/dist/services/request-identity.js +2 -0
- package/dist/services/request-invalid-dump.d.ts +4 -0
- package/dist/services/ring-buffer.d.ts +33 -0
- package/dist/services/runtime-binding.d.ts +56 -4
- package/dist/services/runtime-binding.js +4 -4
- package/dist/services/runtime-lifecycle-policy.d.ts +17 -0
- package/dist/services/runtime-resources-snapshot.d.ts +26 -0
- package/dist/services/sdk-tool-file-change-recorder.d.ts +9 -0
- package/dist/services/session-output.d.ts +63 -0
- package/dist/services/session-output.js +3 -3
- package/dist/services/skill-manager.d.ts +42 -5
- package/dist/services/skill-manager.js +3 -3
- package/dist/services/snapshot-retention.d.ts +23 -0
- package/dist/services/startup-config-wizard.d.ts +7 -1
- package/dist/services/startup-config-wizard.js +10 -10
- package/dist/services/system-metrics.d.ts +3 -0
- package/dist/services/tech-stack-scanner.d.ts +12 -0
- package/dist/services/terminal-persistence.d.ts +43 -0
- package/dist/services/tool-installer.d.ts +12 -0
- package/dist/services/tunnel-client.d.ts +57 -2
- package/dist/services/tunnel-client.js +1 -1
- package/dist/services/update-log.d.ts +6 -0
- package/dist/services/update-notifier.d.ts +17 -0
- package/dist/services/update-notifier.js +2 -2
- package/dist/services/user-scope.d.ts +115 -0
- package/dist/services/user-scope.js +2 -0
- package/dist/services/windows-service-manager.d.ts +12 -0
- package/dist/services/workspace-files.d.ts +64 -0
- package/dist/services/workspace-index/index-manager.d.ts +46 -0
- package/dist/services/workspace-index/index-manager.js +2 -0
- package/dist/services/workspace-index/indexer.d.ts +15 -0
- package/dist/services/workspace-index/indexer.js +2 -0
- package/dist/services/workspace-index/query.d.ts +74 -0
- package/dist/services/workspace-index/query.js +5 -0
- package/dist/services/workspace-index/resolver.d.ts +26 -0
- package/dist/services/workspace-index/resolver.js +2 -0
- package/dist/services/workspace-index/types.d.ts +89 -0
- package/dist/services/workspace-index/types.js +2 -0
- package/dist/services/workspace-watch.d.ts +4 -0
- package/dist/services/workspaceTaskChangeWatcher.d.ts +49 -0
- package/dist/services/workspaceTaskChangeWatcher.js +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/utils/config-write-queue.d.ts +11 -0
- package/dist/utils/env-safety.d.ts +21 -0
- package/dist/utils/file-utils.d.ts +15 -0
- package/dist/utils/instance-ai-config-debug-logger.d.ts +15 -0
- package/dist/utils/lifecycle-log.d.ts +5 -0
- package/dist/utils/logger.d.ts +13 -0
- package/dist/utils/logger.js +2 -1
- package/dist/utils/npm-version.d.ts +10 -0
- package/dist/utils/path-utils.d.ts +12 -0
- package/dist/utils/scheduler-url.d.ts +18 -0
- package/dist/utils/scheduler-url.js +2 -0
- package/dist/utils/sdk-package-loader.d.ts +13 -0
- package/dist/utils/sdk-package-loader.js +1 -1
- package/dist/utils/target-identity.d.ts +31 -0
- package/dist/utils/target-identity.js +2 -0
- package/dist/utils/validation.d.ts +17 -0
- package/dist/utils/yaml-utils.d.ts +48 -0
- package/package/acode/dist/__tests__/summary-request-helper.d.ts +10 -0
- package/package/acode/dist/ask-user-question-tool.d.ts +17 -0
- package/package/acode/dist/await-complete-tool.d.ts +12 -0
- package/package/acode/dist/background-command-manager.d.ts +105 -0
- package/package/acode/dist/background-command-store.d.ts +27 -0
- package/package/acode/dist/background-command-tools.d.ts +8 -0
- package/package/acode/dist/background-output-ringbuffer.d.ts +15 -0
- package/package/acode/dist/built-in-file-tools.d.ts +65 -0
- package/package/acode/dist/built-in-symbols-tool.d.ts +3 -0
- package/package/acode/dist/builtins/commandArtifacts.d.ts +3 -0
- package/package/acode/dist/builtins/shellCommandPreparation.d.ts +17 -0
- package/package/acode/dist/builtins/shellCommandRunner.d.ts +4 -0
- package/package/acode/dist/builtins/shellOutputDecoder.d.ts +15 -0
- package/package/acode/dist/cache-control.d.ts +45 -0
- package/package/acode/dist/commands/commandLoader.d.ts +3 -0
- package/package/acode/dist/commands/commandParser.d.ts +3 -0
- package/package/acode/dist/commands/commandRegistry.d.ts +3 -0
- package/package/acode/dist/compaction.d.ts +354 -0
- package/package/acode/dist/config.d.ts +3 -0
- package/package/acode/dist/context-overflow.d.ts +32 -0
- package/package/acode/dist/fs-utils.d.ts +18 -0
- package/package/acode/dist/hooks/hookBus.d.ts +3 -0
- package/package/acode/dist/image-utils.d.ts +35 -0
- package/package/acode/dist/index.d.ts +1 -0
- package/package/acode/dist/index.js +1 -1
- package/package/acode/dist/mcpToolNames.d.ts +32 -0
- package/package/acode/dist/microcompact.d.ts +53 -0
- package/package/acode/dist/parsers/ast-cache.d.ts +41 -0
- package/package/acode/dist/parsers/file-index.d.ts +83 -0
- package/package/acode/dist/parsers/file-index.js +4 -0
- package/package/acode/dist/parsers/language-detector.d.ts +4 -0
- package/package/acode/dist/parsers/tree-sitter-loader.d.ts +12 -0
- package/package/acode/dist/parsers/types.d.ts +11 -0
- package/package/acode/dist/paths.d.ts +14 -0
- package/package/acode/dist/plugin-types.d.ts +34 -0
- package/package/acode/dist/read-file-state.d.ts +15 -0
- package/package/acode/dist/runtime-environment.d.ts +16 -0
- package/package/acode/dist/runtime.d.ts +824 -9
- package/package/acode/dist/runtime.js +22 -22
- package/package/acode/dist/skill-tool.d.ts +20 -0
- package/package/acode/dist/sub-agent-manager.d.ts +169 -0
- package/package/acode/dist/sub-agent-manager.js +1 -1
- package/package/acode/dist/sub-agent-store.d.ts +50 -0
- package/package/acode/dist/sub-agent-tools.d.ts +15 -0
- package/package/acode/dist/sub-agent-types.d.ts +13 -0
- package/package/acode/dist/terminal-tool.d.ts +15 -0
- package/package/acode/dist/terminal-types.d.ts +10 -0
- package/package/acode/dist/todo-store.d.ts +15 -0
- package/package/acode/dist/token-estimator.d.ts +17 -0
- package/package/acode/dist/tool-allocation.d.ts +113 -0
- package/package/acode/dist/tool-concurrency/index.d.ts +8 -0
- package/package/acode/dist/tool-concurrency/observability.d.ts +32 -0
- package/package/acode/dist/tool-concurrency/resource-lock.d.ts +30 -0
- package/package/acode/dist/tool-concurrency/scheduler.d.ts +19 -0
- package/package/acode/dist/tool-concurrency/types.d.ts +57 -0
- package/package/acode/dist/tool-group-manager.d.ts +195 -0
- package/package/acode/dist/tool-group-manager.js +4 -4
- package/package/acode/dist/types.d.ts +194 -0
- package/package/aws-client-agent-mcp/README.md +130 -215
- package/package/aws-client-agent-mcp/dist/ability-market-tools.d.ts +57 -0
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts +277 -3
- package/package/aws-client-agent-mcp/dist/agent-client.js +1 -1
- package/package/aws-client-agent-mcp/dist/config.d.ts +18 -0
- package/package/aws-client-agent-mcp/dist/constants.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/context-manager.d.ts +70 -0
- package/package/aws-client-agent-mcp/dist/http-client.d.ts +61 -0
- package/package/aws-client-agent-mcp/dist/index.d.ts +10 -0
- package/package/aws-client-agent-mcp/dist/logger.d.ts +24 -0
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +606 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +4 -4
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +158 -0
- package/package/aws-client-agent-mcp/dist/mcp-tools.js +38 -22
- package/package/aws-client-agent-mcp/dist/memory-store.d.ts +81 -0
- package/package/aws-client-agent-mcp/dist/memory-tools.d.ts +18 -0
- package/package/aws-client-agent-mcp/dist/message-buffer.d.ts +86 -0
- package/package/aws-client-agent-mcp/dist/messageContent.d.ts +24 -0
- package/package/aws-client-agent-mcp/dist/office/office-equation.d.ts +19 -0
- package/package/aws-client-agent-mcp/dist/office/office-image.d.ts +18 -0
- package/package/aws-client-agent-mcp/dist/office/office-workspace.d.ts +29 -0
- package/package/aws-client-agent-mcp/dist/parent-lifecycle.d.ts +18 -0
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts +6 -0
- package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts +4 -0
- package/package/aws-client-agent-mcp/dist/status-reporter.d.ts +41 -0
- package/package/aws-client-agent-mcp/dist/tool-groups.d.ts +136 -0
- package/package/aws-client-agent-mcp/dist/types.d.ts +123 -0
- package/package/aws-client-agent-mcp/dist/watchdog.d.ts +7 -0
- package/package/aws-client-agent-mcp/dist/websocket-client.d.ts +108 -0
- package/package.json +1 -1
package/dist/routes/dashboard.js
CHANGED
|
@@ -1,146 +1,480 @@
|
|
|
1
|
-
import{execFile as X}from"node:child_process";import v from"node:fs";import k from"node:os";import O from"node:path";import{Router as Q}from"express";import{getBridgeVersion as Z,getRuntimeHomeDir as ee,getUserHomeDir as te}from"../config.js";import{getAccessControlEntries as ne,isValidIpEntry as se,parseAllowIps as re,refreshAccessControlCache as ae}from"../services/access-control.js";import{getAgentProcessManager as E}from"../services/agent-process-manager.js";import{autoRegister as oe,getRegistrationState as z,getAutoRegisterConfigFilePath as T}from"../services/auto-register.js";import{createSession as ie,getPanelCredentials as L,validateSession as D,destroySession as F,validateCredentials as _,hasConfiguredCredentials as P,isDefaultCredentials as de,setPanelCredentials as le,getAllSessions as ce,getActiveSessionCount as ue,checkLoginAllowed as ge,recordLoginFailure as pe,recordLoginSuccess as fe}from"../services/panel-auth.js";import{getRuntimeBindingPublicState as V,getRuntimePairingCode as me,normalizeSchedulerBaseUrl as be}from"../services/runtime-binding.js";import{collectSystemMetrics as he}from"../services/system-metrics.js";import{loadPersistedSessions as ve}from"../services/terminal-persistence.js";import{runExclusiveConfigWrite as J}from"../utils/config-write-queue.js";import{createLogger as ye,getRecentLogs as xe}from"../utils/logger.js";import{sdkSessions as we}from"./terminal.js";const x=ye("dashboard");let A=null;const Se=1e4;let M=null,R=null;const Ie=3e4;async function j(){he().then(t=>{M={data:t,ts:Date.now()}}).catch(()=>{}),E().discoverAllProcesses().then(t=>{R={data:t,ts:Date.now()}}).catch(()=>{}),E().cleanupStaleRegistry().catch(()=>{})}const ke=12e4;let C=null,w=null;function Ce(){w&&(clearTimeout(w),w=null),w=setTimeout(Be,ke),w.unref?.(),C===null&&(C=setInterval(j,Ie),C.unref?.(),j())}function Be(){C&&(clearInterval(C),C=null),w&&(clearTimeout(w),w=null)}const W=Z();async function Ee(){const t=await import("node:fs/promises"),n=(await import("node:path")).join(ee(),".aws-bridge","lifecycle-state.json");try{const s=await t.readFile(n,"utf8"),i=JSON.parse(s),r=Array.isArray(i.operations)?i.operations:[];return r.length===0?null:(r.sort((a,o)=>new Date(o.updatedAt).getTime()-new Date(a.updatedAt).getTime()),r[0]||null)}catch{return null}}const c=Q(),Te=["/runtime/login","/runtime/logout","/runtime/credentials-status","/runtime/change-credentials","/runtime/server-config","/runtime/access-control","/runtime/node-info","/runtime/logs","/runtime/re-register","/runtime/admin","/runtime/kill-process","/runtime/dashboard","/login","/setup"];function Pe(t){return t==="/"?!0:Te.some(e=>t===e||t.startsWith(`${e}/`))}c.use((t,e,n)=>{Pe(t.path)&&Ce(),n()});const q="panel_sid";function Ae(t){const e={};if(!t)return e;for(const n of t.split(";")){const s=n.indexOf("=");if(s===-1)continue;const i=n.slice(0,s).trim(),r=n.slice(s+1).trim();i&&(e[i]=r)}return e}function N(t){return Ae(t.headers.cookie)[q]}function G(t,e,n){const s=t.secure||t.headers["x-forwarded-proto"]==="https";return`${q}=${e}; HttpOnly; SameSite=Lax; Path=/${s?"; Secure":""}; Max-Age=${n}`}function m(t,e){const n=N(t);return n&&D(n)?!0:t.xhr||t.headers.accept?.includes("application/json")?(e.status(401).json({ok:!1,error:"unauthorized",needLogin:!0}),!1):(e.redirect("/login"),!1)}c.post("/runtime/login",(t,e)=>{const n=t.socket.remoteAddress||"unknown",s=ge(n);if(!s.allowed){e.status(429).set("Retry-After",String(s.retryAfterSec)).json({ok:!1,error:"too many failed login attempts, try again later",retryAfterSec:s.retryAfterSec});return}const{username:i,password:r}=t.body||{};if(!i||!r){e.status(400).json({ok:!1,error:"username and password required"});return}if(!_(String(i),String(r))){pe(n),e.status(401).json({ok:!1,error:"invalid credentials"});return}fe(n);const a=ie(),o=!P();e.setHeader("Set-Cookie",G(t,a,86400)),e.json({ok:!0,needSetup:o})}),c.post("/runtime/logout",(t,e)=>{const n=N(t);n&&F(n),e.setHeader("Set-Cookie",G(t,"",0)),e.json({ok:!0})}),c.get("/runtime/credentials-status",(t,e)=>{if(!m(t,e))return;const n=L();e.json({ok:!0,isDefault:de(),configured:P(),username:n.username})}),c.post("/runtime/change-credentials",async(t,e)=>{if(!m(t,e))return;const{username:n,password:s,passwordConfirm:i,oldPassword:r}=t.body||{};if(s&&String(s).trim()){if(String(s)!==String(i)){e.status(400).json({ok:!1,error:"passwords do not match"});return}if(P()){if(!r){e.status(400).json({ok:!1,error:"old password required"});return}const o=L();if(!_(o.username,String(r))){e.status(403).json({ok:!1,error:"old password is incorrect"});return}}}if(!n||!String(n).trim()){e.status(400).json({ok:!1,error:"username required"});return}const a=s&&String(s).trim()?String(s).trim():L().password;try{await le(String(n).trim(),a),e.json({ok:!0})}catch(o){const l=o;x.error("Failed to change panel credentials:",l.message),e.status(500).json({ok:!1,error:l.message})}});function $(t){const e=typeof t=="string"?t.trim():"";if(e)return e.length<8?"\u2022\u2022\u2022\u2022":"\u2022\u2022\u2022\u2022"+e.slice(-4)}function Y(t){const e=Array.isArray(t.autoRegisterTargets)?t.autoRegisterTargets.filter(s=>!!(s&&typeof s=="object"&&!Array.isArray(s))):[];return e.length>0?e:["serverUrl","schedulerBaseUrl","userKey"].some(s=>!!String(t[s]??"").trim())?[t]:[]}const Re=["enabled","serverUrl","schedulerBaseUrl","tenantId","userKey","connectionKey","password","instanceName","instanceId","projectId","roleName","workspacePath","registerAddr","registerIp","virtualIp","tunnel","preferSameLanIp","maxRetries","retryInterval"];function Ne(t){const e={};for(const n of Re)t[n]!==void 0&&(e[n]=t[n]);return e}function Ue(t,e){const n=String(t.connectionKey??"").trim()||String(e??"").trim();return{serverUrl:String(t.serverUrl??t.schedulerBaseUrl??"").trim(),tenantId:String(t.tenantId??"").trim(),instanceName:String(t.instanceName??"").trim(),instanceId:String(t.instanceId??"").trim()||void 0,registerAddr:String(t.registerAddr??"").trim(),tunnel:typeof t.tunnel=="boolean"?t.tunnel:void 0,userKeyMasked:$(t.userKey),userKeySet:!!String(t.userKey??"").trim(),connectionKeyMasked:$(n),connectionKeySet:!!n}}function U(t){return be(t)??String(t||"").trim()}c.get("/runtime/server-config",(t,e)=>{if(!m(t,e))return;const n=T();let s={};if(v.existsSync(n))try{s=JSON.parse(v.readFileSync(n,"utf-8"))}catch{e.status(500).json({ok:!1,error:"config.json is not valid JSON; fix it manually first"});return}const i=String(s.connectionKey??"").trim()||void 0,r=Y(s).map(a=>Ue(a,i));e.json({ok:!0,targets:r})});function H(t,e){const n=new Error(e);return n.status=t,n}c.post("/runtime/server-config",async(t,e)=>{if(!m(t,e))return;const n=(t.body||{}).targets;if(!Array.isArray(n)){e.status(400).json({ok:!1,error:"targets array is required"});return}const s=[];for(let r=0;r<n.length;r++){const a=n[r];if(!a||typeof a!="object"||Array.isArray(a)){e.status(400).json({ok:!1,error:`targets[${r}] must be an object`});return}const o=a,l=String(o.serverUrl??o.schedulerBaseUrl??"").trim();if(!l){e.status(400).json({ok:!1,error:`targets[${r}].serverUrl is required`});return}try{const d=new URL(l);if(!["http:","https:"].includes(d.protocol))throw new Error("protocol must be http or https")}catch{e.status(400).json({ok:!1,error:`targets[${r}].serverUrl must be a valid http(s) URL`});return}const p=o.tunnel;let f;if(p==="auto"||p===""||p===null||p===void 0)f=void 0;else if(p===!0||p==="true")f=!0;else if(p===!1||p==="false")f=!1;else{e.status(400).json({ok:!1,error:`targets[${r}].tunnel must be auto, true or false`});return}const h=String(o.originalServerUrl??"").trim();s.push({serverUrl:l,originalServerUrl:h&&h!==l?h:"",userKey:String(o.userKey??"").trim(),tenantId:String(o.tenantId??"").trim(),instanceName:String(o.instanceName??"").trim(),connectionKey:String(o.connectionKey??"").trim(),registerAddr:String(o.registerAddr??"").trim(),tunnel:f})}const i=new Set;for(const r of s){const a=U(r.serverUrl);if(i.has(a)){e.status(400).json({ok:!1,error:"duplicate serverUrl in targets"});return}i.add(a)}try{const r=T();await J(r,()=>{let a={};if(v.existsSync(r))try{a=JSON.parse(v.readFileSync(r,"utf-8"))}catch{throw H(500,"config.json is not valid JSON; fix it manually first")}const o=String(a.connectionKey??"").trim(),l=Y(a),p=l.length>0&&l[0]===a,f=new Map(l.map(d=>[U(String(d.serverUrl??d.schedulerBaseUrl??"").trim()),p?Ne(d):d])),h=[];for(const d of s){let b;if(d.originalServerUrl){const S=U(d.originalServerUrl);b=f.get(S),b&&f.delete(S)}if(!b){const S=U(d.serverUrl);b=f.get(S),b&&f.delete(S)}const u=b?{...b}:{};if(u.serverUrl=d.serverUrl,u.tenantId=d.tenantId,u.instanceName=d.instanceName,u.registerAddr=d.registerAddr,d.tunnel===void 0?delete u.tunnel:u.tunnel=d.tunnel,d.userKey)u.userKey=d.userKey;else if(!String(u.userKey??"").trim())throw H(400,`userKey is required for ${d.serverUrl} (new target without inheritable userKey)`);d.connectionKey?u.connectionKey=d.connectionKey:!String(u.connectionKey??"").trim()&&o&&(u.connectionKey=o),h.push(u)}a.autoRegisterTargets=h;for(const d of["enabled","serverUrl","schedulerBaseUrl","tenantId","userKey","connectionKey","password","instanceName","instanceId","registerAddr","registerIp","virtualIp","tunnel"])delete a[d];v.mkdirSync(O.dirname(r),{recursive:!0}),v.writeFileSync(r,JSON.stringify(a,null,2),"utf-8")}),x.info("Server connection config updated via panel",{configPath:r,targetCount:s.length}),e.json({ok:!0})}catch(r){const a=r,o=Number(a.status)||500;o>=500&&x.error("Failed to save server connection config:",a.message),e.status(o).json({ok:!1,error:a.message})}}),c.get("/runtime/access-control",(t,e)=>{m(t,e)&&e.json({ok:!0,allowIps:ne(),defaultAllowIps:["0.0.0.0"]})}),c.post("/runtime/access-control",async(t,e)=>{if(!m(t,e))return;const n=(t.body||{}).allowIps;if(typeof n!="string"&&!Array.isArray(n)){e.status(400).json({ok:!1,error:"allowIps must be a string or an array"});return}const s=re(n),i=s.filter(r=>!se(r));if(i.length>0){e.status(400).json({ok:!1,error:`invalid IP/CIDR entries: ${i.join(", ")}`});return}try{const r=T();await J(r,()=>{let a={};if(v.existsSync(r))try{a=JSON.parse(v.readFileSync(r,"utf-8"))}catch{throw H(500,"config.json is not valid JSON; fix it manually first")}const o=a.accessControl&&typeof a.accessControl=="object"&&!Array.isArray(a.accessControl)?{...a.accessControl}:{};o.allowIps=s,a.accessControl=o,v.mkdirSync(O.dirname(r),{recursive:!0}),v.writeFileSync(r,JSON.stringify(a,null,2),"utf-8"),ae()}),x.info("Access control config updated via panel",{configPath:r,entryCount:s.length}),e.json({ok:!0,allowIps:s})}catch(r){const a=r,o=Number(a.status)||500;o>=500&&x.error("Failed to save access control config:",a.message),e.status(o).json({ok:!1,error:a.message})}}),c.get("/runtime/node-info",(t,e)=>{if(!m(t,e))return;const n=V(),s=z();e.json({ok:!0,hostname:k.hostname(),platform:k.platform(),arch:k.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version,bridgeVersion:W,configPath:T(),homeDir:te(),bindingStatus:n.status,paired:n.paired,instanceId:n.instanceId||s.instanceId,schedulerBaseUrl:n.schedulerBaseUrl,registered:s.registered,pairingCode:n.pairingCode,activeSessions:ue()})}),c.get("/runtime/logs",(t,e)=>{if(!m(t,e))return;const n=t.query.level||void 0,s=Math.min(Number(t.query.count)||200,500),r=n&&["debug","info","warn","error"].includes(n)?n:void 0,a=xe(s,r);e.json({ok:!0,logs:a})}),c.post("/runtime/re-register",async(t,e)=>{if(m(t,e))try{const n=await oe();e.json({ok:n})}catch(n){const s=n;e.status(500).json({ok:!1,error:s.message})}}),c.get("/runtime/admin/sessions",(t,e)=>{if(!m(t,e))return;const n=ce().map(s=>({token:s.token.slice(0,8)+"...",createdAt:new Date(s.createdAt).toISOString(),lastUsedAt:new Date(s.lastUsedAt).toISOString(),age:Math.floor((Date.now()-s.createdAt)/1e3)}));e.json({ok:!0,sessions:n})}),c.post("/runtime/admin/sessions/:token/destroy",(t,e)=>{m(t,e)&&(F(t.params.token),e.json({ok:!0}))});function Le(t){return new Promise((e,n)=>{const s=k.platform()==="win32",i=s?"taskkill":"kill",r=s?["/PID",String(t),"/F"]:["-9",String(t)];X(i,r,{timeout:5e3},a=>a?n(a):e())})}c.post("/runtime/kill-process",async(t,e)=>{if(!m(t,e))return;const n=Number(t.body.pid);if(!n||!Number.isInteger(n)||n<=0){e.status(400).json({ok:!1,error:"Invalid PID"});return}const s=E(),i=()=>{const a=new Set,o=R?.data??[];for(const l of o)typeof l.pid=="number"&&a.add(l.pid);for(const l of s.getAllManagedProcesses())typeof l.pid=="number"&&a.add(l.pid);return a};let r=i();if(r.size===0)try{R={data:await s.discoverAllProcesses(),ts:Date.now()},r=i()}catch{}if(!r.has(n)){x.warn("Rejected kill-process for unrecognized PID",{pid:n}),e.status(404).json({ok:!1,error:"PID is not a bridge-discovered or registered agent process"});return}try{await Le(n),x.info("Killed process",{pid:n}),e.json({ok:!0})}catch(a){const o=a;x.warn("Failed to kill process",{pid:n,error:o.message}),e.status(500).json({ok:!1,error:o.message})}}),c.get("/runtime/dashboard",async(t,e)=>{if(m(t,e)){if(A&&Date.now()-A.ts<Se)return void e.json(A.data);try{const n=M?.data??null,s=R?.data??null;M||j();const[i,r,a]=await Promise.all([Promise.resolve(V()),Promise.resolve(z()),ve().catch(()=>[])]),l=E().getAllManagedProcesses(),p=Array.from(we.entries()).map(([I,y])=>({sessionId:I,agentId:y.agentId,workspacePath:y.workspacePath,runtimeStatus:y.currentRuntimeStatus||"running",runtimeActionType:y.currentRuntimeActionType,runtimeActionLabel:y.currentRuntimeActionLabel,providerId:y.providerId}));let f={managed:0,orphan:0,unknown:0},h=[];if(s){f={managed:s.filter(g=>g.managementStatus==="managed").length,orphan:s.filter(g=>g.managementStatus==="orphan").length,unknown:s.filter(g=>g.managementStatus==="unknown").length};const I=new Map(l.map(g=>[g.pid,g]));h=s.map(g=>{const B=I.get(g.pid);return{agentId:g.agentId||B?.agentId,pid:g.pid,managementStatus:g.managementStatus,sessionId:B?.sessionId,state:B?.state,command:g.command,cwd:g.cwd,startedAt:g.startedAt}});const y={managed:0,orphan:1,unknown:2};h.sort((g,B)=>(y[g.managementStatus]??9)-(y[B.managementStatus]??9))}const d=new Set(p.map(I=>I.sessionId)),b=a.filter(I=>!d.has(I.sessionId)),u=i,S=await Ee(),K={ok:!0,collectedAt:new Date().toISOString(),bridge:{version:W,hostname:k.hostname(),platform:k.platform(),arch:k.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version},binding:{status:u.status,paired:u.paired,instanceId:u.instanceId,userId:u.userId,schedulerBaseUrl:u.schedulerBaseUrl,pairingCode:me()},registration:{registered:r.registered,instanceId:r.instanceId,lastAttempt:r.lastAttempt?.toISOString(),error:r.error},metrics:n,sessions:{active:p,activeCount:p.length,persisted:b,persistedCount:b.length,total:p.length+b.length},processes:{...f,total:f.managed+f.orphan+f.unknown,list:h,registryCount:l.length},lifecycleOperation:S};A={data:K,ts:Date.now()},e.json(K)}catch(n){const s=n;x.error("Dashboard aggregation failed:",s.message),e.status(500).json({ok:!1,error:s.message})}}}),c.get("/login",(t,e)=>{e.type("text/html; charset=utf-8"),e.send(Me)}),c.get("/",(t,e)=>{const n=N(t);if(!n||!D(n)){e.redirect("/login");return}if(!P()){e.redirect("/setup");return}const s=t.query.lang==="en"?"en":"zh";e.type("text/html; charset=utf-8"),e.send(De.replace("{{LANG}}",s))}),c.get("/setup",(t,e)=>{const n=N(t);if(!n||!D(n)){e.redirect("/login");return}e.type("text/html; charset=utf-8"),e.send(je)});const De=`<!DOCTYPE html>
|
|
1
|
+
import{execFile as ne}from"node:child_process";import y from"node:fs";import B from"node:os";import V from"node:path";import{Router as ae}from"express";import{getBridgeVersion as re,getRuntimeHomeDir as se,getUserHomeDir as oe}from"../config.js";import{getAccessControlEntries as ie,isValidIpEntry as de,parseAllowIps as le,refreshAccessControlCache as ce}from"../services/access-control.js";import{getAgentProcessManager as P}from"../services/agent-process-manager.js";import{autoRegister as ue,getRegistrationState as $,getAutoRegisterConfigFilePath as A}from"../services/auto-register.js";import{createSession as ge,getPanelCredentials as D,validateSession as H,destroySession as W,validateCredentials as J,hasConfiguredCredentials as R,isDefaultCredentials as pe,setPanelCredentials as fe,getAllSessions as me,getActiveSessionCount as he,checkLoginAllowed as be,recordLoginFailure as ve,recordLoginSuccess as ye}from"../services/panel-auth.js";import{getRuntimeBindingPublicState as q,getRuntimePairingCode as xe,normalizeSchedulerBaseUrl as we}from"../services/runtime-binding.js";import{collectSystemMetrics as ke}from"../services/system-metrics.js";import{loadPersistedSessions as Se}from"../services/terminal-persistence.js";import{runExclusiveConfigWrite as G}from"../utils/config-write-queue.js";import{createLogger as Ie,getRecentLogs as Be}from"../utils/logger.js";import{buildTargetKey as T}from"../utils/target-identity.js";import{DASHBOARD_FAVICON_DATA_URI as Y,DASHBOARD_FAVICON_LINK as O}from"./dashboard-icon.js";import{THEME_CSS as j,THEME_BOOT_SCRIPT as z,THEME_FAB_SCRIPT as X}from"./dashboard-theme.js";import{sdkSessions as Ce}from"./terminal.js";const x=Ie("dashboard");let N=null;const Ee=1e4;let M=null,U=null;const Ae=3e4;async function F(){ke().then(t=>{M={data:t,ts:Date.now()}}).catch(()=>{}),P().discoverAllProcesses().then(t=>{U={data:t,ts:Date.now()}}).catch(()=>{}),P().cleanupStaleRegistry().catch(()=>{})}const Te=12e4;let C=null,k=null;function Pe(){k&&(clearTimeout(k),k=null),k=setTimeout(Re,Te),k.unref?.(),C===null&&(C=setInterval(F,Ae),C.unref?.(),F())}function Re(){C&&(clearInterval(C),C=null),k&&(clearTimeout(k),k=null)}const Q=re();async function Ne(){const t=await import("node:fs/promises"),n=(await import("node:path")).join(se(),".aws-bridge","lifecycle-state.json");try{const a=await t.readFile(n,"utf8"),o=JSON.parse(a),r=Array.isArray(o.operations)?o.operations:[];return r.length===0?null:(r.sort((s,d)=>new Date(d.updatedAt).getTime()-new Date(s.updatedAt).getTime()),r[0]||null)}catch{return null}}const f=ae(),Ue=["/runtime/login","/runtime/logout","/runtime/credentials-status","/runtime/change-credentials","/runtime/server-config","/runtime/access-control","/runtime/node-info","/runtime/logs","/runtime/re-register","/runtime/admin","/runtime/kill-process","/runtime/dashboard","/login","/setup"];function Le(t){return t==="/"?!0:Ue.some(e=>t===e||t.startsWith(`${e}/`))}f.use((t,e,n)=>{Le(t.path)&&Pe(),n()});const Z="panel_sid";function Ke(t){const e={};if(!t)return e;for(const n of t.split(";")){const a=n.indexOf("=");if(a===-1)continue;const o=n.slice(0,a).trim(),r=n.slice(a+1).trim();o&&(e[o]=r)}return e}function L(t){return Ke(t.headers.cookie)[Z]}function ee(t,e,n){const a=t.secure||t.headers["x-forwarded-proto"]==="https";return`${Z}=${e}; HttpOnly; SameSite=Lax; Path=/${a?"; Secure":""}; Max-Age=${n}`}function v(t,e){const n=L(t);return n&&H(n)?!0:t.xhr||t.headers.accept?.includes("application/json")?(e.status(401).json({ok:!1,error:"unauthorized",needLogin:!0}),!1):(e.redirect("/login"),!1)}f.post("/runtime/login",(t,e)=>{const n=t.socket.remoteAddress||"unknown",a=be(n);if(!a.allowed){e.status(429).set("Retry-After",String(a.retryAfterSec)).json({ok:!1,error:"too many failed login attempts, try again later",retryAfterSec:a.retryAfterSec});return}const{username:o,password:r}=t.body||{};if(!o||!r){e.status(400).json({ok:!1,error:"username and password required"});return}if(!J(String(o),String(r))){ve(n),e.status(401).json({ok:!1,error:"invalid credentials"});return}ye(n);const s=ge(),d=!R();e.setHeader("Set-Cookie",ee(t,s,86400)),e.json({ok:!0,needSetup:d})}),f.post("/runtime/logout",(t,e)=>{const n=L(t);n&&W(n),e.setHeader("Set-Cookie",ee(t,"",0)),e.json({ok:!0})}),f.get("/runtime/credentials-status",(t,e)=>{if(!v(t,e))return;const n=D();e.json({ok:!0,isDefault:pe(),configured:R(),username:n.username})}),f.post("/runtime/change-credentials",async(t,e)=>{if(!v(t,e))return;const{username:n,password:a,passwordConfirm:o,oldPassword:r}=t.body||{};if(a&&String(a).trim()){if(String(a)!==String(o)){e.status(400).json({ok:!1,error:"passwords do not match"});return}if(R()){if(!r){e.status(400).json({ok:!1,error:"old password required"});return}const d=D();if(!J(d.username,String(r))){e.status(403).json({ok:!1,error:"old password is incorrect"});return}}}if(!n||!String(n).trim()){e.status(400).json({ok:!1,error:"username required"});return}const s=a&&String(a).trim()?String(a).trim():D().password;try{await fe(String(n).trim(),s),e.json({ok:!0})}catch(d){const l=d;x.error("Failed to change panel credentials:",l.message),e.status(500).json({ok:!1,error:l.message})}});function te(t){const e=typeof t=="string"?t.trim():"";if(e)return e.length<8?"\u2022\u2022\u2022\u2022":"\u2022\u2022\u2022\u2022"+e.slice(-4)}function _(t){const e=Array.isArray(t.autoRegisterTargets)?t.autoRegisterTargets.filter(a=>!!(a&&typeof a=="object"&&!Array.isArray(a))):[];return e.length>0?e:["serverUrl","schedulerBaseUrl","userKey"].some(a=>!!String(t[a]??"").trim())?[t]:[]}const De=["enabled","serverUrl","schedulerBaseUrl","tenantId","userKey","connectionKey","password","instanceName","instanceId","projectId","roleName","workspacePath","registerAddr","registerIp","virtualIp","tunnel","preferSameLanIp","maxRetries","retryInterval"];function He(t){const e={};for(const n of De)t[n]!==void 0&&(e[n]=t[n]);return e}function Oe(t,e){const n=String(t.connectionKey??"").trim()||String(e??"").trim();return{serverUrl:String(t.serverUrl??t.schedulerBaseUrl??"").trim(),tenantId:String(t.tenantId??"").trim(),instanceName:String(t.instanceName??"").trim(),instanceId:String(t.instanceId??"").trim()||void 0,registerAddr:String(t.registerAddr??"").trim(),tunnel:typeof t.tunnel=="boolean"?t.tunnel:void 0,userKeyMasked:te(t.userKey),userKeySet:!!String(t.userKey??"").trim(),connectionKeyMasked:te(n),connectionKeySet:!!n,targetKey:T(String(t.serverUrl??t.schedulerBaseUrl??"").trim(),t.userKey)}}function je(t){return we(t)??String(t||"").trim()}f.get("/runtime/server-config",(t,e)=>{if(!v(t,e))return;const n=A();let a={};if(y.existsSync(n))try{a=JSON.parse(y.readFileSync(n,"utf-8"))}catch{e.status(500).json({ok:!1,error:"config.json is not valid JSON; fix it manually first"});return}const o=String(a.connectionKey??"").trim()||void 0,r=_(a).map(s=>Oe(s,o));e.json({ok:!0,targets:r})}),f.get("/runtime/server-config/connection-key",(t,e)=>{if(!v(t,e))return;const n=String((t.query||{}).targetKey??"").trim();if(!n){e.status(400).json({ok:!1,error:"targetKey is required"});return}const a=A();if(!y.existsSync(a)){e.status(404).json({ok:!1,error:"config.json not found"});return}let o;try{o=JSON.parse(y.readFileSync(a,"utf-8"))}catch{e.status(500).json({ok:!1,error:"config.json is not valid JSON"});return}const r=String(o.connectionKey??"").trim()||void 0,s=_(o).find(l=>T(String(l.serverUrl??l.schedulerBaseUrl??"").trim(),l.userKey)===n),d=String(s?.connectionKey??"").trim()||r||"";if(!d){e.status(404).json({ok:!1,error:"\u8BE5\u76EE\u6807\u5C1A\u672A\u914D\u7F6E\u8FDE\u63A5\u5BC6\u94A5\uFF1B\u542F\u52A8\u4E00\u6B21 Bridge \u4F1A\u81EA\u52A8\u751F\u6210\u5E76\u5199\u5165 config.json"});return}e.setHeader("Cache-Control","no-store"),e.json({ok:!0,connectionKey:d})});function K(t,e){const n=new Error(e);return n.status=t,n}f.post("/runtime/server-config",async(t,e)=>{if(!v(t,e))return;const n=(t.body||{}).targets;if(!Array.isArray(n)){e.status(400).json({ok:!1,error:"targets array is required"});return}const a=[];for(let o=0;o<n.length;o++){const r=n[o];if(!r||typeof r!="object"||Array.isArray(r)){e.status(400).json({ok:!1,error:`targets[${o}] must be an object`});return}const s=r,d=String(s.serverUrl??s.schedulerBaseUrl??"").trim();if(!d){e.status(400).json({ok:!1,error:`targets[${o}].serverUrl is required`});return}try{const w=new URL(d);if(!["http:","https:"].includes(w.protocol))throw new Error("protocol must be http or https")}catch{e.status(400).json({ok:!1,error:`targets[${o}].serverUrl must be a valid http(s) URL`});return}const l=s.tunnel;let m;if(l==="auto"||l===""||l===null||l===void 0)m=void 0;else if(l===!0||l==="true")m=!0;else if(l===!1||l==="false")m=!1;else{e.status(400).json({ok:!1,error:`targets[${o}].tunnel must be auto, true or false`});return}const h=String(s.originalServerUrl??"").trim();a.push({serverUrl:d,originalServerUrl:h&&h!==d?h:"",originalTargetKey:String(s.originalTargetKey??"").trim(),userKey:String(s.userKey??"").trim(),tenantId:String(s.tenantId??"").trim(),instanceName:String(s.instanceName??"").trim(),connectionKey:String(s.connectionKey??"").trim(),registerAddr:String(s.registerAddr??"").trim(),tunnel:m})}try{const o=A();await G(o,()=>{let r={};if(y.existsSync(o))try{r=JSON.parse(y.readFileSync(o,"utf-8"))}catch{throw K(500,"config.json is not valid JSON; fix it manually first")}const s=String(r.connectionKey??"").trim(),d=_(r),l=d.length>0&&d[0]===r,m=new Map;for(const i of d){const u=T(String(i.serverUrl??i.schedulerBaseUrl??"").trim(),i.userKey);m.set(u,l?He(i):i)}const h=[];for(const i of a){let u;const b=g=>{const c=m.get(g);return c&&m.delete(g),c},S=(g,c)=>T(g,c),E=g=>{const c=`${je(g)}#`;return[...m.keys()].filter(I=>I.startsWith(c))};if(i.originalTargetKey&&(u=b(i.originalTargetKey)),!u&&i.originalServerUrl&&(u=b(S(i.originalServerUrl,i.userKey)),!u)){const g=E(i.originalServerUrl);g.length===1&&(u=b(g[0]))}if(u||(u=b(S(i.serverUrl,i.userKey))),!u){const g=E(i.serverUrl);g.length>=1&&(u=b(g[0]))}const p=u?{...u}:{};if(p.serverUrl=i.serverUrl,p.tenantId=i.tenantId,p.instanceName=i.instanceName,p.registerAddr=i.registerAddr,i.tunnel===void 0?delete p.tunnel:p.tunnel=i.tunnel,i.userKey)p.userKey=i.userKey;else if(!String(p.userKey??"").trim())throw K(400,`userKey is required for ${i.serverUrl} (new target without inheritable userKey)`);i.connectionKey?p.connectionKey=i.connectionKey:!String(p.connectionKey??"").trim()&&s&&(p.connectionKey=s),h.push(p)}const w=new Map;for(let i=0;i<h.length;i++){const u=h[i],b=T(String(u.serverUrl??u.schedulerBaseUrl??"").trim(),u.userKey),S=w.get(b);if(S!==void 0)throw K(400,`duplicate target: ${String(u.serverUrl??"")} appears in both targets[${S}] and targets[${i}] with the same API key; the same scheduler address can be added multiple times only with different API keys`);w.set(b,i)}r.autoRegisterTargets=h;for(const i of["enabled","serverUrl","schedulerBaseUrl","tenantId","userKey","connectionKey","password","instanceName","instanceId","registerAddr","registerIp","virtualIp","tunnel"])delete r[i];y.mkdirSync(V.dirname(o),{recursive:!0}),y.writeFileSync(o,JSON.stringify(r,null,2),"utf-8")}),x.info("Server connection config updated via panel",{configPath:o,targetCount:a.length}),e.json({ok:!0})}catch(o){const r=o,s=Number(r.status)||500;s>=500&&x.error("Failed to save server connection config:",r.message),e.status(s).json({ok:!1,error:r.message})}}),f.get("/runtime/access-control",(t,e)=>{v(t,e)&&e.json({ok:!0,allowIps:ie(),defaultAllowIps:["0.0.0.0"]})}),f.post("/runtime/access-control",async(t,e)=>{if(!v(t,e))return;const n=(t.body||{}).allowIps;if(typeof n!="string"&&!Array.isArray(n)){e.status(400).json({ok:!1,error:"allowIps must be a string or an array"});return}const a=le(n),o=a.filter(r=>!de(r));if(o.length>0){e.status(400).json({ok:!1,error:`invalid IP/CIDR entries: ${o.join(", ")}`});return}try{const r=A();await G(r,()=>{let s={};if(y.existsSync(r))try{s=JSON.parse(y.readFileSync(r,"utf-8"))}catch{throw K(500,"config.json is not valid JSON; fix it manually first")}const d=s.accessControl&&typeof s.accessControl=="object"&&!Array.isArray(s.accessControl)?{...s.accessControl}:{};d.allowIps=a,s.accessControl=d,y.mkdirSync(V.dirname(r),{recursive:!0}),y.writeFileSync(r,JSON.stringify(s,null,2),"utf-8"),ce()}),x.info("Access control config updated via panel",{configPath:r,entryCount:a.length}),e.json({ok:!0,allowIps:a})}catch(r){const s=r,d=Number(s.status)||500;d>=500&&x.error("Failed to save access control config:",s.message),e.status(d).json({ok:!1,error:s.message})}}),f.get("/runtime/node-info",(t,e)=>{if(!v(t,e))return;const n=q(),a=$();e.json({ok:!0,hostname:B.hostname(),platform:B.platform(),arch:B.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version,bridgeVersion:Q,configPath:A(),homeDir:oe(),bindingStatus:n.status,paired:n.paired,instanceId:n.instanceId||a.instanceId,schedulerBaseUrl:n.schedulerBaseUrl,registered:a.registered,pairingCode:n.pairingCode,activeSessions:he()})}),f.get("/runtime/logs",(t,e)=>{if(!v(t,e))return;const n=t.query.level||void 0,a=Math.min(Number(t.query.count)||200,500),r=n&&["debug","info","warn","error"].includes(n)?n:void 0,s=Be(a,r);e.json({ok:!0,logs:s})}),f.post("/runtime/re-register",async(t,e)=>{if(v(t,e))try{const n=await ue();e.json({ok:n})}catch(n){const a=n;e.status(500).json({ok:!1,error:a.message})}}),f.get("/runtime/admin/sessions",(t,e)=>{if(!v(t,e))return;const n=me().map(a=>({token:a.token.slice(0,8)+"...",createdAt:new Date(a.createdAt).toISOString(),lastUsedAt:new Date(a.lastUsedAt).toISOString(),age:Math.floor((Date.now()-a.createdAt)/1e3)}));e.json({ok:!0,sessions:n})}),f.post("/runtime/admin/sessions/:token/destroy",(t,e)=>{v(t,e)&&(W(t.params.token),e.json({ok:!0}))});function ze(t){return new Promise((e,n)=>{const a=B.platform()==="win32",o=a?"taskkill":"kill",r=a?["/PID",String(t),"/F"]:["-9",String(t)];ne(o,r,{timeout:5e3},s=>s?n(s):e())})}f.post("/runtime/kill-process",async(t,e)=>{if(!v(t,e))return;const n=Number(t.body.pid);if(!n||!Number.isInteger(n)||n<=0){e.status(400).json({ok:!1,error:"Invalid PID"});return}const a=P(),o=()=>{const s=new Set,d=U?.data??[];for(const l of d)typeof l.pid=="number"&&s.add(l.pid);for(const l of a.getAllManagedProcesses())typeof l.pid=="number"&&s.add(l.pid);return s};let r=o();if(r.size===0)try{U={data:await a.discoverAllProcesses(),ts:Date.now()},r=o()}catch{}if(!r.has(n)){x.warn("Rejected kill-process for unrecognized PID",{pid:n}),e.status(404).json({ok:!1,error:"PID is not a bridge-discovered or registered agent process"});return}try{await ze(n),x.info("Killed process",{pid:n}),e.json({ok:!0})}catch(s){const d=s;x.warn("Failed to kill process",{pid:n,error:d.message}),e.status(500).json({ok:!1,error:d.message})}}),f.get("/runtime/dashboard",async(t,e)=>{if(v(t,e)){if(N&&Date.now()-N.ts<Ee)return void e.json(N.data);try{const n=M?.data??null,a=U?.data??null;M||F();const[o,r,s]=await Promise.all([Promise.resolve(q()),Promise.resolve($()),Se().catch(()=>[])]),l=P().getAllManagedProcesses(),m=Array.from(Ce.entries()).map(([p,g])=>({sessionId:p,agentId:g.agentId,workspacePath:g.workspacePath,runtimeStatus:g.currentRuntimeStatus||"running",runtimeActionType:g.currentRuntimeActionType,runtimeActionLabel:g.currentRuntimeActionLabel,providerId:g.providerId}));let h={managed:0,orphan:0,unknown:0},w=[];if(a){h={managed:a.filter(c=>c.managementStatus==="managed").length,orphan:a.filter(c=>c.managementStatus==="orphan").length,unknown:a.filter(c=>c.managementStatus==="unknown").length};const p=new Map(l.map(c=>[c.pid,c]));w=a.map(c=>{const I=p.get(c.pid);return{agentId:c.agentId||I?.agentId,pid:c.pid,managementStatus:c.managementStatus,sessionId:I?.sessionId,state:I?.state,command:c.command,cwd:c.cwd,startedAt:c.startedAt}});const g={managed:0,orphan:1,unknown:2};w.sort((c,I)=>(g[c.managementStatus]??9)-(g[I.managementStatus]??9))}const i=new Set(m.map(p=>p.sessionId)),u=s.filter(p=>!i.has(p.sessionId)),b=o,S=await Ne(),E={ok:!0,collectedAt:new Date().toISOString(),bridge:{version:Q,hostname:B.hostname(),platform:B.platform(),arch:B.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version},binding:{status:b.status,paired:b.paired,instanceId:b.instanceId,userId:b.userId,schedulerBaseUrl:b.schedulerBaseUrl,pairingCode:xe()},registration:{registered:r.registered,instanceId:r.instanceId,lastAttempt:r.lastAttempt?.toISOString(),error:r.error},metrics:n,sessions:{active:m,activeCount:m.length,persisted:u,persistedCount:u.length,total:m.length+u.length},processes:{...h,total:h.managed+h.orphan+h.unknown,list:w,registryCount:l.length},lifecycleOperation:S};N={data:E,ts:Date.now()},e.json(E)}catch(n){const a=n;x.error("Dashboard aggregation failed:",a.message),e.status(500).json({ok:!1,error:a.message})}}}),f.get("/login",(t,e)=>{e.type("text/html; charset=utf-8"),e.send(Fe)}),f.get("/",(t,e)=>{const n=L(t);if(!n||!H(n)){e.redirect("/login");return}if(!R()){e.redirect("/setup");return}const a=t.query.lang==="en"?"en":"zh";e.type("text/html; charset=utf-8"),e.send(Me.replace("{{LANG}}",a))}),f.get("/setup",(t,e)=>{const n=L(t);if(!n||!H(n)){e.redirect("/login");return}e.type("text/html; charset=utf-8"),e.send(_e)});const Me=`<!DOCTYPE html>
|
|
2
2
|
<html lang="{{LANG}}">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Bridge Dashboard</title>
|
|
7
|
+
${O}
|
|
8
|
+
${z}
|
|
7
9
|
<style>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
--orange: #f0a030;
|
|
19
|
-
--green: #43c6a8;
|
|
20
|
-
--green-bg: rgba(67,198,168,0.12);
|
|
21
|
-
--red-bg: rgba(240,84,84,0.12);
|
|
22
|
-
--orange-bg: rgba(240,160,48,0.12);
|
|
23
|
-
--radius: 10px;
|
|
10
|
+
/* \u8BBE\u8BA1\u4EE4\u724C\u89C1 dashboard-theme.ts\uFF1A\u9ED8\u8BA4\u6DF1\u8272\uFF0Chtml.light-theme \u5207\u6D45\u8272 */
|
|
11
|
+
${j}
|
|
12
|
+
|
|
13
|
+
:root { --topbar-h: 56px; }
|
|
14
|
+
|
|
15
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
16
|
+
|
|
17
|
+
html {
|
|
18
|
+
scrollbar-width: thin;
|
|
19
|
+
scrollbar-color: var(--border-strong) transparent;
|
|
24
20
|
}
|
|
25
|
-
|
|
21
|
+
html::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
22
|
+
html::-webkit-scrollbar-track { background: transparent; }
|
|
23
|
+
html::-webkit-scrollbar-thumb {
|
|
24
|
+
background: var(--border-strong);
|
|
25
|
+
border: 2px solid var(--bg);
|
|
26
|
+
border-radius: 5px;
|
|
27
|
+
background-clip: content-box;
|
|
28
|
+
}
|
|
29
|
+
html::-webkit-scrollbar-thumb:hover { background: var(--text-3); background-clip: content-box; }
|
|
30
|
+
|
|
26
31
|
body {
|
|
27
|
-
font-family: -
|
|
32
|
+
font-family: var(--font-sans);
|
|
28
33
|
background: var(--bg);
|
|
29
34
|
color: var(--text);
|
|
30
|
-
padding: 24px;
|
|
31
35
|
min-height: 100vh;
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
font-size: 14px;
|
|
37
|
+
line-height: 1.5;
|
|
38
|
+
-webkit-font-smoothing: antialiased;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
42
|
+
\u9876\u680F
|
|
43
|
+
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
|
|
44
|
+
.topbar {
|
|
45
|
+
position: sticky;
|
|
46
|
+
top: 0;
|
|
47
|
+
z-index: 50;
|
|
48
|
+
height: var(--topbar-h);
|
|
34
49
|
display: flex;
|
|
35
50
|
align-items: center;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
51
|
+
gap: 16px;
|
|
52
|
+
padding: 0 24px;
|
|
53
|
+
background: color-mix(in srgb, var(--bg) 82%, transparent);
|
|
54
|
+
backdrop-filter: blur(12px);
|
|
55
|
+
border-bottom: 1px solid var(--border);
|
|
40
56
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
font-weight: 600;
|
|
57
|
+
|
|
58
|
+
.brand {
|
|
44
59
|
display: flex;
|
|
45
60
|
align-items: center;
|
|
46
61
|
gap: 10px;
|
|
62
|
+
font-size: 15px;
|
|
63
|
+
font-weight: 650;
|
|
64
|
+
letter-spacing: -0.01em;
|
|
65
|
+
white-space: nowrap;
|
|
47
66
|
}
|
|
48
|
-
.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
border-radius:
|
|
52
|
-
|
|
53
|
-
|
|
67
|
+
.brand-mark {
|
|
68
|
+
width: 22px;
|
|
69
|
+
height: 22px;
|
|
70
|
+
border-radius: 6px;
|
|
71
|
+
object-fit: contain;
|
|
72
|
+
flex-shrink: 0;
|
|
54
73
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
74
|
+
|
|
75
|
+
.topbar-meta {
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
gap: 8px;
|
|
79
|
+
font-size: 12px;
|
|
80
|
+
color: var(--text-3);
|
|
81
|
+
min-width: 0;
|
|
58
82
|
}
|
|
59
|
-
.
|
|
83
|
+
.meta-sep { opacity: 0.4; }
|
|
84
|
+
.meta-host {
|
|
85
|
+
font-family: var(--font-mono);
|
|
86
|
+
font-size: 11.5px;
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
text-overflow: ellipsis;
|
|
89
|
+
white-space: nowrap;
|
|
90
|
+
max-width: 180px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.topbar-spacer { flex: 1; }
|
|
94
|
+
|
|
95
|
+
.topbar-actions {
|
|
60
96
|
display: flex;
|
|
61
97
|
align-items: center;
|
|
62
|
-
gap:
|
|
98
|
+
gap: 6px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.version-area {
|
|
102
|
+
display: inline-flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
gap: 6px;
|
|
105
|
+
font-size: 12px;
|
|
106
|
+
color: var(--text-2);
|
|
107
|
+
font-variant-numeric: tabular-nums;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.icon-btn {
|
|
111
|
+
display: inline-flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
justify-content: center;
|
|
114
|
+
gap: 6px;
|
|
115
|
+
min-width: 32px;
|
|
116
|
+
height: 32px;
|
|
117
|
+
padding: 0 10px;
|
|
118
|
+
background: transparent;
|
|
119
|
+
border: 1px solid transparent;
|
|
120
|
+
border-radius: var(--radius-sm);
|
|
121
|
+
color: var(--text-2);
|
|
63
122
|
font-size: 13px;
|
|
64
|
-
|
|
123
|
+
font-family: inherit;
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
transition: background .15s, color .15s, border-color .15s;
|
|
65
126
|
}
|
|
66
|
-
.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
border: 1px solid var(--border);
|
|
127
|
+
.icon-btn:hover {
|
|
128
|
+
background: var(--surface-2);
|
|
129
|
+
border-color: var(--border);
|
|
70
130
|
color: var(--text);
|
|
71
|
-
padding: 6px 14px;
|
|
72
|
-
border-radius: 6px;
|
|
73
|
-
cursor: pointer;
|
|
74
|
-
font-size: 13px;
|
|
75
|
-
transition: background .15s;
|
|
76
131
|
}
|
|
77
|
-
.
|
|
78
|
-
|
|
132
|
+
.icon-btn:focus-visible {
|
|
133
|
+
outline: 2px solid var(--accent);
|
|
134
|
+
outline-offset: 1px;
|
|
79
135
|
}
|
|
80
|
-
.
|
|
81
|
-
background: var(--
|
|
82
|
-
border-color: var(--
|
|
136
|
+
.icon-btn.solid {
|
|
137
|
+
background: var(--surface-2);
|
|
138
|
+
border-color: var(--border);
|
|
139
|
+
color: var(--text);
|
|
83
140
|
}
|
|
141
|
+
.icon-btn.solid:hover { background: var(--surface-3); }
|
|
84
142
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
143
|
+
/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
144
|
+
\u4E3B\u5185\u5BB9\u533A
|
|
145
|
+
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
|
|
146
|
+
.page {
|
|
147
|
+
max-width: 1320px;
|
|
148
|
+
margin: 0 auto;
|
|
149
|
+
padding: 28px 24px 64px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.page-head {
|
|
153
|
+
display: flex;
|
|
154
|
+
align-items: flex-end;
|
|
155
|
+
justify-content: space-between;
|
|
88
156
|
gap: 16px;
|
|
89
157
|
margin-bottom: 20px;
|
|
158
|
+
flex-wrap: wrap;
|
|
90
159
|
}
|
|
91
|
-
.
|
|
160
|
+
.page-title {
|
|
161
|
+
font-size: 24px;
|
|
162
|
+
font-weight: 680;
|
|
163
|
+
letter-spacing: -0.02em;
|
|
164
|
+
line-height: 1.2;
|
|
165
|
+
}
|
|
166
|
+
.page-sub {
|
|
167
|
+
font-size: 13px;
|
|
168
|
+
color: var(--text-2);
|
|
169
|
+
margin-top: 4px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
173
|
+
\u5065\u5EB7\u72B6\u6001\u603B\u89C8\uFF08hero\uFF09
|
|
174
|
+
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
|
|
175
|
+
.hero {
|
|
92
176
|
background: var(--surface);
|
|
93
177
|
border: 1px solid var(--border);
|
|
94
|
-
border-radius: var(--radius);
|
|
95
|
-
|
|
178
|
+
border-radius: var(--radius-lg);
|
|
179
|
+
box-shadow: var(--shadow-1);
|
|
180
|
+
overflow: hidden;
|
|
181
|
+
margin-bottom: 20px;
|
|
96
182
|
}
|
|
97
|
-
|
|
98
|
-
|
|
183
|
+
|
|
184
|
+
.hero-body {
|
|
185
|
+
display: grid;
|
|
186
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
187
|
+
gap: 1px;
|
|
188
|
+
background: var(--border);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.hero-cell {
|
|
192
|
+
background: var(--surface);
|
|
193
|
+
padding: 18px 20px;
|
|
194
|
+
min-width: 0;
|
|
195
|
+
}
|
|
196
|
+
.hero-cell-label {
|
|
197
|
+
display: flex;
|
|
198
|
+
align-items: center;
|
|
199
|
+
gap: 6px;
|
|
200
|
+
font-size: 11px;
|
|
99
201
|
font-weight: 600;
|
|
202
|
+
letter-spacing: 0.06em;
|
|
100
203
|
text-transform: uppercase;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
204
|
+
color: var(--text-3);
|
|
205
|
+
margin-bottom: 8px;
|
|
206
|
+
}
|
|
207
|
+
.hero-cell-value {
|
|
104
208
|
display: flex;
|
|
105
209
|
align-items: center;
|
|
106
210
|
gap: 8px;
|
|
211
|
+
font-size: 17px;
|
|
212
|
+
font-weight: 620;
|
|
213
|
+
letter-spacing: -0.01em;
|
|
214
|
+
line-height: 1.3;
|
|
215
|
+
min-width: 0;
|
|
216
|
+
}
|
|
217
|
+
.hero-cell-value > span:last-child {
|
|
218
|
+
overflow: hidden;
|
|
219
|
+
text-overflow: ellipsis;
|
|
220
|
+
white-space: nowrap;
|
|
221
|
+
}
|
|
222
|
+
.hero-cell-detail {
|
|
223
|
+
margin-top: 6px;
|
|
224
|
+
font-size: 12px;
|
|
225
|
+
color: var(--text-3);
|
|
226
|
+
font-family: var(--font-mono);
|
|
227
|
+
overflow: hidden;
|
|
228
|
+
text-overflow: ellipsis;
|
|
229
|
+
white-space: nowrap;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* \u72B6\u6001\u70B9 */
|
|
233
|
+
.dot {
|
|
234
|
+
width: 8px;
|
|
235
|
+
height: 8px;
|
|
236
|
+
border-radius: 50%;
|
|
237
|
+
flex-shrink: 0;
|
|
238
|
+
background: var(--text-3);
|
|
239
|
+
}
|
|
240
|
+
.dot.ok { background: var(--ok); box-shadow: 0 0 0 3px var(--ok-soft); }
|
|
241
|
+
.dot.warn { background: var(--warn); box-shadow: 0 0 0 3px var(--warn-soft); }
|
|
242
|
+
.dot.danger { background: var(--danger); box-shadow: 0 0 0 3px var(--danger-soft); }
|
|
243
|
+
.dot.pulse { animation: pulse 2.4s ease-in-out infinite; }
|
|
244
|
+
@keyframes pulse {
|
|
245
|
+
0%, 100% { opacity: 1; }
|
|
246
|
+
50% { opacity: 0.35; }
|
|
107
247
|
}
|
|
108
248
|
|
|
109
|
-
|
|
249
|
+
/* \u9700\u8981\u5904\u7406\u7684\u4E8B\u9879 */
|
|
250
|
+
.attention {
|
|
110
251
|
display: flex;
|
|
111
|
-
justify-content: space-between;
|
|
112
252
|
align-items: center;
|
|
113
|
-
|
|
253
|
+
gap: 12px;
|
|
254
|
+
padding: 14px 20px;
|
|
255
|
+
background: var(--warn-soft);
|
|
256
|
+
border-top: 1px solid color-mix(in srgb, var(--warn) 30%, transparent);
|
|
114
257
|
font-size: 13px;
|
|
115
|
-
border-bottom: 1px solid var(--border);
|
|
116
258
|
}
|
|
117
|
-
.
|
|
118
|
-
.
|
|
119
|
-
|
|
259
|
+
.attention[hidden] { display: none; }
|
|
260
|
+
.attention-icon {
|
|
261
|
+
width: 20px;
|
|
262
|
+
height: 20px;
|
|
263
|
+
border-radius: 50%;
|
|
264
|
+
background: var(--warn);
|
|
265
|
+
color: #0b0d13;
|
|
266
|
+
display: grid;
|
|
267
|
+
place-items: center;
|
|
268
|
+
font-size: 12px;
|
|
269
|
+
font-weight: 800;
|
|
270
|
+
flex-shrink: 0;
|
|
271
|
+
}
|
|
272
|
+
.attention-text { flex: 1; min-width: 0; color: var(--text); }
|
|
273
|
+
.attention-text b { font-weight: 650; }
|
|
120
274
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
.
|
|
275
|
+
/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
276
|
+
KPI \u78C1\u8D34
|
|
277
|
+
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
|
|
278
|
+
.kpi-grid {
|
|
279
|
+
display: grid;
|
|
280
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
281
|
+
gap: 12px;
|
|
282
|
+
margin-bottom: 28px;
|
|
283
|
+
}
|
|
284
|
+
.kpi {
|
|
285
|
+
background: var(--surface);
|
|
286
|
+
border: 1px solid var(--border);
|
|
287
|
+
border-radius: var(--radius);
|
|
288
|
+
padding: 16px 18px;
|
|
289
|
+
min-width: 0;
|
|
290
|
+
transition: border-color .15s;
|
|
291
|
+
}
|
|
292
|
+
.kpi:hover { border-color: var(--border-strong); }
|
|
293
|
+
.kpi-label {
|
|
294
|
+
font-size: 12px;
|
|
295
|
+
color: var(--text-2);
|
|
296
|
+
margin-bottom: 6px;
|
|
297
|
+
display: flex;
|
|
298
|
+
align-items: center;
|
|
299
|
+
gap: 6px;
|
|
300
|
+
}
|
|
301
|
+
.kpi-value {
|
|
302
|
+
font-size: 26px;
|
|
303
|
+
font-weight: 680;
|
|
304
|
+
letter-spacing: -0.03em;
|
|
305
|
+
font-variant-numeric: tabular-nums;
|
|
306
|
+
line-height: 1.1;
|
|
307
|
+
}
|
|
308
|
+
.kpi-value.muted { color: var(--text-3); }
|
|
309
|
+
.kpi-value.ok { color: var(--ok); }
|
|
310
|
+
.kpi-value.warn { color: var(--warn); }
|
|
311
|
+
.kpi-foot {
|
|
312
|
+
font-size: 11.5px;
|
|
313
|
+
color: var(--text-3);
|
|
314
|
+
margin-top: 4px;
|
|
315
|
+
overflow: hidden;
|
|
316
|
+
text-overflow: ellipsis;
|
|
317
|
+
white-space: nowrap;
|
|
318
|
+
}
|
|
125
319
|
|
|
320
|
+
/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
321
|
+
\u533A\u5757\u6807\u9898
|
|
322
|
+
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
|
|
323
|
+
.section {
|
|
324
|
+
margin-bottom: 28px;
|
|
325
|
+
}
|
|
326
|
+
.section-head {
|
|
327
|
+
display: flex;
|
|
328
|
+
align-items: center;
|
|
329
|
+
gap: 8px;
|
|
330
|
+
margin-bottom: 12px;
|
|
331
|
+
min-height: 24px;
|
|
332
|
+
}
|
|
126
333
|
.section-title {
|
|
127
334
|
font-size: 15px;
|
|
128
|
-
font-weight:
|
|
129
|
-
|
|
335
|
+
font-weight: 640;
|
|
336
|
+
letter-spacing: -0.01em;
|
|
337
|
+
}
|
|
338
|
+
.count {
|
|
339
|
+
font-size: 12px;
|
|
340
|
+
font-weight: 500;
|
|
341
|
+
color: var(--text-3);
|
|
342
|
+
font-variant-numeric: tabular-nums;
|
|
343
|
+
}
|
|
344
|
+
.section-head .spacer { flex: 1; }
|
|
345
|
+
|
|
346
|
+
.toggle-header {
|
|
347
|
+
cursor: pointer;
|
|
348
|
+
user-select: none;
|
|
349
|
+
display: inline-flex;
|
|
350
|
+
align-items: center;
|
|
351
|
+
gap: 6px;
|
|
352
|
+
border-radius: var(--radius-sm);
|
|
353
|
+
padding: 2px 4px;
|
|
354
|
+
margin-left: -4px;
|
|
355
|
+
transition: background .15s;
|
|
356
|
+
}
|
|
357
|
+
.toggle-header:hover { background: var(--surface-2); }
|
|
358
|
+
.toggle-icon {
|
|
359
|
+
display: inline-block;
|
|
360
|
+
font-size: 9px;
|
|
361
|
+
color: var(--text-3);
|
|
362
|
+
transition: transform .2s;
|
|
363
|
+
}
|
|
364
|
+
.toggle-icon.open { transform: rotate(90deg); }
|
|
365
|
+
|
|
366
|
+
/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
367
|
+
\u5361\u7247
|
|
368
|
+
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
|
|
369
|
+
.card {
|
|
370
|
+
background: var(--surface);
|
|
371
|
+
border: 1px solid var(--border);
|
|
372
|
+
border-radius: var(--radius);
|
|
373
|
+
box-shadow: var(--shadow-1);
|
|
374
|
+
overflow: hidden;
|
|
375
|
+
}
|
|
376
|
+
.card-head {
|
|
130
377
|
display: flex;
|
|
131
378
|
align-items: center;
|
|
132
379
|
gap: 8px;
|
|
380
|
+
padding: 14px 18px;
|
|
381
|
+
border-bottom: 1px solid var(--border);
|
|
382
|
+
}
|
|
383
|
+
.card-title {
|
|
384
|
+
font-size: 13px;
|
|
385
|
+
font-weight: 620;
|
|
386
|
+
letter-spacing: -0.01em;
|
|
387
|
+
}
|
|
388
|
+
.card-title .unit {
|
|
389
|
+
font-size: 11.5px;
|
|
390
|
+
font-weight: 500;
|
|
391
|
+
color: var(--text-3);
|
|
392
|
+
margin-left: 6px;
|
|
393
|
+
font-family: var(--font-mono);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/* \u8D44\u6E90\u4EEA\u8868 */
|
|
397
|
+
.gauges {
|
|
398
|
+
display: grid;
|
|
399
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
400
|
+
gap: 1px;
|
|
401
|
+
background: var(--border);
|
|
402
|
+
}
|
|
403
|
+
.gauge {
|
|
404
|
+
background: var(--surface);
|
|
405
|
+
padding: 16px 18px;
|
|
406
|
+
min-width: 0;
|
|
407
|
+
}
|
|
408
|
+
.gauge-head {
|
|
409
|
+
display: flex;
|
|
410
|
+
align-items: baseline;
|
|
411
|
+
justify-content: space-between;
|
|
412
|
+
gap: 8px;
|
|
413
|
+
margin-bottom: 10px;
|
|
414
|
+
}
|
|
415
|
+
.gauge-name {
|
|
416
|
+
font-size: 11px;
|
|
417
|
+
font-weight: 620;
|
|
418
|
+
letter-spacing: 0.06em;
|
|
419
|
+
text-transform: uppercase;
|
|
420
|
+
color: var(--text-3);
|
|
421
|
+
}
|
|
422
|
+
.gauge-reading {
|
|
423
|
+
font-size: 19px;
|
|
424
|
+
font-weight: 680;
|
|
425
|
+
letter-spacing: -0.02em;
|
|
426
|
+
font-variant-numeric: tabular-nums;
|
|
427
|
+
line-height: 1;
|
|
428
|
+
}
|
|
429
|
+
.gauge-reading.muted { font-size: 15px; color: var(--text-2); font-weight: 600; }
|
|
430
|
+
|
|
431
|
+
.bar {
|
|
432
|
+
height: 5px;
|
|
433
|
+
background: var(--surface-3);
|
|
434
|
+
border-radius: 3px;
|
|
435
|
+
overflow: hidden;
|
|
436
|
+
margin-bottom: 12px;
|
|
437
|
+
}
|
|
438
|
+
.bar-placeholder { background: transparent; }
|
|
439
|
+
.bar-fill {
|
|
440
|
+
height: 100%;
|
|
441
|
+
border-radius: 3px;
|
|
442
|
+
transition: width .45s cubic-bezier(.4, 0, .2, 1);
|
|
133
443
|
}
|
|
134
|
-
.
|
|
444
|
+
.bar-fill.green { background: var(--ok); }
|
|
445
|
+
.bar-fill.orange { background: var(--warn); }
|
|
446
|
+
.bar-fill.red { background: var(--danger); }
|
|
447
|
+
|
|
448
|
+
.gauge-lines {
|
|
449
|
+
display: flex;
|
|
450
|
+
flex-direction: column;
|
|
451
|
+
gap: 5px;
|
|
452
|
+
}
|
|
453
|
+
.gauge-line {
|
|
454
|
+
display: flex;
|
|
455
|
+
justify-content: space-between;
|
|
456
|
+
gap: 10px;
|
|
135
457
|
font-size: 12px;
|
|
136
|
-
|
|
137
|
-
|
|
458
|
+
min-width: 0;
|
|
459
|
+
}
|
|
460
|
+
.gauge-line dt { color: var(--text-3); flex-shrink: 0; }
|
|
461
|
+
.gauge-line dd {
|
|
462
|
+
color: var(--text-2);
|
|
463
|
+
font-variant-numeric: tabular-nums;
|
|
464
|
+
text-align: right;
|
|
465
|
+
overflow: hidden;
|
|
466
|
+
text-overflow: ellipsis;
|
|
467
|
+
white-space: nowrap;
|
|
138
468
|
}
|
|
139
469
|
|
|
470
|
+
/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
471
|
+
\u8868\u683C
|
|
472
|
+
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
|
|
140
473
|
.table-wrap {
|
|
141
474
|
background: var(--surface);
|
|
142
475
|
border: 1px solid var(--border);
|
|
143
476
|
border-radius: var(--radius);
|
|
477
|
+
box-shadow: var(--shadow-1);
|
|
144
478
|
overflow-x: auto;
|
|
145
479
|
}
|
|
146
480
|
table {
|
|
@@ -148,251 +482,368 @@ import{execFile as X}from"node:child_process";import v from"node:fs";import k fr
|
|
|
148
482
|
border-collapse: collapse;
|
|
149
483
|
font-size: 13px;
|
|
150
484
|
}
|
|
151
|
-
th {
|
|
485
|
+
thead th {
|
|
152
486
|
text-align: left;
|
|
153
|
-
padding: 10px
|
|
487
|
+
padding: 10px 16px;
|
|
154
488
|
font-size: 11px;
|
|
155
|
-
font-weight:
|
|
489
|
+
font-weight: 620;
|
|
490
|
+
letter-spacing: 0.05em;
|
|
156
491
|
text-transform: uppercase;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
background: var(--surface2);
|
|
492
|
+
color: var(--text-3);
|
|
493
|
+
background: var(--bg-elevated);
|
|
160
494
|
border-bottom: 1px solid var(--border);
|
|
161
495
|
white-space: nowrap;
|
|
496
|
+
position: sticky;
|
|
497
|
+
top: 0;
|
|
498
|
+
z-index: 1;
|
|
162
499
|
}
|
|
163
|
-
td {
|
|
164
|
-
padding:
|
|
500
|
+
tbody td {
|
|
501
|
+
padding: 11px 16px;
|
|
165
502
|
border-bottom: 1px solid var(--border);
|
|
166
503
|
white-space: nowrap;
|
|
504
|
+
color: var(--text-2);
|
|
505
|
+
}
|
|
506
|
+
tbody tr:last-child td { border-bottom: none; }
|
|
507
|
+
tbody tr { transition: background .12s; }
|
|
508
|
+
tbody tr:hover { background: var(--surface-2); }
|
|
509
|
+
tbody td.strong { color: var(--text); font-weight: 550; }
|
|
510
|
+
code, .mono {
|
|
511
|
+
font-family: var(--font-mono);
|
|
512
|
+
font-size: 12px;
|
|
513
|
+
color: var(--text);
|
|
167
514
|
}
|
|
168
|
-
tr:last-child td { border-bottom: none; }
|
|
169
|
-
tr:hover { background: rgba(255,255,255,0.03); }
|
|
170
515
|
|
|
171
|
-
|
|
516
|
+
/* \u7A7A\u72B6\u6001 */
|
|
517
|
+
.empty {
|
|
172
518
|
display: flex;
|
|
519
|
+
flex-direction: column;
|
|
173
520
|
align-items: center;
|
|
174
|
-
gap:
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
background: var(--surface2);
|
|
180
|
-
border-radius: 3px;
|
|
181
|
-
overflow: hidden;
|
|
182
|
-
}
|
|
183
|
-
.bar-fill {
|
|
184
|
-
height: 100%;
|
|
185
|
-
border-radius: 3px;
|
|
186
|
-
transition: width 0.5s ease;
|
|
521
|
+
gap: 6px;
|
|
522
|
+
padding: 40px 24px;
|
|
523
|
+
text-align: center;
|
|
524
|
+
color: var(--text-3);
|
|
525
|
+
font-size: 13px;
|
|
187
526
|
}
|
|
188
|
-
.
|
|
189
|
-
.
|
|
190
|
-
.bar-fill.red { background: var(--red); }
|
|
191
|
-
.bar-label { font-size: 12px; min-width: 48px; text-align: right; font-variant-numeric: tabular-nums; }
|
|
527
|
+
.empty-icon { font-size: 22px; opacity: 0.5; line-height: 1; }
|
|
528
|
+
.empty-hint { font-size: 12px; color: var(--text-3); opacity: 0.8; }
|
|
192
529
|
|
|
530
|
+
/* \u6807\u7B7E */
|
|
193
531
|
.tag {
|
|
194
|
-
display: inline-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
532
|
+
display: inline-flex;
|
|
533
|
+
align-items: center;
|
|
534
|
+
gap: 5px;
|
|
535
|
+
padding: 2px 8px;
|
|
536
|
+
border-radius: 20px;
|
|
537
|
+
font-size: 11.5px;
|
|
198
538
|
font-weight: 600;
|
|
539
|
+
white-space: nowrap;
|
|
199
540
|
}
|
|
200
|
-
.tag
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
.
|
|
209
|
-
.
|
|
210
|
-
|
|
541
|
+
.tag::before {
|
|
542
|
+
content: "";
|
|
543
|
+
width: 5px;
|
|
544
|
+
height: 5px;
|
|
545
|
+
border-radius: 50%;
|
|
546
|
+
background: currentColor;
|
|
547
|
+
flex-shrink: 0;
|
|
548
|
+
}
|
|
549
|
+
.tag.managed, .tag.green { background: var(--ok-soft); color: var(--ok); }
|
|
550
|
+
.tag.orphan, .tag.orange { background: var(--warn-soft); color: var(--warn); }
|
|
551
|
+
.tag.unknown, .tag.gray { background: var(--surface-3); color: var(--text-2); }
|
|
552
|
+
.tag.red { background: var(--danger-soft); color: var(--danger); }
|
|
553
|
+
.tag.blue { background: var(--info-soft); color: var(--info); }
|
|
554
|
+
.tag.plain::before { display: none; }
|
|
555
|
+
|
|
556
|
+
/* \u72B6\u6001\u6587\u5B57 */
|
|
557
|
+
.status-green { color: var(--ok); font-weight: 550; }
|
|
558
|
+
.status-orange { color: var(--warn); font-weight: 550; }
|
|
559
|
+
.status-red { color: var(--danger); font-weight: 550; }
|
|
560
|
+
.status-muted { color: var(--text-3); }
|
|
561
|
+
|
|
562
|
+
/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
563
|
+
\u5B64\u513F\u8FDB\u7A0B\u544A\u8B66
|
|
564
|
+
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
|
|
211
565
|
.orphan-banner {
|
|
212
|
-
background: var(--orange-bg);
|
|
213
|
-
border: 1px solid var(--orange);
|
|
214
|
-
border-radius: var(--radius);
|
|
215
|
-
padding: 12px 16px;
|
|
216
|
-
margin-bottom: 12px;
|
|
217
|
-
font-size: 13px;
|
|
218
566
|
display: flex;
|
|
219
567
|
align-items: center;
|
|
220
568
|
gap: 10px;
|
|
569
|
+
padding: 12px 16px;
|
|
570
|
+
margin-bottom: 10px;
|
|
571
|
+
background: var(--warn-soft);
|
|
572
|
+
border: 1px solid color-mix(in srgb, var(--warn) 32%, transparent);
|
|
573
|
+
border-radius: var(--radius);
|
|
574
|
+
font-size: 13px;
|
|
575
|
+
}
|
|
576
|
+
.orphan-banner .icon { font-size: 15px; flex-shrink: 0; }
|
|
577
|
+
.orphan-banner .text { flex: 1; min-width: 0; }
|
|
578
|
+
.orphan-banner .action-hint {
|
|
579
|
+
color: var(--warn);
|
|
580
|
+
font-weight: 620;
|
|
581
|
+
font-size: 12px;
|
|
582
|
+
white-space: nowrap;
|
|
221
583
|
}
|
|
222
|
-
.orphan-banner .icon { font-size: 16px; }
|
|
223
|
-
.orphan-banner .text { flex: 1; }
|
|
224
|
-
.orphan-banner .action-hint { color: var(--orange); font-weight: 600; }
|
|
225
584
|
|
|
226
|
-
|
|
227
|
-
|
|
585
|
+
/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
586
|
+
\u6309\u94AE
|
|
587
|
+
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
|
|
588
|
+
.btn {
|
|
589
|
+
display: inline-flex;
|
|
590
|
+
align-items: center;
|
|
591
|
+
justify-content: center;
|
|
592
|
+
gap: 6px;
|
|
593
|
+
padding: 7px 14px;
|
|
594
|
+
border-radius: var(--radius-sm);
|
|
228
595
|
border: 1px solid var(--border);
|
|
596
|
+
background: var(--surface-2);
|
|
229
597
|
color: var(--text);
|
|
230
|
-
|
|
231
|
-
|
|
598
|
+
font-family: inherit;
|
|
599
|
+
font-size: 12.5px;
|
|
600
|
+
font-weight: 550;
|
|
232
601
|
cursor: pointer;
|
|
233
|
-
|
|
234
|
-
|
|
602
|
+
transition: background .15s, border-color .15s, color .15s, opacity .15s;
|
|
603
|
+
white-space: nowrap;
|
|
235
604
|
}
|
|
236
|
-
.
|
|
237
|
-
.
|
|
238
|
-
.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
align-items: center;
|
|
244
|
-
margin-bottom: 12px;
|
|
245
|
-
font-size: 13px;
|
|
246
|
-
flex-wrap: wrap;
|
|
605
|
+
.btn:hover { background: var(--surface-3); border-color: var(--border-strong); }
|
|
606
|
+
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
607
|
+
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
608
|
+
.btn-primary {
|
|
609
|
+
background: var(--accent);
|
|
610
|
+
border-color: var(--accent);
|
|
611
|
+
color: #fff;
|
|
247
612
|
}
|
|
248
|
-
.
|
|
249
|
-
.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
613
|
+
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
|
|
614
|
+
.btn-ghost { background: transparent; border-color: transparent; color: var(--text-2); }
|
|
615
|
+
.btn-ghost:hover { background: var(--surface-2); color: var(--text); border-color: transparent; }
|
|
616
|
+
.btn-danger { border-color: color-mix(in srgb, var(--danger) 45%, transparent); color: var(--danger); background: transparent; }
|
|
617
|
+
.btn-danger:hover { background: var(--danger-soft); border-color: var(--danger); }
|
|
618
|
+
.btn-sm { padding: 3px 10px; font-size: 11.5px; }
|
|
619
|
+
|
|
620
|
+
.btn-kill {
|
|
621
|
+
padding: 3px 10px;
|
|
622
|
+
border-radius: var(--radius-sm);
|
|
623
|
+
font-size: 11.5px;
|
|
624
|
+
font-weight: 600;
|
|
625
|
+
cursor: pointer;
|
|
626
|
+
border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent);
|
|
627
|
+
background: transparent;
|
|
628
|
+
color: var(--danger);
|
|
629
|
+
transition: background .15s, border-color .15s;
|
|
253
630
|
}
|
|
254
|
-
.
|
|
255
|
-
.
|
|
256
|
-
.summary-line .dot.gray { background: var(--text2); }
|
|
631
|
+
.btn-kill:hover { background: var(--danger-soft); border-color: var(--danger); }
|
|
632
|
+
.btn-kill:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
257
633
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
gap: 6px;
|
|
262
|
-
font-size: 13px;
|
|
263
|
-
color: var(--text2);
|
|
264
|
-
}
|
|
634
|
+
/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
635
|
+
\u66F4\u65B0\u6307\u793A\u5668
|
|
636
|
+
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
|
|
265
637
|
.update-indicator {
|
|
266
638
|
font-size: 12px;
|
|
267
639
|
cursor: pointer;
|
|
268
640
|
white-space: nowrap;
|
|
269
641
|
transition: color .15s;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
.update-indicator
|
|
274
|
-
.update-indicator.
|
|
275
|
-
.update-indicator.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
.s-log-list,
|
|
308
|
-
.s-table-wrap {
|
|
309
|
-
scrollbar-width: thin;
|
|
310
|
-
scrollbar-color: color-mix(in srgb, var(--accent) 30%, transparent) transparent;
|
|
311
|
-
}
|
|
312
|
-
.table-wrap::-webkit-scrollbar,
|
|
313
|
-
.settings-content::-webkit-scrollbar,
|
|
314
|
-
.s-log-list::-webkit-scrollbar,
|
|
315
|
-
.s-table-wrap::-webkit-scrollbar {
|
|
316
|
-
width: 6px;
|
|
317
|
-
height: 6px;
|
|
318
|
-
}
|
|
319
|
-
.table-wrap::-webkit-scrollbar-track,
|
|
320
|
-
.settings-content::-webkit-scrollbar-track,
|
|
321
|
-
.s-log-list::-webkit-scrollbar-track,
|
|
322
|
-
.s-table-wrap::-webkit-scrollbar-track {
|
|
323
|
-
background: transparent;
|
|
324
|
-
}
|
|
325
|
-
.table-wrap::-webkit-scrollbar-thumb,
|
|
326
|
-
.settings-content::-webkit-scrollbar-thumb,
|
|
327
|
-
.s-log-list::-webkit-scrollbar-thumb,
|
|
328
|
-
.s-table-wrap::-webkit-scrollbar-thumb {
|
|
329
|
-
background: color-mix(in srgb, var(--accent) 30%, transparent);
|
|
330
|
-
border-radius: 3px;
|
|
331
|
-
}
|
|
332
|
-
.table-wrap::-webkit-scrollbar-thumb:hover,
|
|
333
|
-
.settings-content::-webkit-scrollbar-thumb:hover,
|
|
334
|
-
.s-log-list::-webkit-scrollbar-thumb:hover,
|
|
335
|
-
.s-table-wrap::-webkit-scrollbar-thumb:hover {
|
|
336
|
-
background: color-mix(in srgb, var(--accent) 50%, transparent);
|
|
642
|
+
border-radius: var(--radius-sm);
|
|
643
|
+
padding: 2px 6px;
|
|
644
|
+
}
|
|
645
|
+
.update-indicator:hover { background: var(--surface-2); }
|
|
646
|
+
.update-indicator.available { color: var(--accent); font-weight: 600; }
|
|
647
|
+
.update-indicator.installing { color: var(--warn); cursor: default; }
|
|
648
|
+
.update-indicator.success { color: var(--ok); }
|
|
649
|
+
.update-indicator.failed { color: var(--danger); }
|
|
650
|
+
|
|
651
|
+
/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
652
|
+
\u54CD\u5E94\u5F0F
|
|
653
|
+
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
|
|
654
|
+
@media (max-width: 1080px) {
|
|
655
|
+
.gauges { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
@media (max-width: 860px) {
|
|
659
|
+
.hero-body { grid-template-columns: 1fr; }
|
|
660
|
+
.kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
661
|
+
.topbar { padding: 0 16px; gap: 10px; }
|
|
662
|
+
.topbar-meta { display: none; }
|
|
663
|
+
.page { padding: 20px 16px 48px; }
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
@media (max-width: 560px) {
|
|
667
|
+
:root { --topbar-h: 52px; }
|
|
668
|
+
body { font-size: 13.5px; }
|
|
669
|
+
.page { padding: 16px 12px 40px; }
|
|
670
|
+
.page-title { font-size: 19px; }
|
|
671
|
+
.kpi-grid { gap: 8px; }
|
|
672
|
+
.kpi { padding: 13px 14px; }
|
|
673
|
+
.kpi-value { font-size: 21px; }
|
|
674
|
+
.gauges { grid-template-columns: 1fr; }
|
|
675
|
+
.hero-cell { padding: 14px 16px; }
|
|
676
|
+
thead th, tbody td { padding: 9px 12px; }
|
|
677
|
+
.brand { font-size: 14px; }
|
|
678
|
+
.icon-btn { min-width: 30px; height: 30px; padding: 0 8px; }
|
|
337
679
|
}
|
|
338
680
|
</style>
|
|
339
681
|
</head>
|
|
340
682
|
<body>
|
|
341
683
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
<div class="
|
|
345
|
-
<
|
|
346
|
-
<span
|
|
684
|
+
<!-- \u2550\u2550 \u9876\u680F\uFF1A\u54C1\u724C + \u8FD0\u884C\u5143\u4FE1\u606F + \u5168\u5C40\u64CD\u4F5C \u2550\u2550 -->
|
|
685
|
+
<header class="topbar">
|
|
686
|
+
<div class="brand">
|
|
687
|
+
<img class="brand-mark" src="${Y}" alt="">
|
|
688
|
+
<span>AgentsWorkStudio</span>
|
|
689
|
+
</div>
|
|
690
|
+
<div class="topbar-meta">
|
|
691
|
+
<span class="meta-sep">/</span>
|
|
692
|
+
<span>Bridge</span>
|
|
693
|
+
<span class="meta-sep">\xB7</span>
|
|
694
|
+
<span class="meta-host" id="bridgeHostname">-</span>
|
|
695
|
+
</div>
|
|
696
|
+
<div class="topbar-spacer"></div>
|
|
697
|
+
<div class="topbar-actions">
|
|
347
698
|
<span class="version-area">
|
|
348
699
|
<span id="bridgeVersion">v-</span>
|
|
349
700
|
<span id="updateIndicator" style="display:none"></span>
|
|
350
701
|
</span>
|
|
351
|
-
<span class="
|
|
352
|
-
<
|
|
353
|
-
<button onclick="
|
|
354
|
-
<button onclick="refresh()" id="refreshBtn">↻</button>
|
|
702
|
+
<span class="icon-btn" id="themeBtn" onclick="toggleTheme()" title="\u5207\u6362\u4E3B\u9898">☽</span>
|
|
703
|
+
<span class="icon-btn" id="langBtn" onclick="toggleLang()" title="Switch language">\u4E2D\u6587</span>
|
|
704
|
+
<button class="icon-btn solid" onclick="openSettings()" id="settingsBtn" title="Settings / \u8BBE\u7F6E">⚙</button>
|
|
705
|
+
<button class="icon-btn" onclick="refresh()" id="refreshBtn" title="Refresh / \u5237\u65B0">↻</button>
|
|
706
|
+
<button class="icon-btn" onclick="doLogout()" id="logoutBtn" title="Logout / \u9000\u51FA\u767B\u5F55">⏻</button>
|
|
355
707
|
</div>
|
|
356
|
-
</
|
|
357
|
-
|
|
358
|
-
<div class="grid" id="statusCards"></div>
|
|
708
|
+
</header>
|
|
359
709
|
|
|
360
|
-
<
|
|
361
|
-
<div class="grid" id="metricsGrid"></div>
|
|
710
|
+
<main class="page">
|
|
362
711
|
|
|
363
|
-
<!--
|
|
364
|
-
<div class="
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
</div>
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
<div class="table-wrap" id="sessionTable"><div class="empty" id="loadingSessions">Loading...</div></div>
|
|
373
|
-
|
|
374
|
-
<!-- Orphan Processes (shown only when orphans exist) -->
|
|
375
|
-
<div id="orphanContainer" style="display:none">
|
|
376
|
-
<div class="section-title" id="sectionOrphans">Orphan Processes <span class="count" id="orphanCount"></span></div>
|
|
377
|
-
<div class="orphan-banner">
|
|
378
|
-
<span class="icon">⚠</span>
|
|
379
|
-
<span class="text" id="orphanBannerText">These processes are running but no longer managed by bridge</span>
|
|
380
|
-
<span class="action-hint">Terminate to clean up</span>
|
|
712
|
+
<!-- \u2550\u2550 \u9875\u9762\u6807\u9898 + \u5237\u65B0\u65F6\u95F4 \u2550\u2550 -->
|
|
713
|
+
<div class="page-head">
|
|
714
|
+
<div>
|
|
715
|
+
<div class="page-title" id="pageTitle">\u8FD0\u884C\u603B\u89C8</div>
|
|
716
|
+
<div class="page-sub" id="pageSub">\u672C\u673A Bridge \u72B6\u6001\u4E0E Agent \u4F1A\u8BDD</div>
|
|
717
|
+
</div>
|
|
718
|
+
<div class="page-sub">
|
|
719
|
+
<span id="refreshTime">-</span>
|
|
720
|
+
</div>
|
|
381
721
|
</div>
|
|
382
|
-
<div class="table-wrap" id="orphanTable"><div class="empty">No orphans</div></div>
|
|
383
|
-
</div>
|
|
384
722
|
|
|
385
|
-
<!--
|
|
386
|
-
<
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
723
|
+
<!-- \u2550\u2550 \u5065\u5EB7\u603B\u89C8\uFF1A\u4E09\u4E2A\u6700\u5173\u952E\u7684\u300C\u662F\u5426\u53EF\u7528\u300D\u5224\u65AD \u2550\u2550 -->
|
|
724
|
+
<section class="hero" aria-label="Bridge health">
|
|
725
|
+
<div class="hero-body">
|
|
726
|
+
<div class="hero-cell">
|
|
727
|
+
<div class="hero-cell-label" id="heroServiceLabel">Bridge \u670D\u52A1</div>
|
|
728
|
+
<div class="hero-cell-value">
|
|
729
|
+
<span class="dot ok pulse" id="heroServiceDot"></span>
|
|
730
|
+
<span id="heroServiceValue">\u8FD0\u884C\u4E2D</span>
|
|
731
|
+
</div>
|
|
732
|
+
<div class="hero-cell-detail" id="heroServiceDetail">-</div>
|
|
733
|
+
</div>
|
|
734
|
+
<div class="hero-cell">
|
|
735
|
+
<div class="hero-cell-label" id="heroRegLabel">\u8C03\u5EA6\u4E2D\u5FC3\u6CE8\u518C</div>
|
|
736
|
+
<div class="hero-cell-value">
|
|
737
|
+
<span class="dot" id="heroRegDot"></span>
|
|
738
|
+
<span id="heroRegValue">-</span>
|
|
739
|
+
</div>
|
|
740
|
+
<div class="hero-cell-detail" id="heroRegDetail">-</div>
|
|
741
|
+
</div>
|
|
742
|
+
<div class="hero-cell">
|
|
743
|
+
<div class="hero-cell-label" id="heroBindLabel">\u9762\u677F\u7ED1\u5B9A</div>
|
|
744
|
+
<div class="hero-cell-value">
|
|
745
|
+
<span class="dot" id="heroBindDot"></span>
|
|
746
|
+
<span id="heroBindValue">-</span>
|
|
747
|
+
</div>
|
|
748
|
+
<div class="hero-cell-detail" id="heroBindDetail">-</div>
|
|
749
|
+
</div>
|
|
750
|
+
</div>
|
|
751
|
+
<!-- \u9700\u8981\u7528\u6237\u5904\u7406\u7684\u4E8B\u9879\uFF1A\u9ED8\u8BA4\u9690\u85CF\uFF0C\u6709\u5185\u5BB9\u65F6\u624D\u663E\u793A -->
|
|
752
|
+
<div class="attention" id="attentionBar" hidden>
|
|
753
|
+
<span class="attention-icon">!</span>
|
|
754
|
+
<span class="attention-text" id="attentionText"></span>
|
|
755
|
+
<button class="btn btn-sm" id="attentionAction" style="display:none" onclick="runAttentionAction()"></button>
|
|
756
|
+
</div>
|
|
757
|
+
</section>
|
|
758
|
+
|
|
759
|
+
<!-- \u2550\u2550 KPI \u78C1\u8D34\uFF1A\u56DB\u4E2A\u6570\u5B57\uFF0C\u4E00\u773C\u770B\u5B8C \u2550\u2550 -->
|
|
760
|
+
<section class="kpi-grid" id="kpiGrid" aria-label="Key metrics">
|
|
761
|
+
<div class="kpi">
|
|
762
|
+
<div class="kpi-label" id="kpiActiveLabel">\u6D3B\u8DC3\u4F1A\u8BDD</div>
|
|
763
|
+
<div class="kpi-value muted" id="kpiActive">0</div>
|
|
764
|
+
<div class="kpi-foot" id="kpiActiveFoot">-</div>
|
|
765
|
+
</div>
|
|
766
|
+
<div class="kpi">
|
|
767
|
+
<div class="kpi-label" id="kpiPersistedLabel">\u6301\u4E45\u5316\u4F1A\u8BDD</div>
|
|
768
|
+
<div class="kpi-value" id="kpiPersisted">0</div>
|
|
769
|
+
<div class="kpi-foot" id="kpiPersistedFoot">-</div>
|
|
770
|
+
</div>
|
|
771
|
+
<div class="kpi">
|
|
772
|
+
<div class="kpi-label" id="kpiOrphanLabel">\u5B64\u513F\u8FDB\u7A0B</div>
|
|
773
|
+
<div class="kpi-value muted" id="kpiOrphan">0</div>
|
|
774
|
+
<div class="kpi-foot" id="kpiOrphanFoot">-</div>
|
|
775
|
+
</div>
|
|
776
|
+
<div class="kpi">
|
|
777
|
+
<div class="kpi-label" id="kpiUptimeLabel">\u8FD0\u884C\u65F6\u957F</div>
|
|
778
|
+
<div class="kpi-value" id="kpiUptime">-</div>
|
|
779
|
+
<div class="kpi-foot" id="kpiUptimeFoot">-</div>
|
|
780
|
+
</div>
|
|
781
|
+
</section>
|
|
782
|
+
|
|
783
|
+
<!-- \u2550\u2550 \u7CFB\u7EDF\u8D44\u6E90\uFF1A\u56DB\u4E2A\u4EEA\u8868\uFF0C\u6A2A\u5411\u5BF9\u9F50 \u2550\u2550 -->
|
|
784
|
+
<section class="section">
|
|
785
|
+
<div class="section-head">
|
|
786
|
+
<span class="section-title" id="sectionSysRes">\u7CFB\u7EDF\u8D44\u6E90</span>
|
|
787
|
+
<span class="spacer"></span>
|
|
788
|
+
<span class="count" id="sysMetricsCount"></span>
|
|
789
|
+
</div>
|
|
790
|
+
<div class="card" id="metricsCard">
|
|
791
|
+
<div class="gauges" id="metricsGrid"></div>
|
|
792
|
+
</div>
|
|
793
|
+
</section>
|
|
794
|
+
|
|
795
|
+
<!-- \u2550\u2550 \u6D3B\u8DC3\u4F1A\u8BDD \u2550\u2550 -->
|
|
796
|
+
<section class="section">
|
|
797
|
+
<div class="section-head">
|
|
798
|
+
<span class="section-title" id="sectionSessions">\u6D3B\u8DC3\u4F1A\u8BDD</span>
|
|
799
|
+
<span class="count" id="sessionCount"></span>
|
|
800
|
+
<span class="spacer"></span>
|
|
801
|
+
<!-- \u8FDB\u7A0B\u5206\u7C7B\u5C0F\u8BA1\uFF08\u539F summary-line\uFF0C\u6536\u8FDB\u6807\u9898\u884C\u53F3\u4FA7\uFF09 -->
|
|
802
|
+
<span class="count" id="summaryLine">
|
|
803
|
+
<span id="summaryManaged">0 managed</span> \xB7
|
|
804
|
+
<span id="summaryOrphan">0 orphan</span> \xB7
|
|
805
|
+
<span id="summaryUnknown">0 unknown</span>
|
|
806
|
+
</span>
|
|
807
|
+
</div>
|
|
808
|
+
<div class="table-wrap" id="sessionTable"><div class="empty" id="loadingSessions">Loading...</div></div>
|
|
809
|
+
</section>
|
|
810
|
+
|
|
811
|
+
<!-- \u2550\u2550 \u5B64\u513F\u8FDB\u7A0B\uFF08\u6709\u624D\u663E\u793A\uFF09 \u2550\u2550 -->
|
|
812
|
+
<section class="section" id="orphanContainer" style="display:none">
|
|
813
|
+
<div class="section-head">
|
|
814
|
+
<span class="section-title" id="sectionOrphans">\u5B64\u513F\u8FDB\u7A0B</span>
|
|
815
|
+
<span class="count" id="orphanCount"></span>
|
|
816
|
+
</div>
|
|
817
|
+
<div class="orphan-banner">
|
|
818
|
+
<span class="icon">⚠</span>
|
|
819
|
+
<span class="text" id="orphanBannerText">These processes are running but no longer managed by bridge</span>
|
|
820
|
+
<span class="action-hint" id="orphanActionHint">Terminate to clean up</span>
|
|
821
|
+
</div>
|
|
822
|
+
<div class="table-wrap" id="orphanTable"><div class="empty">No orphans</div></div>
|
|
823
|
+
</section>
|
|
824
|
+
|
|
825
|
+
<!-- \u2550\u2550 \u5176\u4ED6\u8FDB\u7A0B\uFF08\u9ED8\u8BA4\u6298\u53E0\uFF09 \u2550\u2550 -->
|
|
826
|
+
<section class="section" id="unknownContainer">
|
|
827
|
+
<div class="section-head">
|
|
828
|
+
<span class="section-title toggle-header" id="sectionUnknown" onclick="toggleUnknown()">
|
|
829
|
+
<span class="toggle-icon" id="unknownIcon">▶</span>
|
|
830
|
+
<span id="unknownLabel"> \u5176\u4ED6\u8FDB\u7A0B</span>
|
|
831
|
+
<span class="count" id="unknownCount"></span>
|
|
832
|
+
</span>
|
|
833
|
+
</div>
|
|
834
|
+
<div class="table-wrap" id="unknownTable" style="display:none"><div class="empty" id="loadingUnknown">Loading...</div></div>
|
|
835
|
+
</section>
|
|
836
|
+
|
|
837
|
+
<!-- \u2550\u2550 \u6301\u4E45\u5316\u4F1A\u8BDD \u2550\u2550 -->
|
|
838
|
+
<section class="section">
|
|
839
|
+
<div class="section-head">
|
|
840
|
+
<span class="section-title" id="sectionPersisted">\u6301\u4E45\u5316\u4F1A\u8BDD</span>
|
|
841
|
+
<span class="count" id="persistedCount"></span>
|
|
842
|
+
</div>
|
|
843
|
+
<div class="table-wrap" id="persistedTable"><div class="empty" id="loadingPersisted">Loading...</div></div>
|
|
844
|
+
</section>
|
|
392
845
|
|
|
393
|
-
|
|
394
|
-
<div class="section-title" id="sectionPersisted">Persisted Sessions <span class="count" id="persistedCount"></span></div>
|
|
395
|
-
<div class="table-wrap" id="persistedTable"><div class="empty" id="loadingPersisted">Loading...</div></div>
|
|
846
|
+
</main>
|
|
396
847
|
|
|
397
848
|
<!-- Settings Modal -->
|
|
398
849
|
<div class="modal-overlay" id="settingsModal" style="display:none" onclick="closeSettings(event)">
|
|
@@ -454,7 +905,7 @@ import{execFile as X}from"node:child_process";import v from"node:fs";import k fr
|
|
|
454
905
|
<div class="s-info-row"><span class="s-info-label" id="lblNodeActiveSessions"></span><span class="s-info-value" id="valNodeActiveSessions">-</span></div>
|
|
455
906
|
</div>
|
|
456
907
|
<div class="s-actions">
|
|
457
|
-
<button class="s-btn s-btn-secondary" onclick="refreshNodeInfo()"
|
|
908
|
+
<button class="s-btn s-btn-secondary s-btn-sm" id="btnRefreshNodeInfo" onclick="refreshNodeInfo()"></button>
|
|
458
909
|
</div>
|
|
459
910
|
</div>
|
|
460
911
|
<!-- Logs page -->
|
|
@@ -473,6 +924,8 @@ import{execFile as X}from"node:child_process";import v from"node:fs";import k fr
|
|
|
473
924
|
</div>
|
|
474
925
|
<!-- Scheduler page -->
|
|
475
926
|
<div class="settings-page" id="pageScheduler">
|
|
927
|
+
<!-- \u4ECE Dashboard\u300C\u5F15\u5BFC\u914D\u7F6E\u300D\u8DF3\u8F6C\u8FC7\u6765\u65F6\u7684\u5E26\u5165\u63D0\u793A\uFF08\u53EA\u56DE\u586B\u975E\u5BC6\u94A5\u5B57\u6BB5\uFF09 -->
|
|
928
|
+
<div class="s-msg info" id="schedPrefillNotice" style="display:none"></div>
|
|
476
929
|
<div class="s-field">
|
|
477
930
|
<label id="lblSchedTargets"></label>
|
|
478
931
|
<div class="s-target-list" id="schedTargetList"></div>
|
|
@@ -481,6 +934,13 @@ import{execFile as X}from"node:child_process";import v from"node:fs";import k fr
|
|
|
481
934
|
<button class="s-btn s-btn-secondary" id="btnAddTarget" onclick="startAddTarget()"></button>
|
|
482
935
|
</div>
|
|
483
936
|
<div class="s-target-form" id="targetForm">
|
|
937
|
+
<!-- \u5B9E\u4F8B\u540D\u79F0\uFF1A\u6700\u5E38\u6539\u7684\u53EF\u9009\u9879\uFF0C\u653E\u5230\u8868\u5355\u6700\u4E0A\u9762 -->
|
|
938
|
+
<div class="s-field">
|
|
939
|
+
<label id="lblSchedInstanceName"></label>
|
|
940
|
+
<input type="text" id="cfgInstanceName" autocomplete="off">
|
|
941
|
+
<span class="s-hint" id="hintSchedInstanceName"></span>
|
|
942
|
+
</div>
|
|
943
|
+
<!-- \u5FC5\u586B\uFF1A\u666E\u901A\u7528\u6237\u53EA\u9700\u8981\u586B\u8FD9\u4E24\u9879 -->
|
|
484
944
|
<div class="s-field">
|
|
485
945
|
<label id="lblSchedUrl"></label>
|
|
486
946
|
<input type="text" id="cfgServerUrl" placeholder="http://192.168.1.100:8080" autocomplete="off">
|
|
@@ -491,38 +951,43 @@ import{execFile as X}from"node:child_process";import v from"node:fs";import k fr
|
|
|
491
951
|
<input type="password" id="cfgUserKey" autocomplete="new-password">
|
|
492
952
|
<span class="s-hint" id="hintSchedUserKey"></span>
|
|
493
953
|
</div>
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
<
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
<
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
<
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
954
|
+
<!-- \u9AD8\u7EA7\u8BBE\u7F6E\uFF1A\u5168\u90E8\u53EF\u9009\u9879\uFF0C\u9ED8\u8BA4\u6536\u7EB3 -->
|
|
955
|
+
<div class="s-advanced" id="schedAdvanced">
|
|
956
|
+
<button type="button" class="s-advanced-head" id="schedAdvancedHead" onclick="toggleSchedAdvanced()" aria-expanded="false" aria-controls="schedAdvancedBody">
|
|
957
|
+
<span class="toggle-icon" id="schedAdvancedIcon">▶</span>
|
|
958
|
+
<span id="lblSchedAdvanced"></span>
|
|
959
|
+
<span class="s-advanced-count" id="schedAdvancedCount"></span>
|
|
960
|
+
</button>
|
|
961
|
+
<div class="s-advanced-body" id="schedAdvancedBody" hidden>
|
|
962
|
+
<div class="s-field">
|
|
963
|
+
<label id="lblSchedTenantId"></label>
|
|
964
|
+
<input type="text" id="cfgTenantId" autocomplete="off">
|
|
965
|
+
<span class="s-hint" id="hintSchedTenantId"></span>
|
|
966
|
+
</div>
|
|
967
|
+
<div class="s-field">
|
|
968
|
+
<label id="lblSchedConnKey"></label>
|
|
969
|
+
<input type="password" id="cfgConnectionKey" autocomplete="new-password">
|
|
970
|
+
<span class="s-hint" id="hintSchedConnKey"></span>
|
|
971
|
+
</div>
|
|
972
|
+
<div class="s-field">
|
|
973
|
+
<label id="lblSchedRegisterAddr"></label>
|
|
974
|
+
<input type="text" id="cfgRegisterAddr" placeholder="192.168.1.25:18081" autocomplete="off">
|
|
975
|
+
<span class="s-hint" id="hintSchedRegisterAddr"></span>
|
|
976
|
+
</div>
|
|
977
|
+
<div class="s-field">
|
|
978
|
+
<label id="lblSchedTunnel"></label>
|
|
979
|
+
<select id="cfgTunnel">
|
|
980
|
+
<option value="auto" id="optTunnelAuto"></option>
|
|
981
|
+
<option value="true" id="optTunnelOn"></option>
|
|
982
|
+
<option value="false" id="optTunnelOff"></option>
|
|
983
|
+
</select>
|
|
984
|
+
<span class="s-hint" id="hintSchedTunnel"></span>
|
|
985
|
+
</div>
|
|
986
|
+
<div class="s-field">
|
|
987
|
+
<div class="s-info-row"><span class="s-info-label" id="lblSchedInstanceId"></span><span class="s-info-value" id="valSchedInstanceId">-</span></div>
|
|
988
|
+
<span class="s-hint" id="hintSchedInstanceId"></span>
|
|
989
|
+
</div>
|
|
990
|
+
</div>
|
|
526
991
|
</div>
|
|
527
992
|
<div class="s-msg" id="schedMsg"></div>
|
|
528
993
|
<div class="s-actions s-actions-row">
|
|
@@ -533,10 +998,6 @@ import{execFile as X}from"node:child_process";import v from"node:fs";import k fr
|
|
|
533
998
|
<div class="s-field">
|
|
534
999
|
<p class="s-desc" id="descSched"></p>
|
|
535
1000
|
</div>
|
|
536
|
-
<div class="s-actions s-actions-row">
|
|
537
|
-
<button class="s-btn s-btn-danger" id="btnSaveRestart" onclick="saveServerConfigAndRestart()"></button>
|
|
538
|
-
<button class="s-btn s-btn-secondary" id="btnReRegister" onclick="doReRegister()"></button>
|
|
539
|
-
</div>
|
|
540
1001
|
</div>
|
|
541
1002
|
<!-- Access control page -->
|
|
542
1003
|
<div class="settings-page" id="pageAccess">
|
|
@@ -569,107 +1030,438 @@ import{execFile as X}from"node:child_process";import v from"node:fs";import k fr
|
|
|
569
1030
|
</div>
|
|
570
1031
|
</div>
|
|
571
1032
|
|
|
1033
|
+
<!-- \u4FDD\u5B58\u6210\u529F\u540E\u7684\u540E\u7EED\u64CD\u4F5C\u5F39\u6846\uFF1A\u91CD\u65B0\u6CE8\u518C\uFF08\u63A8\u8350\uFF09/ \u91CD\u542F Bridge / \u6682\u4E0D -->
|
|
1034
|
+
<div class="modal-overlay" id="afterSaveModal" style="display:none" onclick="closeAfterSave(event)">
|
|
1035
|
+
<div class="settings-panel aftersave-panel" onclick="event.stopPropagation()" role="dialog" aria-modal="true" aria-labelledby="aftersaveTitle">
|
|
1036
|
+
<div class="aftersave-icon" aria-hidden="true">
|
|
1037
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
|
|
1038
|
+
</div>
|
|
1039
|
+
<div class="aftersave-title" id="aftersaveTitle"></div>
|
|
1040
|
+
<p class="aftersave-desc" id="aftersaveDesc"></p>
|
|
1041
|
+
<div class="aftersave-actions">
|
|
1042
|
+
<button class="s-btn s-btn-primary aftersave-recommended" id="aftersaveBtnRegister" onclick="afterSaveAction('register')"></button>
|
|
1043
|
+
<button class="s-btn s-btn-danger" id="aftersaveBtnRestart" onclick="afterSaveAction('restart')"></button>
|
|
1044
|
+
<button type="button" class="aftersave-skip" id="aftersaveBtnSkip" onclick="afterSaveAction('skip')"></button>
|
|
1045
|
+
</div>
|
|
1046
|
+
</div>
|
|
1047
|
+
</div>
|
|
1048
|
+
|
|
572
1049
|
<style>
|
|
1050
|
+
/* \u2550\u2550 \u8BBE\u7F6E\u5F39\u7A97\uFF08\u590D\u7528\u4E3B\u6837\u5F0F\u8868\u7684\u4EE4\u724C\uFF09 \u2550\u2550 */
|
|
573
1051
|
.modal-overlay {
|
|
574
|
-
position: fixed;
|
|
575
|
-
background: rgba(0,0,0,0.
|
|
1052
|
+
position: fixed; inset: 0;
|
|
1053
|
+
background: rgba(0, 0, 0, 0.5);
|
|
1054
|
+
backdrop-filter: blur(3px);
|
|
1055
|
+
z-index: 1000;
|
|
576
1056
|
display: flex; align-items: center; justify-content: center;
|
|
577
1057
|
padding: 20px;
|
|
1058
|
+
animation: fadeIn .12s ease;
|
|
1059
|
+
}
|
|
1060
|
+
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
1061
|
+
|
|
1062
|
+
.settings-panel {
|
|
1063
|
+
background: var(--surface);
|
|
1064
|
+
border: 1px solid var(--border);
|
|
1065
|
+
border-radius: var(--radius-lg);
|
|
1066
|
+
box-shadow: var(--shadow-2);
|
|
1067
|
+
width: 720px; max-width: 100%;
|
|
1068
|
+
/* \u56FA\u5B9A\u9AD8\u5EA6\uFF1A\u5404\u8BBE\u7F6E\u9875\u5185\u5BB9\u591A\u5C11\u4E0D\u4E00\uFF0C\u82E5\u968F\u5185\u5BB9\u6491\u5F00\uFF0C\u5207\u6362\u9875\u7B7E\u65F6\u9762\u677F\u4F1A\u5FFD\u9AD8\u5FFD\u77EE\u3002
|
|
1069
|
+
\u5916\u6846\u6052\u5B9A 88vh\uFF0C\u957F\u5185\u5BB9\uFF08\u65E5\u5FD7\u3001\u8C03\u5EA6\u8868\u5355\uFF09\u7531 .settings-content \u5185\u90E8\u6EDA\u52A8\u6D88\u5316\u3002 */
|
|
1070
|
+
height: min(88vh, 720px);
|
|
1071
|
+
display: flex; flex-direction: column;
|
|
1072
|
+
overflow: hidden;
|
|
1073
|
+
animation: panelIn .16s cubic-bezier(.2, 0, .2, 1);
|
|
1074
|
+
}
|
|
1075
|
+
@keyframes panelIn {
|
|
1076
|
+
from { opacity: 0; transform: translateY(6px) scale(.99); }
|
|
1077
|
+
to { opacity: 1; transform: none; }
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
.settings-head {
|
|
1081
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
1082
|
+
padding: 15px 20px;
|
|
1083
|
+
border-bottom: 1px solid var(--border);
|
|
1084
|
+
flex-shrink: 0;
|
|
1085
|
+
}
|
|
1086
|
+
.settings-head span {
|
|
1087
|
+
font-size: 15px; font-weight: 650; letter-spacing: -0.01em;
|
|
1088
|
+
}
|
|
1089
|
+
.settings-close {
|
|
1090
|
+
background: none; border: none;
|
|
1091
|
+
color: var(--text-3);
|
|
1092
|
+
font-size: 20px; cursor: pointer;
|
|
1093
|
+
width: 28px; height: 28px;
|
|
1094
|
+
border-radius: var(--radius-sm);
|
|
1095
|
+
display: grid; place-items: center;
|
|
1096
|
+
line-height: 1;
|
|
1097
|
+
transition: background .15s, color .15s;
|
|
1098
|
+
}
|
|
1099
|
+
.settings-close:hover { background: var(--surface-2); color: var(--text); }
|
|
1100
|
+
|
|
1101
|
+
.settings-body { display: flex; flex: 1; min-height: 0; }
|
|
1102
|
+
|
|
1103
|
+
.settings-nav {
|
|
1104
|
+
width: 148px; flex-shrink: 0;
|
|
1105
|
+
border-right: 1px solid var(--border);
|
|
1106
|
+
padding: 10px 8px;
|
|
1107
|
+
display: flex; flex-direction: column; gap: 2px;
|
|
1108
|
+
overflow-y: auto;
|
|
1109
|
+
}
|
|
1110
|
+
.settings-nav-item {
|
|
1111
|
+
padding: 8px 12px;
|
|
1112
|
+
font-size: 13px;
|
|
1113
|
+
border-radius: var(--radius-sm);
|
|
1114
|
+
cursor: pointer;
|
|
1115
|
+
color: var(--text-2);
|
|
1116
|
+
transition: background .15s, color .15s;
|
|
1117
|
+
}
|
|
1118
|
+
.settings-nav-item:hover { color: var(--text); background: var(--surface-2); }
|
|
1119
|
+
.settings-nav-item.active {
|
|
1120
|
+
color: var(--accent);
|
|
1121
|
+
background: var(--accent-soft);
|
|
1122
|
+
font-weight: 600;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
.settings-content {
|
|
1126
|
+
flex: 1; padding: 22px 24px; overflow-y: auto; min-width: 0;
|
|
1127
|
+
}
|
|
1128
|
+
.settings-page { display: none; }
|
|
1129
|
+
.settings-page.active { display: block; }
|
|
1130
|
+
|
|
1131
|
+
/* \u2500\u2500 \u8868\u5355 \u2500\u2500 */
|
|
1132
|
+
.s-field { margin-bottom: 18px; }
|
|
1133
|
+
.s-field label {
|
|
1134
|
+
display: block; font-size: 12.5px; font-weight: 600;
|
|
1135
|
+
margin-bottom: 7px; color: var(--text-2);
|
|
1136
|
+
}
|
|
1137
|
+
.s-field input,
|
|
1138
|
+
.s-field select,
|
|
1139
|
+
.s-field textarea {
|
|
1140
|
+
width: 100%;
|
|
1141
|
+
padding: 9px 12px;
|
|
1142
|
+
background: var(--bg-elevated);
|
|
1143
|
+
border: 1px solid var(--border);
|
|
1144
|
+
border-radius: var(--radius-sm);
|
|
1145
|
+
color: var(--text);
|
|
1146
|
+
font-size: 13.5px;
|
|
1147
|
+
font-family: inherit;
|
|
1148
|
+
outline: none;
|
|
1149
|
+
transition: border-color .15s, box-shadow .15s;
|
|
1150
|
+
}
|
|
1151
|
+
.s-field input:focus,
|
|
1152
|
+
.s-field select:focus,
|
|
1153
|
+
.s-field textarea:focus {
|
|
1154
|
+
border-color: var(--accent);
|
|
1155
|
+
box-shadow: 0 0 0 3px var(--accent-soft);
|
|
1156
|
+
}
|
|
1157
|
+
.s-field input::placeholder { color: var(--text-3); }
|
|
1158
|
+
.s-field select { cursor: pointer; }
|
|
1159
|
+
.s-field select option { background: var(--bg-elevated); color: var(--text); }
|
|
1160
|
+
.s-field textarea {
|
|
1161
|
+
font-family: var(--font-mono);
|
|
1162
|
+
font-size: 12.5px;
|
|
1163
|
+
resize: vertical;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
.s-hint { display: block; font-size: 11.5px; color: var(--text-3); margin-top: 5px; line-height: 1.5; }
|
|
1167
|
+
.s-desc { font-size: 12.5px; color: var(--text-2); line-height: 1.65; margin: 0; }
|
|
1168
|
+
|
|
1169
|
+
.s-msg {
|
|
1170
|
+
padding: 9px 12px;
|
|
1171
|
+
border-radius: var(--radius-sm);
|
|
1172
|
+
font-size: 12.5px;
|
|
1173
|
+
margin-bottom: 14px;
|
|
1174
|
+
display: none;
|
|
1175
|
+
}
|
|
1176
|
+
.s-msg.error { display: block; background: var(--danger-soft); color: var(--danger); }
|
|
1177
|
+
.s-msg.success { display: block; background: var(--ok-soft); color: var(--ok); }
|
|
1178
|
+
.s-msg.info { display: block; background: var(--info-soft); color: var(--info); }
|
|
1179
|
+
|
|
1180
|
+
.s-actions { margin-top: 14px; }
|
|
1181
|
+
.s-actions-row { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
1182
|
+
.s-actions-col { display: flex; flex-direction: column; gap: 8px; }
|
|
1183
|
+
.s-btn-ghost { background: transparent; border-color: transparent; color: var(--text-2); }
|
|
1184
|
+
.s-btn-ghost:hover { background: var(--surface-2); color: var(--text); border-color: transparent; }
|
|
1185
|
+
|
|
1186
|
+
/* \u2500\u2500 \u4FDD\u5B58\u6210\u529F\u540E\u7684\u540E\u7EED\u64CD\u4F5C\u5F39\u6846 \u2500\u2500 */
|
|
1187
|
+
.settings-panel.aftersave-panel {
|
|
1188
|
+
width: 380px;
|
|
1189
|
+
/* \u8986\u76D6 .settings-panel \u7684\u56FA\u5B9A\u9AD8\u5EA6\uFF1A\u8FD9\u662F\u5185\u5BB9\u5F88\u5C11\u7684\u5C0F\u5F39\u6846\uFF0C
|
|
1190
|
+
\u82E5\u6CBF\u7528 88vh \u4F1A\u88AB\u6491\u51FA\u5927\u7247\u7A7A\u767D\uFF08\u7A84\u5C4F\u5A92\u4F53\u67E5\u8BE2\u540C\u6837\u8986\u76D6\uFF09\u3002 */
|
|
1191
|
+
height: auto;
|
|
1192
|
+
max-height: min(88vh, 720px);
|
|
1193
|
+
padding: 24px 24px 20px;
|
|
1194
|
+
}
|
|
1195
|
+
.aftersave-icon {
|
|
1196
|
+
width: 40px; height: 40px;
|
|
1197
|
+
margin: 0 auto 14px;
|
|
1198
|
+
border-radius: 50%;
|
|
1199
|
+
background: var(--ok-soft);
|
|
1200
|
+
color: var(--ok);
|
|
1201
|
+
display: grid; place-items: center;
|
|
1202
|
+
}
|
|
1203
|
+
.aftersave-icon svg { width: 20px; height: 20px; display: block; }
|
|
1204
|
+
.aftersave-title {
|
|
1205
|
+
font-size: 15px; font-weight: 650; letter-spacing: -0.01em;
|
|
1206
|
+
margin-bottom: 5px;
|
|
1207
|
+
text-align: center;
|
|
1208
|
+
}
|
|
1209
|
+
.aftersave-desc {
|
|
1210
|
+
font-size: 12.5px; color: var(--text-2); line-height: 1.6;
|
|
1211
|
+
margin: 0 0 18px;
|
|
1212
|
+
text-align: center;
|
|
1213
|
+
}
|
|
1214
|
+
.aftersave-actions { display: flex; flex-direction: column; gap: 8px; }
|
|
1215
|
+
.aftersave-skip {
|
|
1216
|
+
margin-top: 2px;
|
|
1217
|
+
background: none;
|
|
1218
|
+
border: none;
|
|
1219
|
+
color: var(--text-3);
|
|
1220
|
+
font-family: inherit;
|
|
1221
|
+
font-size: 12.5px;
|
|
1222
|
+
cursor: pointer;
|
|
1223
|
+
padding: 6px;
|
|
1224
|
+
border-radius: var(--radius-sm);
|
|
1225
|
+
transition: color .15s, background .15s;
|
|
1226
|
+
}
|
|
1227
|
+
.aftersave-skip:hover { color: var(--text); background: var(--surface-2); }
|
|
1228
|
+
.aftersave-recommended {
|
|
1229
|
+
position: relative;
|
|
1230
|
+
}
|
|
1231
|
+
.aftersave-recommended::after {
|
|
1232
|
+
content: attr(data-recommended);
|
|
1233
|
+
position: absolute;
|
|
1234
|
+
top: -8px; right: 10px;
|
|
1235
|
+
padding: 1px 7px;
|
|
1236
|
+
border-radius: 20px;
|
|
1237
|
+
background: var(--accent);
|
|
1238
|
+
color: #fff;
|
|
1239
|
+
font-size: 10px;
|
|
1240
|
+
font-weight: 600;
|
|
1241
|
+
letter-spacing: 0.02em;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
.s-btn {
|
|
1245
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
|
1246
|
+
padding: 8px 18px;
|
|
1247
|
+
border-radius: var(--radius-sm);
|
|
1248
|
+
font-size: 13px; font-weight: 600;
|
|
1249
|
+
font-family: inherit;
|
|
1250
|
+
cursor: pointer;
|
|
1251
|
+
transition: background .15s, border-color .15s, opacity .15s;
|
|
1252
|
+
border: 1px solid transparent;
|
|
1253
|
+
white-space: nowrap;
|
|
1254
|
+
}
|
|
1255
|
+
.s-btn-sm { padding: 5px 12px; font-size: 12px; }
|
|
1256
|
+
.s-btn-primary { background: var(--accent); color: #fff; }
|
|
1257
|
+
.s-btn-primary:hover { background: var(--accent-hover); }
|
|
1258
|
+
.s-btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1259
|
+
.s-btn-secondary {
|
|
1260
|
+
background: var(--surface-2); color: var(--text); border-color: var(--border);
|
|
1261
|
+
}
|
|
1262
|
+
.s-btn-secondary:hover { background: var(--surface-3); border-color: var(--border-strong); }
|
|
1263
|
+
.s-btn-danger {
|
|
1264
|
+
background: transparent; color: var(--danger);
|
|
1265
|
+
border-color: color-mix(in srgb, var(--danger) 45%, transparent);
|
|
1266
|
+
}
|
|
1267
|
+
.s-btn-danger:hover { background: var(--danger-soft); border-color: var(--danger); }
|
|
1268
|
+
.s-btn-danger:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1269
|
+
|
|
1270
|
+
/* \u2500\u2500 \u8C03\u5EA6\u4E2D\u5FC3\u76EE\u6807\u5217\u8868 \u2500\u2500 */
|
|
1271
|
+
.s-target-list { display: flex; flex-direction: column; gap: 8px; }
|
|
1272
|
+
.s-target-item {
|
|
1273
|
+
display: flex; align-items: center; justify-content: space-between; gap: 10px;
|
|
1274
|
+
padding: 11px 13px;
|
|
1275
|
+
background: var(--bg-elevated);
|
|
1276
|
+
border: 1px solid var(--border);
|
|
1277
|
+
border-radius: var(--radius-sm);
|
|
1278
|
+
cursor: pointer;
|
|
1279
|
+
transition: border-color .15s, background .15s;
|
|
1280
|
+
}
|
|
1281
|
+
.s-target-item:hover { border-color: var(--border-strong); }
|
|
1282
|
+
.s-target-item.active { border-color: var(--accent); background: var(--accent-soft); }
|
|
1283
|
+
.s-target-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
|
|
1284
|
+
.s-target-url {
|
|
1285
|
+
font-size: 13.5px; font-weight: 600; color: var(--text);
|
|
1286
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
1287
|
+
}
|
|
1288
|
+
.s-target-meta {
|
|
1289
|
+
font-size: 11.5px; color: var(--text-3);
|
|
1290
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
1291
|
+
}
|
|
1292
|
+
.s-target-actions { display: flex; gap: 6px; flex-shrink: 0; }
|
|
1293
|
+
.s-target-form {
|
|
1294
|
+
border: 1px solid var(--border);
|
|
1295
|
+
border-radius: var(--radius);
|
|
1296
|
+
padding: 18px;
|
|
1297
|
+
margin-bottom: 18px;
|
|
1298
|
+
background: var(--bg-elevated);
|
|
1299
|
+
}
|
|
1300
|
+
.s-target-empty {
|
|
1301
|
+
padding: 20px 14px;
|
|
1302
|
+
text-align: center;
|
|
1303
|
+
font-size: 12.5px;
|
|
1304
|
+
color: var(--text-3);
|
|
1305
|
+
border: 1px dashed var(--border-strong);
|
|
1306
|
+
border-radius: var(--radius-sm);
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
/* \u2500\u2500 \u9AD8\u7EA7\u8BBE\u7F6E\u6298\u53E0\u533A \u2500\u2500 */
|
|
1310
|
+
.s-advanced {
|
|
1311
|
+
border: 1px solid var(--border);
|
|
1312
|
+
border-radius: var(--radius-sm);
|
|
1313
|
+
background: var(--surface-2);
|
|
1314
|
+
margin: 4px 0 18px;
|
|
1315
|
+
overflow: hidden;
|
|
578
1316
|
}
|
|
579
|
-
.
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
1317
|
+
.s-advanced-head {
|
|
1318
|
+
display: flex;
|
|
1319
|
+
align-items: center;
|
|
1320
|
+
gap: 7px;
|
|
1321
|
+
width: 100%;
|
|
1322
|
+
padding: 10px 13px;
|
|
1323
|
+
background: transparent;
|
|
1324
|
+
border: none;
|
|
1325
|
+
color: var(--text-2);
|
|
1326
|
+
font-family: inherit;
|
|
1327
|
+
font-size: 12.5px;
|
|
1328
|
+
font-weight: 600;
|
|
1329
|
+
cursor: pointer;
|
|
1330
|
+
text-align: left;
|
|
1331
|
+
transition: color .15s, background .15s;
|
|
1332
|
+
}
|
|
1333
|
+
.s-advanced-head:hover { color: var(--text); background: var(--surface-3); }
|
|
1334
|
+
.s-advanced-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
|
1335
|
+
.s-advanced-head .toggle-icon { font-size: 9px; transition: transform .2s; }
|
|
1336
|
+
.s-advanced-head[aria-expanded="true"] .toggle-icon { transform: rotate(90deg); }
|
|
1337
|
+
.s-advanced-count { margin-left: auto; font-weight: 500; color: var(--text-3); font-size: 11.5px; }
|
|
1338
|
+
.s-advanced-body {
|
|
1339
|
+
padding: 14px 13px 4px;
|
|
1340
|
+
background: var(--bg-elevated);
|
|
1341
|
+
border-top: 1px solid var(--border);
|
|
1342
|
+
}
|
|
1343
|
+
.s-advanced-body[hidden] { display: none; }
|
|
1344
|
+
|
|
1345
|
+
/* \u2500\u2500 \u8282\u70B9\u4FE1\u606F \u2500\u2500 */
|
|
1346
|
+
.s-info-grid { display: grid; gap: 0; margin-bottom: 16px; }
|
|
1347
|
+
.s-info-row {
|
|
1348
|
+
display: flex; gap: 16px;
|
|
1349
|
+
padding: 9px 0;
|
|
1350
|
+
border-bottom: 1px solid var(--border);
|
|
1351
|
+
font-size: 13px;
|
|
1352
|
+
min-width: 0;
|
|
583
1353
|
}
|
|
584
|
-
.
|
|
585
|
-
|
|
586
|
-
|
|
1354
|
+
.s-info-row:last-child { border-bottom: none; }
|
|
1355
|
+
.s-info-label { color: var(--text-3); min-width: 118px; flex-shrink: 0; }
|
|
1356
|
+
.s-info-value {
|
|
1357
|
+
color: var(--text); word-break: break-all; min-width: 0;
|
|
1358
|
+
font-family: var(--font-mono); font-size: 12.5px;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
/* \u2500\u2500 \u65E5\u5FD7 \u2500\u2500 */
|
|
1362
|
+
.s-log-toolbar {
|
|
1363
|
+
display: flex; align-items: center; gap: 10px;
|
|
1364
|
+
margin-bottom: 12px; flex-wrap: wrap;
|
|
1365
|
+
}
|
|
1366
|
+
.s-log-toolbar select {
|
|
1367
|
+
background: var(--bg-elevated);
|
|
1368
|
+
border: 1px solid var(--border);
|
|
1369
|
+
color: var(--text);
|
|
1370
|
+
padding: 5px 9px;
|
|
1371
|
+
border-radius: var(--radius-sm);
|
|
1372
|
+
font-size: 12px; outline: none;
|
|
587
1373
|
}
|
|
588
|
-
.settings-head span { font-size: 16px; font-weight: 600; }
|
|
589
|
-
.settings-close {
|
|
590
|
-
background: none; border: none; color: var(--text2);
|
|
591
|
-
font-size: 22px; cursor: pointer; padding: 0 4px; line-height: 1;
|
|
592
|
-
}
|
|
593
|
-
.settings-close:hover { color: var(--text); }
|
|
594
|
-
.settings-body { display: flex; flex: 1; min-height: 320px; }
|
|
595
|
-
.settings-nav { width: 100px; flex-shrink: 0; border-right: 1px solid var(--border); padding: 8px 0; }
|
|
596
|
-
.settings-nav-item { padding: 7px 14px; font-size: 13px; cursor: pointer; color: var(--text2); transition: all .15s; }
|
|
597
|
-
.settings-nav-item:hover { color: var(--text); background: rgba(255,255,255,0.03); }
|
|
598
|
-
.settings-nav-item.active { color: var(--accent); background: rgba(91,141,239,0.1); }
|
|
599
|
-
.settings-content { flex: 1; padding: 20px; overflow-y: auto; }
|
|
600
|
-
.settings-page { display: none; }
|
|
601
|
-
.settings-page.active { display: block; }
|
|
602
|
-
.s-field { margin-bottom: 16px; }
|
|
603
|
-
.s-field label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 6px; color: var(--text2); }
|
|
604
|
-
.s-field input { width: 100%; padding: 9px 12px; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; color: var(--text); font-size: 14px; outline: none; }
|
|
605
|
-
.s-field input:focus { border-color: var(--accent); }
|
|
606
|
-
.s-field select { width: 100%; padding: 9px 12px; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; color: var(--text); font-size: 14px; outline: none; cursor: pointer; }
|
|
607
|
-
.s-field select:focus { border-color: var(--accent); }
|
|
608
|
-
.s-field select option { background: var(--surface2); color: var(--text); }
|
|
609
|
-
.s-field textarea { width: 100%; padding: 9px 12px; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; color: var(--text); font-size: 14px; font-family: "Cascadia Code", "Fira Code", Consolas, monospace; outline: none; resize: vertical; }
|
|
610
|
-
.s-field textarea:focus { border-color: var(--accent); }
|
|
611
|
-
.s-actions-row { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
612
|
-
.s-target-list { display: flex; flex-direction: column; gap: 8px; }
|
|
613
|
-
.s-target-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 12px; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; cursor: pointer; }
|
|
614
|
-
.s-target-item.active { border-color: var(--accent); }
|
|
615
|
-
.s-target-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
616
|
-
.s-target-url { font-size: 14px; font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
617
|
-
.s-target-meta { font-size: 12px; opacity: 0.7; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
618
|
-
.s-target-actions { display: flex; gap: 6px; flex-shrink: 0; }
|
|
619
|
-
.s-target-form { border: 1px solid var(--border); border-radius: 8px; padding: 16px; margin-bottom: 18px; background: var(--surface); }
|
|
620
|
-
.s-target-empty { padding: 14px; text-align: center; font-size: 13px; opacity: 0.6; border: 1px dashed var(--border); border-radius: 6px; }
|
|
621
|
-
.s-hint { display: block; font-size: 11px; color: var(--text2); margin-top: 4px; }
|
|
622
|
-
.s-msg { padding: 8px 12px; border-radius: 6px; font-size: 13px; margin-bottom: 14px; display: none; }
|
|
623
|
-
.s-msg.error { display: block; background: rgba(240,84,84,0.12); color: var(--red); }
|
|
624
|
-
.s-msg.success { display: block; background: rgba(67,198,168,0.12); color: var(--green); }
|
|
625
|
-
.s-msg.info { display: block; background: rgba(91,141,239,0.12); color: var(--accent); }
|
|
626
|
-
.s-actions { margin-top: 12px; }
|
|
627
|
-
.s-btn { padding: 9px 24px; border-radius: 6px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all .15s; border: none; }
|
|
628
|
-
.s-btn-sm { padding: 5px 12px; font-size: 13px; }
|
|
629
|
-
.s-btn-primary { background: var(--accent); color: #fff; }
|
|
630
|
-
.s-btn-primary:hover { background: var(--accent-hover); }
|
|
631
|
-
.s-btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
632
|
-
.s-btn-secondary { background: var(--surface2); color: var(--text); border: 1px solid var(--border); }
|
|
633
|
-
.s-btn-secondary:hover { background: var(--border); }
|
|
634
|
-
.s-btn-danger { background: var(--red); color: #fff; }
|
|
635
|
-
.s-btn-danger:hover { background: #d94a4a; }
|
|
636
|
-
.s-btn-danger:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
637
|
-
.s-desc { font-size: 13px; color: var(--text2); line-height: 1.6; margin: 0; }
|
|
638
|
-
.s-restart-msg { padding: 8px 12px; border-radius: 6px; font-size: 13px; margin-bottom: 14px; display: none; }
|
|
639
|
-
.s-restart-msg.info { display: block; background: rgba(91,141,239,0.12); color: var(--accent); }
|
|
640
|
-
.s-restart-msg.error { display: block; background: rgba(240,84,84,0.12); color: var(--red); }
|
|
641
|
-
/* Info grid */
|
|
642
|
-
.s-info-grid { display: grid; gap: 2px; margin-bottom: 16px; }
|
|
643
|
-
.s-info-row { display: flex; padding: 7px 0; border-bottom: 1px solid var(--border); font-size: 13px; }
|
|
644
|
-
.s-info-label { color: var(--text2); min-width: 110px; flex-shrink: 0; }
|
|
645
|
-
.s-info-value { color: var(--text); word-break: break-all; }
|
|
646
|
-
/* Log viewer */
|
|
647
|
-
.s-log-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }
|
|
648
|
-
.s-log-toolbar select { background: var(--surface2); border: 1px solid var(--border); color: var(--text); padding: 5px 8px; border-radius: 4px; font-size: 12px; outline: none; }
|
|
649
1374
|
.s-log-toolbar select:focus { border-color: var(--accent); }
|
|
650
|
-
.s-log-toggle {
|
|
1375
|
+
.s-log-toggle {
|
|
1376
|
+
font-size: 12px; color: var(--text-2);
|
|
1377
|
+
cursor: pointer; display: flex; align-items: center; gap: 5px;
|
|
1378
|
+
}
|
|
651
1379
|
.s-log-toggle input { accent-color: var(--accent); }
|
|
652
|
-
.s-log-list {
|
|
1380
|
+
.s-log-list {
|
|
1381
|
+
max-height: 380px; overflow-y: auto;
|
|
1382
|
+
font-family: var(--font-mono);
|
|
1383
|
+
font-size: 11.5px; line-height: 1.75;
|
|
1384
|
+
background: var(--bg-elevated);
|
|
1385
|
+
border: 1px solid var(--border);
|
|
1386
|
+
border-radius: var(--radius-sm);
|
|
1387
|
+
padding: 10px;
|
|
1388
|
+
}
|
|
653
1389
|
.s-log-entry { padding: 1px 4px; white-space: pre-wrap; word-break: break-all; }
|
|
654
|
-
.s-log-entry .ll { font-weight:
|
|
1390
|
+
.s-log-entry .ll { font-weight: 700; }
|
|
655
1391
|
.s-log-entry .ll.INFO { color: var(--accent); }
|
|
656
|
-
.s-log-entry .ll.WARN { color: var(--
|
|
657
|
-
.s-log-entry .ll.ERROR { color: var(--
|
|
658
|
-
.s-log-entry .ts { color: var(--
|
|
659
|
-
.s-log-empty { color: var(--
|
|
660
|
-
|
|
1392
|
+
.s-log-entry .ll.WARN { color: var(--warn); }
|
|
1393
|
+
.s-log-entry .ll.ERROR { color: var(--danger); }
|
|
1394
|
+
.s-log-entry .ts { color: var(--text-3); }
|
|
1395
|
+
.s-log-empty { color: var(--text-3); font-size: 12.5px; text-align: center; padding: 28px; }
|
|
1396
|
+
|
|
1397
|
+
/* \u2500\u2500 \u8BBE\u7F6E\u5185\u8868\u683C \u2500\u2500 */
|
|
661
1398
|
.s-table-wrap { max-height: 320px; overflow-y: auto; }
|
|
662
1399
|
.s-table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
|
663
|
-
.s-table th {
|
|
664
|
-
|
|
1400
|
+
.s-table th {
|
|
1401
|
+
text-align: left; padding: 8px 10px;
|
|
1402
|
+
font-size: 11px; font-weight: 620; color: var(--text-3);
|
|
1403
|
+
background: var(--surface-2);
|
|
1404
|
+
border-bottom: 1px solid var(--border);
|
|
1405
|
+
white-space: nowrap;
|
|
1406
|
+
}
|
|
1407
|
+
.s-table td { padding: 7px 10px; border-bottom: 1px solid var(--border); }
|
|
665
1408
|
.s-table tr:last-child td { border-bottom: none; }
|
|
666
|
-
.s-tag {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
.
|
|
671
|
-
.
|
|
672
|
-
.
|
|
1409
|
+
.s-tag {
|
|
1410
|
+
display: inline-block; padding: 1px 7px;
|
|
1411
|
+
border-radius: 20px; font-size: 11px; font-weight: 600;
|
|
1412
|
+
}
|
|
1413
|
+
.s-tag.green { background: var(--ok-soft); color: var(--ok); }
|
|
1414
|
+
.s-tag.red { background: var(--danger-soft); color: var(--danger); }
|
|
1415
|
+
.s-tag.orange { background: var(--warn-soft); color: var(--warn); }
|
|
1416
|
+
|
|
1417
|
+
.s-restart-msg {
|
|
1418
|
+
padding: 9px 12px; border-radius: var(--radius-sm);
|
|
1419
|
+
font-size: 12.5px; margin-bottom: 14px; display: none;
|
|
1420
|
+
}
|
|
1421
|
+
.s-restart-msg.info { display: block; background: var(--info-soft); color: var(--info); }
|
|
1422
|
+
.s-restart-msg.error { display: block; background: var(--danger-soft); color: var(--danger); }
|
|
1423
|
+
|
|
1424
|
+
/* \u8BBE\u7F6E\u5F39\u7A97\u5185\u7684\u6EDA\u52A8\u5BB9\u5668\uFF1A\u7EC6\u6EDA\u52A8\u6761 */
|
|
1425
|
+
.settings-content,
|
|
1426
|
+
.settings-nav,
|
|
1427
|
+
.s-log-list,
|
|
1428
|
+
.s-table-wrap {
|
|
1429
|
+
scrollbar-width: thin;
|
|
1430
|
+
scrollbar-color: var(--border-strong) transparent;
|
|
1431
|
+
}
|
|
1432
|
+
.settings-content::-webkit-scrollbar,
|
|
1433
|
+
.settings-nav::-webkit-scrollbar,
|
|
1434
|
+
.s-log-list::-webkit-scrollbar,
|
|
1435
|
+
.s-table-wrap::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
1436
|
+
.settings-content::-webkit-scrollbar-track,
|
|
1437
|
+
.settings-nav::-webkit-scrollbar-track,
|
|
1438
|
+
.s-log-list::-webkit-scrollbar-track,
|
|
1439
|
+
.s-table-wrap::-webkit-scrollbar-track { background: transparent; }
|
|
1440
|
+
.settings-content::-webkit-scrollbar-thumb,
|
|
1441
|
+
.settings-nav::-webkit-scrollbar-thumb,
|
|
1442
|
+
.s-log-list::-webkit-scrollbar-thumb,
|
|
1443
|
+
.s-table-wrap::-webkit-scrollbar-thumb {
|
|
1444
|
+
background: var(--border-strong); border-radius: 3px;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
/* \u7A84\u5C4F\uFF1A\u5BFC\u822A\u6539\u4E3A\u9876\u90E8\u6A2A\u5411\u6EDA\u52A8\uFF08\u9762\u677F\u9AD8\u5EA6\u540C\u6837\u56FA\u5B9A\uFF09 */
|
|
1448
|
+
@media (max-width: 620px) {
|
|
1449
|
+
.settings-panel { height: min(92vh, 640px); }
|
|
1450
|
+
.settings-body { flex-direction: column; }
|
|
1451
|
+
.settings-nav {
|
|
1452
|
+
width: auto;
|
|
1453
|
+
flex-direction: row;
|
|
1454
|
+
border-right: none;
|
|
1455
|
+
border-bottom: 1px solid var(--border);
|
|
1456
|
+
padding: 8px;
|
|
1457
|
+
gap: 4px;
|
|
1458
|
+
overflow-x: auto;
|
|
1459
|
+
overflow-y: hidden;
|
|
1460
|
+
}
|
|
1461
|
+
.settings-nav-item { white-space: nowrap; padding: 6px 12px; }
|
|
1462
|
+
.settings-content { padding: 16px; }
|
|
1463
|
+
.s-info-label { min-width: 92px; }
|
|
1464
|
+
}
|
|
673
1465
|
</style>
|
|
674
1466
|
|
|
675
1467
|
<script>
|
|
@@ -686,6 +1478,32 @@ const T = {
|
|
|
686
1478
|
managed: "\u7BA1\u7406",
|
|
687
1479
|
orphan: "\u5B64\u513F",
|
|
688
1480
|
managedLabel: "\u7BA1\u7406\u4E2D",
|
|
1481
|
+
// \u9875\u9762\u9AA8\u67B6
|
|
1482
|
+
pageTitle: "\u8FD0\u884C\u603B\u89C8",
|
|
1483
|
+
pageSub: "\u672C\u673A Bridge \u72B6\u6001\u4E0E Agent \u4F1A\u8BDD",
|
|
1484
|
+
heroServiceLabel: "Bridge \u670D\u52A1",
|
|
1485
|
+
heroRegLabel: "\u8C03\u5EA6\u4E2D\u5FC3\u6CE8\u518C",
|
|
1486
|
+
heroBindLabel: "\u9762\u677F\u7ED1\u5B9A",
|
|
1487
|
+
heroRunning: "\u8FD0\u884C\u6B63\u5E38",
|
|
1488
|
+
heroRegYes: "\u5DF2\u6CE8\u518C",
|
|
1489
|
+
heroRegNo: "\u672A\u6CE8\u518C",
|
|
1490
|
+
heroBindYes: "\u5DF2\u7ED1\u5B9A",
|
|
1491
|
+
heroBindNo: "\u672A\u7ED1\u5B9A",
|
|
1492
|
+
kpiActiveLabel: "\u6D3B\u8DC3\u4F1A\u8BDD",
|
|
1493
|
+
kpiPersistedLabel: "\u6301\u4E45\u5316\u4F1A\u8BDD",
|
|
1494
|
+
kpiOrphanLabel: "\u5B64\u513F\u8FDB\u7A0B",
|
|
1495
|
+
kpiUptimeLabel: "\u8FD0\u884C\u65F6\u957F",
|
|
1496
|
+
kpiActiveFoot: "\u6B63\u5728\u5DE5\u4F5C\u7684 Agent",
|
|
1497
|
+
kpiPersistedFoot: "\u53EF\u6062\u590D\u7684\u4F1A\u8BDD",
|
|
1498
|
+
kpiOrphanFoot: "\u9700\u6E05\u7406\u7684\u6B8B\u7559\u8FDB\u7A0B",
|
|
1499
|
+
kpiUptimeFoot: "\u81EA\u4E0A\u6B21\u542F\u52A8",
|
|
1500
|
+
// \u5F85\u5904\u7406\u4E8B\u9879
|
|
1501
|
+
attnNoScheduler: "\u8FD8\u672A\u8FDE\u63A5\u8C03\u5EA6\u4E2D\u5FC3",
|
|
1502
|
+
attnNoSchedulerDetail: "\u914D\u7F6E\u670D\u52A1\u5668\u5730\u5740\u540E\uFF0CAgent \u624D\u80FD\u63A5\u6536\u4EFB\u52A1\u3002",
|
|
1503
|
+
attnOrphans: "\u68C0\u51FA {n} \u4E2A\u5B64\u513F\u8FDB\u7A0B",
|
|
1504
|
+
attnOrphansDetail: "\u8FD9\u4E9B\u8FDB\u7A0B\u4ECD\u5728\u8FD0\u884C\uFF0C\u4F46\u5DF2\u4E0D\u53D7 Bridge \u7BA1\u7406\u3002",
|
|
1505
|
+
attnActionScheduler: "\u53BB\u914D\u7F6E",
|
|
1506
|
+
attnActionOrphans: "\u67E5\u770B",
|
|
689
1507
|
// \u72B6\u6001\u5361\u7247
|
|
690
1508
|
cardBridge: "Bridge",
|
|
691
1509
|
cardRegistration: "\u6CE8\u518C",
|
|
@@ -765,6 +1583,7 @@ const T = {
|
|
|
765
1583
|
setRestarting: "\u91CD\u542F\u4E2D...",
|
|
766
1584
|
// \u8282\u70B9\u4FE1\u606F
|
|
767
1585
|
nodeTitle: "\u8282\u70B9\u4FE1\u606F",
|
|
1586
|
+
nodeRefresh: "\u21BB \u5237\u65B0",
|
|
768
1587
|
nodeHostname: "\u4E3B\u673A\u540D",
|
|
769
1588
|
nodePlatform: "\u5E73\u53F0",
|
|
770
1589
|
nodeUptime: "\u8FD0\u884C\u65F6\u957F",
|
|
@@ -794,12 +1613,13 @@ const T = {
|
|
|
794
1613
|
schedAddTarget: "\uFF0B \u6DFB\u52A0\u8C03\u5EA6\u4E2D\u5FC3",
|
|
795
1614
|
schedEditTarget: "\u7F16\u8F91",
|
|
796
1615
|
schedDeleteTarget: "\u5220\u9664",
|
|
1616
|
+
schedCopyConnKey: "\u590D\u5236\u5BC6\u94A5",
|
|
797
1617
|
schedConfirmDelete: "\u5220\u9664\u8BE5\u8C03\u5EA6\u4E2D\u5FC3\u914D\u7F6E\uFF1F",
|
|
798
1618
|
schedFormNew: "\u65B0\u589E\u8C03\u5EA6\u4E2D\u5FC3",
|
|
799
1619
|
schedUrl: "\u670D\u52A1\u5730\u5740 (serverUrl)",
|
|
800
1620
|
schedUrlHint: "\u5FC5\u586B\uFF0C\u4EE5 http:// \u6216 https:// \u5F00\u5934\uFF1B\u5730\u5740\u53D8\u66F4\u65F6 API Key \u4F1A\u81EA\u52A8\u6CBF\u7528",
|
|
801
1621
|
schedUserKey: "\u7528\u6237 API Key (userKey)",
|
|
802
|
-
schedUserKeyHint: "\u5FC5\u586B\uFF1B\u5DF2\u914D\u7F6E\u65F6\u7559\u7A7A\u8868\u793A\u4FDD\u6301\u4E0D\u53D8",
|
|
1622
|
+
schedUserKeyHint: "\u5FC5\u586B\uFF1B\u5DF2\u914D\u7F6E\u65F6\u7559\u7A7A\u8868\u793A\u4FDD\u6301\u4E0D\u53D8\u3002\u540C\u4E00\u5730\u5740\u53EF\u6DFB\u52A0\u591A\u884C\u2014\u2014\u4E0D\u540C API Key \u4EE3\u8868\u4E0D\u540C\u8D26\u53F7/\u79DF\u6237\uFF0C\u4F1A\u5E76\u884C\u6CE8\u518C",
|
|
803
1623
|
schedTenantId: "\u79DF\u6237 ID\uFF08\u53EF\u9009\uFF09",
|
|
804
1624
|
schedTenantIdHint: "\u7559\u7A7A\u7531 server \u6839\u636E userKey \u81EA\u52A8\u63A8\u5BFC",
|
|
805
1625
|
schedInstanceName: "\u5B9E\u4F8B\u540D\u79F0\uFF08\u53EF\u9009\uFF09",
|
|
@@ -815,18 +1635,37 @@ const T = {
|
|
|
815
1635
|
schedTunnelOff: "\u7981\u7528",
|
|
816
1636
|
schedInstanceId: "\u5B9E\u4F8B ID",
|
|
817
1637
|
schedInstanceIdHint: "\u7F16\u8F91\u6539\u5740\u65F6\u81EA\u52A8\u7EE7\u627F\uFF08\u540C\u4E00\u5B9E\u4F8B\u6362\u5730\u5740\uFF09\uFF0C\u65B0\u589E\u76EE\u6807\u6CE8\u518C\u6210\u529F\u540E\u81EA\u52A8\u56DE\u586B",
|
|
1638
|
+
schedAdvanced: "\u9AD8\u7EA7\u8BBE\u7F6E",
|
|
1639
|
+
schedAdvancedSummary: "\u9AD8\u7EA7\u8BBE\u7F6E \xB7 \u5DF2\u586B {n} \u9879\u53EF\u9009\u9879",
|
|
1640
|
+
schedAdvancedEmpty: "\u9AD8\u7EA7\u8BBE\u7F6E \xB7 \u53EF\u9009\u9879\u5747\u4E3A\u7A7A\uFF0C\u4FDD\u6301\u9ED8\u8BA4\u5373\u53EF",
|
|
1641
|
+
schedAdvancedN: "{n} \u9879\u5DF2\u586B",
|
|
818
1642
|
schedSave: "\u4FDD\u5B58\u914D\u7F6E",
|
|
819
1643
|
schedSaving: "\u4FDD\u5B58\u4E2D...",
|
|
820
1644
|
schedSaved: "\u914D\u7F6E\u5DF2\u4FDD\u5B58",
|
|
821
1645
|
schedSavedRegistering: "\u914D\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u6B63\u5728\u91CD\u65B0\u6CE8\u518C...",
|
|
822
1646
|
schedSaveFail: "\u4FDD\u5B58\u5931\u8D25",
|
|
1647
|
+
schedDupNoKey: "\u8BE5\u5730\u5740\u5DF2\u6709\u914D\u7F6E\u3002\u540C\u4E00\u5730\u5740\u8981\u65B0\u589E\u4E00\u6761\uFF0C\u8BF7\u586B\u5165\u53E6\u4E00\u4E2A API Key\uFF08\u4E0D\u540C\u8D26\u53F7/\u79DF\u6237\uFF09\uFF1B\u82E5\u8981\u4FEE\u6539\u5DF2\u6709\u6761\u76EE\uFF0C\u8BF7\u70B9\u51FB\u5217\u8868\u4E2D\u8BE5\u6761\u76EE\u7684\u300C\u7F16\u8F91\u300D\u3002",
|
|
1648
|
+
schedTenantShort: "\u79DF\u6237",
|
|
823
1649
|
schedSaveAndRestart: "\u4FDD\u5B58\u5E76\u91CD\u542F",
|
|
824
1650
|
schedSaveRestartConfirm: "\u4FDD\u5B58\u914D\u7F6E\u5E76\u91CD\u542F Bridge\uFF1F\u91CD\u542F\u671F\u95F4\u670D\u52A1\u4F1A\u77ED\u6682\u4E2D\u65AD\uFF0C\u8FD0\u884C\u4E2D\u7684 Agent \u4F1A\u88AB\u4FDD\u7559\u3002",
|
|
1651
|
+
aftersaveTitle: "\u914D\u7F6E\u5DF2\u4FDD\u5B58",
|
|
1652
|
+
aftersaveDesc: "\u65B0\u914D\u7F6E\u5DF2\u5199\u5165\u3002\u8981\u7ACB\u5373\u751F\u6548\u5417\uFF1F",
|
|
1653
|
+
aftersaveRegister: "\u7ACB\u5373\u91CD\u65B0\u6CE8\u518C",
|
|
1654
|
+
aftersaveRegisterRec: "\u63A8\u8350",
|
|
1655
|
+
aftersaveRestart: "\u91CD\u542F Bridge \u751F\u6548",
|
|
1656
|
+
aftersaveRestartHint: "\u96A7\u9053\u6A21\u5F0F\u7B49\u53D8\u66F4\u9700\u8981\u91CD\u542F\uFF1B\u91CD\u542F\u671F\u95F4\u670D\u52A1\u77ED\u6682\u4E2D\u65AD\uFF0C\u8FD0\u884C\u4E2D\u7684 Agent \u4F1A\u88AB\u4FDD\u7559",
|
|
1657
|
+
aftersaveSkip: "\u6682\u4E0D\uFF0C\u7A0D\u540E\u518D\u8BF4",
|
|
1658
|
+
aftersaveRegistering: "\u6B63\u5728\u91CD\u65B0\u6CE8\u518C...",
|
|
1659
|
+
aftersaveRestarting: "\u6B63\u5728\u91CD\u542F...",
|
|
1660
|
+
aftersaveRegisterOk: "\u91CD\u65B0\u6CE8\u518C\u6210\u529F\uFF0C\u65B0\u914D\u7F6E\u5DF2\u751F\u6548",
|
|
1661
|
+
aftersaveRegisterFail: "\u91CD\u65B0\u6CE8\u518C\u5931\u8D25",
|
|
1662
|
+
aftersaveSkipDone: "\u5DF2\u4FDD\u5B58\u3002\u914D\u7F6E\u4F1A\u5728\u4E0B\u6B21 Bridge \u542F\u52A8\u65F6\u751F\u6548\uFF1B\u4E5F\u53EF\u968F\u65F6\u624B\u52A8\u91CD\u542F\u3002",
|
|
825
1663
|
schedReRegister: "\u91CD\u65B0\u6CE8\u518C",
|
|
826
1664
|
schedReRegistering: "\u6CE8\u518C\u4E2D...",
|
|
827
1665
|
schedReRegistered: "\u91CD\u65B0\u6CE8\u518C\u6210\u529F",
|
|
828
1666
|
schedReRegisterFail: "\u91CD\u65B0\u6CE8\u518C\u5931\u8D25",
|
|
829
|
-
schedDesc: "\u4FDD\u5B58\u540E\
|
|
1667
|
+
schedDesc: "\u4FDD\u5B58\u6210\u529F\u540E\u4F1A\u8BE2\u95EE\u5982\u4F55\u5E94\u7528\uFF1A\u63A8\u8350\u7ACB\u5373\u91CD\u65B0\u6CE8\u518C\uFF1B\u96A7\u9053\u6A21\u5F0F\u7B49\u90E8\u5206\u53D8\u66F4\u9700\u91CD\u542F Bridge \u751F\u6548\u3002",
|
|
1668
|
+
schedPrefillNotice: "\u5DF2\u4ECE\u9762\u677F\u5E26\u5165\u670D\u52A1\u5668\u5730\u5740 {0}\u3002\u8BF7\u5728\u4E0B\u65B9\u7C98\u8D34\u4F60\u7684 userKey \u540E\u4FDD\u5B58\uFF08\u5BC6\u94A5\u4E0D\u4F1A\u901A\u8FC7\u94FE\u63A5\u4F20\u5165\uFF09\u3002",
|
|
830
1669
|
// \u8BBF\u95EE\u63A7\u5236
|
|
831
1670
|
accTitle: "\u8BBF\u95EE\u63A7\u5236",
|
|
832
1671
|
accAllowIps: "\u5141\u8BB8\u8BBF\u95EE\u7684 IP / \u7F51\u6BB5",
|
|
@@ -859,6 +1698,32 @@ const T = {
|
|
|
859
1698
|
managed: "managed",
|
|
860
1699
|
orphan: "orphan",
|
|
861
1700
|
managedLabel: "managed",
|
|
1701
|
+
// \u9875\u9762\u9AA8\u67B6
|
|
1702
|
+
pageTitle: "Overview",
|
|
1703
|
+
pageSub: "Local bridge status and agent sessions",
|
|
1704
|
+
heroServiceLabel: "Bridge service",
|
|
1705
|
+
heroRegLabel: "Scheduler registration",
|
|
1706
|
+
heroBindLabel: "Panel binding",
|
|
1707
|
+
heroRunning: "Running",
|
|
1708
|
+
heroRegYes: "Registered",
|
|
1709
|
+
heroRegNo: "Not registered",
|
|
1710
|
+
heroBindYes: "Paired",
|
|
1711
|
+
heroBindNo: "Unpaired",
|
|
1712
|
+
kpiActiveLabel: "Active sessions",
|
|
1713
|
+
kpiPersistedLabel: "Persisted sessions",
|
|
1714
|
+
kpiOrphanLabel: "Orphan processes",
|
|
1715
|
+
kpiUptimeLabel: "Uptime",
|
|
1716
|
+
kpiActiveFoot: "Agents currently working",
|
|
1717
|
+
kpiPersistedFoot: "Sessions that can be resumed",
|
|
1718
|
+
kpiOrphanFoot: "Leftover processes to clean up",
|
|
1719
|
+
kpiUptimeFoot: "Since last start",
|
|
1720
|
+
// \u5F85\u5904\u7406\u4E8B\u9879
|
|
1721
|
+
attnNoScheduler: "Not connected to a scheduler",
|
|
1722
|
+
attnNoSchedulerDetail: "Configure a server address so agents can receive tasks.",
|
|
1723
|
+
attnOrphans: "{n} orphan process(es) detected",
|
|
1724
|
+
attnOrphansDetail: "They are still running but no longer managed by Bridge.",
|
|
1725
|
+
attnActionScheduler: "Configure",
|
|
1726
|
+
attnActionOrphans: "Review",
|
|
862
1727
|
cardBridge: "Bridge",
|
|
863
1728
|
cardRegistration: "Registration",
|
|
864
1729
|
cardBinding: "Binding",
|
|
@@ -935,6 +1800,7 @@ const T = {
|
|
|
935
1800
|
setRestarting: "Restarting...",
|
|
936
1801
|
// Node Info
|
|
937
1802
|
nodeTitle: "Node Info",
|
|
1803
|
+
nodeRefresh: "\u21BB Refresh",
|
|
938
1804
|
nodeHostname: "Hostname",
|
|
939
1805
|
nodePlatform: "Platform",
|
|
940
1806
|
nodeUptime: "Uptime",
|
|
@@ -964,12 +1830,13 @@ const T = {
|
|
|
964
1830
|
schedAddTarget: "+ Add scheduler",
|
|
965
1831
|
schedEditTarget: "Edit",
|
|
966
1832
|
schedDeleteTarget: "Delete",
|
|
1833
|
+
schedCopyConnKey: "Copy key",
|
|
967
1834
|
schedConfirmDelete: "Delete this scheduler configuration?",
|
|
968
1835
|
schedFormNew: "New scheduler",
|
|
969
1836
|
schedUrl: "Server URL (serverUrl)",
|
|
970
1837
|
schedUrlHint: "Required, must start with http:// or https://; the API key is kept when the URL changes",
|
|
971
1838
|
schedUserKey: "User API Key (userKey)",
|
|
972
|
-
schedUserKeyHint: "Required; leave blank to keep the configured value",
|
|
1839
|
+
schedUserKeyHint: "Required; leave blank to keep the configured value. The same address can be added multiple times \u2014 a different API key means a different account/tenant, registered in parallel",
|
|
973
1840
|
schedTenantId: "Tenant ID (optional)",
|
|
974
1841
|
schedTenantIdHint: "Leave blank to auto-derive from userKey on the server",
|
|
975
1842
|
schedInstanceName: "Instance name (optional)",
|
|
@@ -985,18 +1852,37 @@ const T = {
|
|
|
985
1852
|
schedTunnelOff: "Off",
|
|
986
1853
|
schedInstanceId: "Instance ID",
|
|
987
1854
|
schedInstanceIdHint: "Inherited automatically when editing the URL (same instance, new address); backfilled after a new target registers successfully",
|
|
1855
|
+
schedAdvanced: "Advanced",
|
|
1856
|
+
schedAdvancedSummary: "Advanced \xB7 {n} optional field(s) filled",
|
|
1857
|
+
schedAdvancedEmpty: "Advanced \xB7 all optional, defaults are fine",
|
|
1858
|
+
schedAdvancedN: "{n} filled",
|
|
988
1859
|
schedSave: "Save config",
|
|
989
1860
|
schedSaving: "Saving...",
|
|
990
1861
|
schedSaved: "Configuration saved",
|
|
991
1862
|
schedSavedRegistering: "Configuration saved, re-registering...",
|
|
992
1863
|
schedSaveFail: "Failed to save",
|
|
1864
|
+
schedDupNoKey: "This address already has an entry. To add another one for the same address, enter a different API key (a different account/tenant); to modify the existing entry, click Edit on it in the list.",
|
|
1865
|
+
schedTenantShort: "tenant",
|
|
993
1866
|
schedSaveAndRestart: "Save & restart",
|
|
994
1867
|
schedSaveRestartConfirm: "Save the configuration and restart the bridge? The service will be briefly interrupted; running agents are preserved.",
|
|
1868
|
+
aftersaveTitle: "Configuration saved",
|
|
1869
|
+
aftersaveDesc: "The new configuration has been written. Apply it now?",
|
|
1870
|
+
aftersaveRegister: "Re-register now",
|
|
1871
|
+
aftersaveRegisterRec: "Recommended",
|
|
1872
|
+
aftersaveRestart: "Restart Bridge to apply",
|
|
1873
|
+
aftersaveRestartHint: "Needed for tunnel-mode changes; restart briefly interrupts the service, running agents are preserved",
|
|
1874
|
+
aftersaveSkip: "Not now",
|
|
1875
|
+
aftersaveRegistering: "Re-registering...",
|
|
1876
|
+
aftersaveRestarting: "Restarting...",
|
|
1877
|
+
aftersaveRegisterOk: "Re-registered successfully, new configuration is live",
|
|
1878
|
+
aftersaveRegisterFail: "Re-register failed",
|
|
1879
|
+
aftersaveSkipDone: "Saved. The configuration applies on next Bridge start; you can also restart manually anytime.",
|
|
995
1880
|
schedReRegister: "Re-register",
|
|
996
1881
|
schedReRegistering: "Registering...",
|
|
997
1882
|
schedReRegistered: "Re-registered successfully",
|
|
998
1883
|
schedReRegisterFail: "Re-registration failed",
|
|
999
|
-
schedDesc: "After saving
|
|
1884
|
+
schedDesc: "After saving you'll be asked how to apply it: re-registering now is recommended; some changes such as tunnel mode require a bridge restart.",
|
|
1885
|
+
schedPrefillNotice: "The server address {0} was pre-filled from the dashboard. Paste your userKey below and save (secrets are never passed via the link).",
|
|
1000
1886
|
// Access control
|
|
1001
1887
|
accTitle: "Access Control",
|
|
1002
1888
|
accAllowIps: "Allowed IPs / networks",
|
|
@@ -1050,6 +1936,7 @@ function toggleLang() {
|
|
|
1050
1936
|
localStorage.setItem("dashboard-lang", lang);
|
|
1051
1937
|
document.documentElement.lang = lang === "en" ? "en" : "zh-CN";
|
|
1052
1938
|
document.getElementById("langBtn").textContent = t("langName");
|
|
1939
|
+
updateThemeBtn();
|
|
1053
1940
|
if (document.getElementById("settingsModal").style.display === "flex") {
|
|
1054
1941
|
renderSettingsUI();
|
|
1055
1942
|
settingsLang = lang;
|
|
@@ -1057,6 +1944,30 @@ function toggleLang() {
|
|
|
1057
1944
|
if (lastData) render(lastData);
|
|
1058
1945
|
}
|
|
1059
1946
|
|
|
1947
|
+
// \u2500\u2500 \u4E3B\u9898\u504F\u597D\uFF08\u9ED8\u8BA4\u6DF1\u8272\uFF1B\u4E0E\u767B\u5F55/\u8BBE\u7F6E\u9875\u5171\u7528 dashboard-theme\uFF09\u2500\u2500
|
|
1948
|
+
function isLightTheme() {
|
|
1949
|
+
return document.documentElement.classList.contains("light-theme");
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
// \u6309\u94AE\u5448\u73B0"\u5373\u5C06\u5207\u6362\u5230\u7684\u76EE\u6807"\uFF1A\u6DF1\u8272\u65F6\u663E\u592A\u9633\uFF08\u70B9\u2192\u6D45\u8272\uFF09\uFF0C\u6D45\u8272\u65F6\u663E\u6708\u4EAE\uFF08\u70B9\u2192\u6DF1\u8272\uFF09
|
|
1953
|
+
function updateThemeBtn() {
|
|
1954
|
+
var btn = document.getElementById("themeBtn");
|
|
1955
|
+
if (!btn) return;
|
|
1956
|
+
var light = isLightTheme();
|
|
1957
|
+
btn.textContent = light ? "\\u263D" : "\\u2600";
|
|
1958
|
+
btn.title = light
|
|
1959
|
+
? (lang === "en" ? "Switch to dark" : "\u5207\u6362\u6DF1\u8272")
|
|
1960
|
+
: (lang === "en" ? "Switch to light" : "\u5207\u6362\u6D45\u8272");
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
function toggleTheme() {
|
|
1964
|
+
var light = document.documentElement.classList.toggle("light-theme");
|
|
1965
|
+
try { localStorage.setItem("dashboard-theme", light ? "light" : "dark"); } catch (e) {}
|
|
1966
|
+
updateThemeBtn();
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
updateThemeBtn();
|
|
1970
|
+
|
|
1060
1971
|
// \u2500\u2500 \u5DE5\u5177\u51FD\u6570 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1061
1972
|
let lastData = null;
|
|
1062
1973
|
|
|
@@ -1141,105 +2052,237 @@ function barColor(pct) {
|
|
|
1141
2052
|
}
|
|
1142
2053
|
|
|
1143
2054
|
// \u2500\u2500 \u6E32\u67D3 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2055
|
+
|
|
2056
|
+
/** \u8BBE\u7F6E\u4E00\u4E2A\u72B6\u6001\u70B9\uFF08hero \u7528\uFF09\uFF1Aok / warn / danger / \u7A7A\u3002 */
|
|
2057
|
+
function setDot(id, tone, pulse) {
|
|
2058
|
+
const el = document.getElementById(id);
|
|
2059
|
+
if (!el) return;
|
|
2060
|
+
el.className = "dot" + (tone ? " " + tone : "") + (pulse ? " pulse" : "");
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
/** \u5F85\u5904\u7406\u4E8B\u9879\u7684\u52A8\u4F5C\u76EE\u6807\uFF1A\u70B9\u51FB\u540E\u8DF3\u8F6C\u5230\u5BF9\u5E94\u8BBE\u7F6E\u9875\u3002 */
|
|
2064
|
+
let attentionActionKind = null;
|
|
2065
|
+
|
|
2066
|
+
function runAttentionAction() {
|
|
2067
|
+
if (attentionActionKind === "scheduler") {
|
|
2068
|
+
openSettings();
|
|
2069
|
+
switchSettingsPage("scheduler");
|
|
2070
|
+
} else if (attentionActionKind === "orphans") {
|
|
2071
|
+
const table = document.getElementById("unknownTable");
|
|
2072
|
+
if (table && table.style.display === "none") toggleUnknown();
|
|
2073
|
+
const el = document.getElementById("orphanContainer");
|
|
2074
|
+
if (el) el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
/**
|
|
2079
|
+
* \u5F85\u5904\u7406\u4E8B\u9879\u6761\uFF1A\u53EA\u5728\u771F\u7684\u9700\u8981\u7528\u6237\u52A8\u624B\u65F6\u51FA\u73B0\u3002
|
|
2080
|
+
* \u4F18\u5148\u7EA7\uFF1A\u672A\u6CE8\u518C\u8C03\u5EA6\u4E2D\u5FC3 > \u5B64\u513F\u8FDB\u7A0B\u3002\u4E24\u8005\u4E92\u4E0D\u8986\u76D6\uFF0C\u4F46\u4E00\u6B21\u53EA\u63D0\u793A\u6700\u7D27\u8981\u7684\u4E00\u6761\uFF0C
|
|
2081
|
+
* \u907F\u514D\u9762\u677F\u53D8\u6210\u4E00\u5806\u9EC4\u6761\u3002
|
|
2082
|
+
*/
|
|
2083
|
+
function renderAttention(data) {
|
|
2084
|
+
const bar = document.getElementById("attentionBar");
|
|
2085
|
+
const textEl = document.getElementById("attentionText");
|
|
2086
|
+
const actionEl = document.getElementById("attentionAction");
|
|
2087
|
+
if (!bar || !textEl || !actionEl) return;
|
|
2088
|
+
|
|
2089
|
+
const registered = Boolean(data.registration?.registered);
|
|
2090
|
+
const orphanCount = data.processes?.orphan ?? 0;
|
|
2091
|
+
|
|
2092
|
+
if (!registered) {
|
|
2093
|
+
textEl.innerHTML =
|
|
2094
|
+
"<b>" + escHtml(t("attnNoScheduler")) + "</b> " +
|
|
2095
|
+
escHtml(t("attnNoSchedulerDetail"));
|
|
2096
|
+
actionEl.textContent = t("attnActionScheduler");
|
|
2097
|
+
actionEl.style.display = "";
|
|
2098
|
+
attentionActionKind = "scheduler";
|
|
2099
|
+
bar.hidden = false;
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
if (orphanCount > 0) {
|
|
2104
|
+
textEl.innerHTML =
|
|
2105
|
+
"<b>" + escHtml(t("attnOrphans").replace("{n}", String(orphanCount))) + "</b> " +
|
|
2106
|
+
escHtml(t("attnOrphansDetail"));
|
|
2107
|
+
actionEl.textContent = t("attnActionOrphans");
|
|
2108
|
+
actionEl.style.display = "";
|
|
2109
|
+
attentionActionKind = "orphans";
|
|
2110
|
+
bar.hidden = false;
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
bar.hidden = true;
|
|
2115
|
+
attentionActionKind = null;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
/** \u5065\u5EB7\u603B\u89C8\uFF1A\u4E09\u4E2A\u300C\u662F\u5426\u53EF\u7528\u300D\u7684\u5224\u65AD\uFF0C\u53D6\u4EE3\u539F\u5148 4 \u5F20\u5E73\u94FA\u72B6\u6001\u5361\u3002 */
|
|
2119
|
+
function renderHero(data) {
|
|
2120
|
+
const b = data.binding || {};
|
|
2121
|
+
const r = data.registration || {};
|
|
2122
|
+
|
|
2123
|
+
// Bridge \u670D\u52A1\uFF08\u9875\u9762\u80FD\u6253\u5F00\u5C31\u8BF4\u660E\u8FDB\u7A0B\u6D3B\u7740\uFF0C\u72B6\u6001\u70B9\u6052\u4E3A ok\uFF09
|
|
2124
|
+
document.getElementById("heroServiceLabel").textContent = t("heroServiceLabel");
|
|
2125
|
+
document.getElementById("heroServiceValue").textContent = t("heroRunning");
|
|
2126
|
+
setDot("heroServiceDot", "ok", true);
|
|
2127
|
+
const platform = [data.bridge?.platform, data.bridge?.arch].filter(Boolean).join(" ");
|
|
2128
|
+
document.getElementById("heroServiceDetail").textContent =
|
|
2129
|
+
[platform, data.bridge?.nodeVersion, fmtDuration(data.bridge?.uptime)]
|
|
2130
|
+
.filter(Boolean)
|
|
2131
|
+
.join(" \xB7 ");
|
|
2132
|
+
|
|
2133
|
+
// \u8C03\u5EA6\u4E2D\u5FC3\u6CE8\u518C
|
|
2134
|
+
document.getElementById("heroRegLabel").textContent = t("heroRegLabel");
|
|
2135
|
+
document.getElementById("heroRegValue").textContent =
|
|
2136
|
+
r.registered ? t("heroRegYes") : t("heroRegNo");
|
|
2137
|
+
setDot("heroRegDot", r.registered ? "ok" : "warn", false);
|
|
2138
|
+
document.getElementById("heroRegDetail").textContent =
|
|
2139
|
+
r.error || r.instanceId || r.lastAttempt
|
|
2140
|
+
? r.error || r.instanceId || fmtTime(r.lastAttempt)
|
|
2141
|
+
: "-";
|
|
2142
|
+
|
|
2143
|
+
// \u9762\u677F\u7ED1\u5B9A
|
|
2144
|
+
document.getElementById("heroBindLabel").textContent = t("heroBindLabel");
|
|
2145
|
+
document.getElementById("heroBindValue").textContent =
|
|
2146
|
+
b.paired ? t("heroBindYes") : t("heroBindNo");
|
|
2147
|
+
setDot("heroBindDot", b.paired ? "ok" : "warn", false);
|
|
2148
|
+
document.getElementById("heroBindDetail").textContent =
|
|
2149
|
+
b.schedulerBaseUrl || b.instanceId || "-";
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
/** KPI \u78C1\u8D34\uFF1A\u56DB\u4E2A\u6700\u5173\u952E\u7684\u6570\u5B57\u3002 */
|
|
2153
|
+
function renderKpis(data) {
|
|
2154
|
+
const activeCount = data.sessions?.activeCount ?? 0;
|
|
2155
|
+
const persistedCount = data.sessions?.persistedCount ?? 0;
|
|
2156
|
+
const orphanCount = data.processes?.orphan ?? 0;
|
|
2157
|
+
const managedCount = data.processes?.managed ?? 0;
|
|
2158
|
+
|
|
2159
|
+
document.getElementById("kpiActiveLabel").textContent = t("kpiActiveLabel");
|
|
2160
|
+
document.getElementById("kpiPersistedLabel").textContent = t("kpiPersistedLabel");
|
|
2161
|
+
document.getElementById("kpiOrphanLabel").textContent = t("kpiOrphanLabel");
|
|
2162
|
+
document.getElementById("kpiUptimeLabel").textContent = t("kpiUptimeLabel");
|
|
2163
|
+
|
|
2164
|
+
const activeEl = document.getElementById("kpiActive");
|
|
2165
|
+
activeEl.textContent = String(activeCount);
|
|
2166
|
+
activeEl.className = "kpi-value " + (activeCount > 0 ? "ok" : "muted");
|
|
2167
|
+
document.getElementById("kpiActiveFoot").textContent = t("kpiActiveFoot");
|
|
2168
|
+
|
|
2169
|
+
document.getElementById("kpiPersisted").textContent = String(persistedCount);
|
|
2170
|
+
document.getElementById("kpiPersistedFoot").textContent =
|
|
2171
|
+
t("kpiPersistedFoot") + (managedCount > 0 ? " \xB7 " + managedCount + " " + t("managedLabel") : "");
|
|
2172
|
+
|
|
2173
|
+
const orphanEl = document.getElementById("kpiOrphan");
|
|
2174
|
+
orphanEl.textContent = String(orphanCount);
|
|
2175
|
+
orphanEl.className = "kpi-value " + (orphanCount > 0 ? "warn" : "muted");
|
|
2176
|
+
document.getElementById("kpiOrphanFoot").textContent = t("kpiOrphanFoot");
|
|
2177
|
+
|
|
2178
|
+
document.getElementById("kpiUptime").textContent = fmtDuration(data.bridge?.uptime);
|
|
2179
|
+
document.getElementById("kpiUptimeFoot").textContent = t("kpiUptimeFoot");
|
|
2180
|
+
}
|
|
2181
|
+
|
|
1144
2182
|
function render(data) {
|
|
1145
2183
|
lastData = data;
|
|
1146
2184
|
document.getElementById("refreshTime").textContent = fmtTime(data.collectedAt);
|
|
1147
2185
|
document.getElementById("bridgeHostname").textContent = data.bridge?.hostname || "-";
|
|
1148
2186
|
document.getElementById("bridgeVersion").textContent = "v" + (data.bridge?.version || "-");
|
|
1149
2187
|
document.getElementById("langBtn").textContent = t("langName");
|
|
1150
|
-
document.getElementById("
|
|
2188
|
+
document.getElementById("pageTitle").textContent = t("pageTitle");
|
|
2189
|
+
document.getElementById("pageSub").textContent = t("pageSub");
|
|
1151
2190
|
|
|
1152
2191
|
// Section titles
|
|
1153
|
-
document.getElementById("sectionSysRes").
|
|
1154
|
-
document.getElementById("sectionSessions").
|
|
2192
|
+
document.getElementById("sectionSysRes").textContent = t("secSysRes");
|
|
2193
|
+
document.getElementById("sectionSessions").textContent = t("secSessions");
|
|
1155
2194
|
document.getElementById("unknownLabel").textContent = " " + t("secUnknown");
|
|
1156
|
-
document.getElementById("sectionOrphans").
|
|
1157
|
-
document.getElementById("sectionPersisted").
|
|
2195
|
+
document.getElementById("sectionOrphans").textContent = t("secOrphans");
|
|
2196
|
+
document.getElementById("sectionPersisted").textContent = t("secPersisted");
|
|
1158
2197
|
document.getElementById("orphanBannerText").textContent = t("orphanBanner");
|
|
1159
2198
|
if (document.getElementById("unknownTable").style.display !== "none") {
|
|
1160
2199
|
document.getElementById("unknownIcon").classList.add("open");
|
|
1161
2200
|
}
|
|
1162
2201
|
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
[t("fldVersion"), data.bridge?.version || "-"],
|
|
1169
|
-
[t("fldHost"), data.bridge?.hostname || "-"],
|
|
1170
|
-
[t("fldPlatform"), (data.bridge?.platform || "") + " " + (data.bridge?.arch || "")],
|
|
1171
|
-
[t("fldUptime"), fmtDuration(data.bridge?.uptime)],
|
|
1172
|
-
[t("fldNode"), data.bridge?.nodeVersion || "-"],
|
|
1173
|
-
]},
|
|
1174
|
-
{ title: t("cardRegistration"), items: [
|
|
1175
|
-
[t("fldStatus"), '<span class="' + statusClass(r.registered) + '">' + (r.registered ? t("stRegistered") : t("stNotRegistered")) + "</span>"],
|
|
1176
|
-
[t("fldInstanceId"), r.instanceId || "-"],
|
|
1177
|
-
[t("fldLastAttempt"), fmtTime(r.lastAttempt)],
|
|
1178
|
-
[t("fldError"), r.error ? '<span class="status-red">' + r.error + "</span>" : t("stNone")],
|
|
1179
|
-
]},
|
|
1180
|
-
{ title: t("cardBinding"), items: [
|
|
1181
|
-
[t("fldStatus"), '<span class="' + statusClass(b.paired) + '">' + (b.paired ? t("stPaired") : t("stUnpaired")) + "</span>"],
|
|
1182
|
-
[t("fldInstanceId"), b.instanceId || "-"],
|
|
1183
|
-
[t("fldScheduler"), b.schedulerBaseUrl || "-"],
|
|
1184
|
-
[t("fldPairingCode"), b.pairingCode || "-"],
|
|
1185
|
-
]},
|
|
1186
|
-
{ title: t("cardSessions"), items: [
|
|
1187
|
-
[t("fldActiveSessions"), '<span class="' + ((data.sessions?.activeCount ?? 0) > 0 ? "status-green" : "status-muted") + '">' + (data.sessions?.activeCount ?? 0) + "</span>"],
|
|
1188
|
-
[t("fldPersistedSessions"), (data.sessions?.persistedCount ?? 0)],
|
|
1189
|
-
[t("fldManagedProcesses"), data.processes?.managed ?? 0],
|
|
1190
|
-
[t("fldOrphanProcesses"), '<span class="' + ((data.processes?.orphan ?? 0) > 0 ? "status-orange" : "status-muted") + '">' + (data.processes?.orphan ?? 0) + "</span>"],
|
|
1191
|
-
[t("fldRegistryEntries"), data.processes?.registryCount ?? 0],
|
|
1192
|
-
]},
|
|
1193
|
-
];
|
|
1194
|
-
|
|
1195
|
-
document.getElementById("statusCards").innerHTML = cards.map(c => {
|
|
1196
|
-
const rows = c.items.map(([k, v]) =>
|
|
1197
|
-
'<div class="stat-row"><span class="stat-label">' + k + '</span><span class="stat-value">' + v + '</span></div>'
|
|
1198
|
-
).join("");
|
|
1199
|
-
return '<div class="card"><div class="card-title">' + c.title + '</div>' + rows + '</div>';
|
|
1200
|
-
}).join("");
|
|
1201
|
-
|
|
1202
|
-
// Metrics
|
|
2202
|
+
renderHero(data);
|
|
2203
|
+
renderKpis(data);
|
|
2204
|
+
renderAttention(data);
|
|
2205
|
+
|
|
2206
|
+
// Metrics \u2014 \u56DB\u4E2A\u4EEA\u8868\u6A2A\u5411\u5BF9\u9F50\uFF0C\u8BFB\u6570\u7528\u5927\u5B57\uFF0C\u660E\u7EC6\u7528\u5C0F\u5B57
|
|
1203
2207
|
const m = data.metrics;
|
|
1204
2208
|
const metricsGrid = document.getElementById("metricsGrid");
|
|
2209
|
+
const metricsCard = document.getElementById("metricsCard");
|
|
1205
2210
|
if (m && m.ok) {
|
|
1206
2211
|
const cpuPct = m.cpu?.usagePercent ?? 0;
|
|
1207
2212
|
const memPct = m.memory?.usagePercent ?? 0;
|
|
1208
2213
|
const storagePct = m.storage?.usagePercent ?? 0;
|
|
2214
|
+
// \u5B58\u50A8\u8DEF\u5F84\u53EA\u53D6\u6700\u540E\u4E00\u6BB5\uFF08\u5982\u6839\u76D8\u53EA\u663E\u793A\u76D8\u7B26\uFF09\uFF0C\u5B8C\u6574\u8DEF\u5F84\u653E title
|
|
2215
|
+
const storageLabel = m.storage?.path ? fmtShortPath(m.storage.path).split("/").pop() : "";
|
|
2216
|
+
metricsCard.classList.remove("metrics-error");
|
|
1209
2217
|
metricsGrid.innerHTML = [
|
|
1210
|
-
{
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
2218
|
+
{
|
|
2219
|
+
name: "CPU",
|
|
2220
|
+
reading: fmtPercent(cpuPct),
|
|
2221
|
+
usage: cpuPct,
|
|
2222
|
+
lines: [
|
|
2223
|
+
[t("fldCores"), m.cpu?.coreCount ?? "-"],
|
|
2224
|
+
[t("fldModel"), (m.cpu?.model || "-").substring(0, 36)],
|
|
2225
|
+
],
|
|
2226
|
+
},
|
|
2227
|
+
{
|
|
2228
|
+
name: "Memory",
|
|
2229
|
+
reading: fmtPercent(memPct),
|
|
2230
|
+
usage: memPct,
|
|
2231
|
+
lines: [
|
|
2232
|
+
[t("fldUsed"), fmtBytes(m.memory?.usedBytes)],
|
|
2233
|
+
[t("fldTotal"), fmtBytes(m.memory?.totalBytes)],
|
|
2234
|
+
],
|
|
2235
|
+
},
|
|
2236
|
+
{
|
|
2237
|
+
name: "Storage",
|
|
2238
|
+
reading: fmtPercent(storagePct),
|
|
2239
|
+
usage: storagePct,
|
|
2240
|
+
lines: [
|
|
2241
|
+
[t("fldFree"), m.storage ? m.storage.freeGiB + " GiB" : "-"],
|
|
2242
|
+
[t("fldTotal"), m.storage ? m.storage.totalGiB + " GiB" : "-"],
|
|
2243
|
+
],
|
|
2244
|
+
// \u53EA\u663E\u793A\u672B\u4E24\u6BB5\u8DEF\u5F84\uFF0C\u5B8C\u6574\u8DEF\u5F84\u653E title \u5C5E\u6027\uFF08\u65E7\u7248\u628A\u6574\u6761\u7EDD\u5BF9\u8DEF\u5F84\u585E\u8FDB\u6807\u9898\u5BFC\u81F4\u6362\u884C\uFF09
|
|
2245
|
+
foot: storageLabel,
|
|
2246
|
+
footFull: m.storage?.path || "",
|
|
2247
|
+
},
|
|
2248
|
+
{
|
|
2249
|
+
name: "Network",
|
|
2250
|
+
// \u4E0A\u4E0B\u884C\u901F\u7387\u662F\u77AC\u65F6\u503C\uFF0C0 \u4E0E"\u65E0\u6570\u636E"\u8BED\u4E49\u4E0D\u540C\uFF1A0 \u662F\u771F\u5B9E\u8BFB\u6570\uFF0C\u4E0D\u5E94\u663E\u793A\u4E3A -
|
|
2251
|
+
reading: fmtBytes(m.network?.downloadBytesPerSecond ?? 0) + "/s",
|
|
2252
|
+
muted: true,
|
|
2253
|
+
usage: null,
|
|
2254
|
+
lines: [
|
|
2255
|
+
[t("fldUpload"), fmtBytes(m.network?.uploadBytesPerSecond ?? 0) + "/s"],
|
|
2256
|
+
[t("fldTotalDown"), fmtBytes(m.network?.downloadTotalBytes)],
|
|
2257
|
+
],
|
|
2258
|
+
},
|
|
1232
2259
|
].map(c => {
|
|
2260
|
+
// \u65E0\u767E\u5206\u6BD4\u8BFB\u6570\u7684\u4EEA\u8868\uFF08Network\uFF09\u4E5F\u5360\u540C\u6837\u9AD8\u5EA6\u7684\u8FDB\u5EA6\u6761\u4F4D\uFF0C
|
|
2261
|
+
// \u5426\u5219\u56DB\u5217\u9AD8\u5EA6\u4E0D\u9F50\uFF0C\u5E95\u90E8\u5BF9\u9F50\u4F1A\u88AB\u7834\u574F
|
|
1233
2262
|
const barHtml = c.usage != null
|
|
1234
|
-
? '<div class="bar
|
|
1235
|
-
: "";
|
|
1236
|
-
const
|
|
1237
|
-
'<div class="
|
|
2263
|
+
? '<div class="bar"><div class="bar-fill ' + barColor(c.usage) + '" style="width:' + Math.min(c.usage, 100) + '%"></div></div>'
|
|
2264
|
+
: '<div class="bar bar-placeholder"></div>';
|
|
2265
|
+
const lines = c.lines.map(([k, v]) =>
|
|
2266
|
+
'<div class="gauge-line"><dt>' + escHtml(k) + '</dt><dd>' + escHtml(v) + '</dd></div>'
|
|
1238
2267
|
).join("");
|
|
1239
|
-
return '<div class="
|
|
2268
|
+
return '<div class="gauge">' +
|
|
2269
|
+
'<div class="gauge-head">' +
|
|
2270
|
+
'<span class="gauge-name">' + escHtml(c.name) + '</span>' +
|
|
2271
|
+
'<span class="gauge-reading' + (c.muted ? " muted" : "") + '">' + escHtml(c.reading) + '</span>' +
|
|
2272
|
+
'</div>' +
|
|
2273
|
+
barHtml +
|
|
2274
|
+
'<dl class="gauge-lines">' + lines + '</dl>' +
|
|
2275
|
+
(c.foot
|
|
2276
|
+
? '<div class="gauge-line" style="margin-top:6px"><dt style="font-size:11px" title="' + escHtml(c.footFull || c.foot) + '">' + escHtml(c.foot) + '</dt></div>'
|
|
2277
|
+
: '') +
|
|
2278
|
+
'</div>';
|
|
1240
2279
|
}).join("");
|
|
1241
2280
|
} else {
|
|
1242
|
-
metricsGrid.innerHTML =
|
|
2281
|
+
metricsGrid.innerHTML =
|
|
2282
|
+
'<div class="empty" style="grid-column:1/-1">' +
|
|
2283
|
+
'<span class="empty-icon">–</span>' +
|
|
2284
|
+
escHtml(t("metricsUnavailable")) +
|
|
2285
|
+
'</div>';
|
|
1243
2286
|
}
|
|
1244
2287
|
|
|
1245
2288
|
// Build process lookup maps from process list
|
|
@@ -1262,20 +2305,24 @@ function render(data) {
|
|
|
1262
2305
|
|
|
1263
2306
|
// Active sessions (primary table, with PID from process cross-reference)
|
|
1264
2307
|
const sessions = data.sessions?.active || [];
|
|
1265
|
-
document.getElementById("sessionCount").textContent = "(" + sessions.length + ")";
|
|
2308
|
+
document.getElementById("sessionCount").textContent = sessions.length ? "(" + sessions.length + ")" : "";
|
|
1266
2309
|
if (sessions.length === 0) {
|
|
1267
|
-
document.getElementById("sessionTable").innerHTML =
|
|
2310
|
+
document.getElementById("sessionTable").innerHTML =
|
|
2311
|
+
'<div class="empty"><span class="empty-icon">○</span>' + escHtml(t("noSessions")) +
|
|
2312
|
+
'<span class="empty-hint">' + escHtml(t("kpiActiveFoot")) + '</span></div>';
|
|
1268
2313
|
} else {
|
|
1269
2314
|
document.getElementById("sessionTable").innerHTML =
|
|
1270
2315
|
'<table><thead><tr><th>' + t("thAgentId") + '</th><th>' + t("thPid") + '</th><th>' + t("thStatus") + '</th><th>' + t("thAction") + '</th><th>' + t("thWorkspace") + '</th><th>' + t("thProvider") + '</th></tr></thead><tbody>' +
|
|
1271
2316
|
sessions.map(s => {
|
|
1272
2317
|
const pid = pidByAgentId[s.agentId];
|
|
1273
2318
|
const st = s.runtimeStatus || "running";
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
+ '<td>' + (
|
|
1277
|
-
+ '<td
|
|
1278
|
-
+ '<td>' + (s.
|
|
2319
|
+
const ws = s.workspacePath ? s.workspacePath.replace(/\\\\/g, "/").split("/").pop() : "-";
|
|
2320
|
+
return '<tr><td class="strong mono">' + escHtml(s.agentId || "-") + '</td>'
|
|
2321
|
+
+ '<td>' + (pid ? '<code>' + escHtml(String(pid)) + '</code>' : '<span class="status-muted">-</span>') + '</td>'
|
|
2322
|
+
+ '<td><span class="' + statusClass(st) + '">' + escHtml(t("st" + st.charAt(0).toUpperCase() + st.slice(1)) || st) + '</span></td>'
|
|
2323
|
+
+ '<td>' + escHtml(s.runtimeActionLabel || "-") + '</td>'
|
|
2324
|
+
+ '<td title="' + escHtml(s.workspacePath || "") + '">' + escHtml(ws) + '</td>'
|
|
2325
|
+
+ '<td>' + escHtml(s.providerId || "-") + '</td></tr>';
|
|
1279
2326
|
}).join("") +
|
|
1280
2327
|
'</tbody></table>';
|
|
1281
2328
|
}
|
|
@@ -1288,7 +2335,8 @@ function render(data) {
|
|
|
1288
2335
|
document.getElementById("orphanTable").innerHTML =
|
|
1289
2336
|
'<table><thead><tr><th>' + t("thAgentId") + '</th><th>' + t("thPid") + '</th></tr></thead><tbody>' +
|
|
1290
2337
|
orphanProcs.map(p =>
|
|
1291
|
-
'<tr><td>' + (p.agentId
|
|
2338
|
+
'<tr><td class="mono">' + (p.agentId ? escHtml(p.agentId) : '<span class="status-muted">-</span>') + '</td>'
|
|
2339
|
+
+ '<td><code>' + escHtml(String(p.pid)) + '</code></td></tr>'
|
|
1292
2340
|
).join("") +
|
|
1293
2341
|
'</tbody></table>';
|
|
1294
2342
|
} else {
|
|
@@ -1296,33 +2344,42 @@ function render(data) {
|
|
|
1296
2344
|
}
|
|
1297
2345
|
|
|
1298
2346
|
// Unknown processes (collapsible, hidden by default)
|
|
1299
|
-
document.getElementById("unknownCount").textContent = "(" + unknownProcs.length + ")";
|
|
2347
|
+
document.getElementById("unknownCount").textContent = unknownProcs.length ? "(" + unknownProcs.length + ")" : "";
|
|
1300
2348
|
const unknownTable = document.getElementById("unknownTable");
|
|
1301
2349
|
if (unknownProcs.length === 0) {
|
|
1302
|
-
unknownTable.innerHTML =
|
|
2350
|
+
unknownTable.innerHTML =
|
|
2351
|
+
'<div class="empty"><span class="empty-icon">○</span>' + escHtml(t("noProcesses")) + '</div>';
|
|
1303
2352
|
} else {
|
|
1304
2353
|
unknownTable.innerHTML =
|
|
1305
2354
|
'<table><thead><tr><th>' + t("thPid") + '</th><th>' + t("thCommand") + '</th><th>' + t("thDirectory") + '</th><th>' + t("thStarted") + '</th><th>' + t("thAction") + '</th></tr></thead><tbody>' +
|
|
1306
2355
|
unknownProcs.map(p =>
|
|
1307
|
-
'<tr><td><code>' +
|
|
2356
|
+
'<tr><td><code>' + escHtml(String(p.pid)) + '</code></td>'
|
|
2357
|
+
+ '<td class="mono">' + escHtml(p.command || "-") + '</td>'
|
|
2358
|
+
+ '<td title="' + escHtml(p.cwd || "") + '">' + escHtml(fmtShortPath(p.cwd)) + '</td>'
|
|
2359
|
+
+ '<td>' + (p.startedAt ? escHtml(fmtShortDate(p.startedAt)) : "-") + '</td>'
|
|
2360
|
+
+ '<td><button class="btn-kill" data-pid="' + escHtml(String(p.pid)) + '">' + t("btnKill") + '</button></td></tr>'
|
|
1308
2361
|
).join("") +
|
|
1309
2362
|
'</tbody></table>';
|
|
1310
2363
|
}
|
|
1311
2364
|
|
|
1312
2365
|
// Persisted sessions
|
|
1313
2366
|
const persisted = data.sessions?.persisted || [];
|
|
1314
|
-
document.getElementById("persistedCount").textContent = "(" + persisted.length + ")";
|
|
2367
|
+
document.getElementById("persistedCount").textContent = persisted.length ? "(" + persisted.length + ")" : "";
|
|
1315
2368
|
if (persisted.length === 0) {
|
|
1316
|
-
document.getElementById("persistedTable").innerHTML =
|
|
2369
|
+
document.getElementById("persistedTable").innerHTML =
|
|
2370
|
+
'<div class="empty"><span class="empty-icon">○</span>' + escHtml(t("noPersisted")) + '</div>';
|
|
1317
2371
|
} else {
|
|
1318
2372
|
document.getElementById("persistedTable").innerHTML =
|
|
1319
2373
|
'<table><thead><tr><th>' + t("thAgentId") + '</th><th>' + t("thStatus") + '</th><th>' + t("thWorkspace") + '</th><th>' + t("thCommand") + '</th><th>' + t("thStarted") + '</th></tr></thead><tbody>' +
|
|
1320
|
-
persisted.map(s =>
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
2374
|
+
persisted.map(s => {
|
|
2375
|
+
const ws = s.workspacePath ? s.workspacePath.replace(/\\\\/g, "/").split("/").pop() : "-";
|
|
2376
|
+
const status = s.status || "stopped";
|
|
2377
|
+
return '<tr><td class="strong mono">' + escHtml(s.agentId || "-") + '</td>'
|
|
2378
|
+
+ '<td><span class="tag ' + (status === "running" ? "green" : "gray") + '">' + escHtml(status) + '</span></td>'
|
|
2379
|
+
+ '<td title="' + escHtml(s.workspacePath || "") + '">' + escHtml(ws) + '</td>'
|
|
2380
|
+
+ '<td class="mono">' + escHtml(s.command || "-") + '</td>'
|
|
2381
|
+
+ '<td>' + escHtml(fmtTime(s.startedAt)) + '</td></tr>';
|
|
2382
|
+
}).join("") +
|
|
1326
2383
|
'</tbody></table>';
|
|
1327
2384
|
}
|
|
1328
2385
|
}
|
|
@@ -1503,6 +2560,7 @@ function renderSettingsUI() {
|
|
|
1503
2560
|
document.getElementById("descRestart").textContent = t("setRestartDesc");
|
|
1504
2561
|
document.getElementById("btnRestartBridge").textContent = t("setRestartBridge");
|
|
1505
2562
|
document.getElementById("navNode").textContent = t("nodeTitle");
|
|
2563
|
+
document.getElementById("btnRefreshNodeInfo").textContent = t("nodeRefresh");
|
|
1506
2564
|
document.getElementById("navLogs").textContent = t("logTitle");
|
|
1507
2565
|
document.getElementById("navScheduler").textContent = t("schedTitle");
|
|
1508
2566
|
document.getElementById("lblNodeHostname").textContent = t("nodeHostname");
|
|
@@ -1541,10 +2599,10 @@ function renderSettingsUI() {
|
|
|
1541
2599
|
document.getElementById("optTunnelOff").textContent = t("schedTunnelOff");
|
|
1542
2600
|
document.getElementById("lblSchedInstanceId").textContent = t("schedInstanceId");
|
|
1543
2601
|
document.getElementById("hintSchedInstanceId").textContent = t("schedInstanceIdHint");
|
|
2602
|
+
document.getElementById("lblSchedAdvanced").textContent = t("schedAdvanced");
|
|
2603
|
+
updateSchedAdvancedBadge();
|
|
1544
2604
|
document.getElementById("btnSaveServerConfig").textContent = t("schedSave");
|
|
1545
|
-
document.getElementById("btnSaveRestart").textContent = t("schedSaveAndRestart");
|
|
1546
2605
|
document.getElementById("descSched").textContent = t("schedDesc");
|
|
1547
|
-
document.getElementById("btnReRegister").textContent = t("schedReRegister");
|
|
1548
2606
|
document.getElementById("navAccess").textContent = t("accTitle");
|
|
1549
2607
|
document.getElementById("lblAccessIps").textContent = t("accAllowIps");
|
|
1550
2608
|
document.getElementById("hintAccessIps").textContent = t("accAllowIpsHint");
|
|
@@ -1656,6 +2714,8 @@ function confirmRestartBridge() {
|
|
|
1656
2714
|
// \u2500\u2500 Node Info \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1657
2715
|
let nodeInfoTimer = null;
|
|
1658
2716
|
function refreshNodeInfo() {
|
|
2717
|
+
var btn = document.getElementById("btnRefreshNodeInfo");
|
|
2718
|
+
if (btn) { btn.disabled = true; btn.textContent = t("logRefreshing"); }
|
|
1659
2719
|
fetch("/runtime/node-info").then(function(r) { return r.json(); }).then(function(d) {
|
|
1660
2720
|
if (!d.ok) return;
|
|
1661
2721
|
document.getElementById("valNodeHostname").textContent = d.hostname;
|
|
@@ -1670,7 +2730,12 @@ function refreshNodeInfo() {
|
|
|
1670
2730
|
document.getElementById("valNodeRegistered").textContent = d.registered ? t("nodeRegYes") : t("nodeRegNo");
|
|
1671
2731
|
document.getElementById("valNodeInstanceId").textContent = d.instanceId || "-";
|
|
1672
2732
|
document.getElementById("valNodeActiveSessions").textContent = d.activeSessions;
|
|
1673
|
-
}).catch(function() {})
|
|
2733
|
+
}).catch(function() {}).then(function() {
|
|
2734
|
+
// \u6210\u529F/\u5931\u8D25\u90FD\u6062\u590D\u6309\u94AE\uFF1B\u6210\u529F\u540E\u77ED\u6682\u663E\u793A\u5B8C\u6210\u6001\u518D\u8FD8\u539F\uFF0C\u7ED9\u7528\u6237"\u786E\u5B9E\u5237\u65B0\u4E86"\u7684\u53CD\u9988
|
|
2735
|
+
if (!btn) return;
|
|
2736
|
+
btn.disabled = false;
|
|
2737
|
+
btn.textContent = t("nodeRefresh");
|
|
2738
|
+
});
|
|
1674
2739
|
}
|
|
1675
2740
|
|
|
1676
2741
|
// \u2500\u2500 Logs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -1746,8 +2811,11 @@ function renderSchedTargetList() {
|
|
|
1746
2811
|
var meta = document.createElement("div");
|
|
1747
2812
|
meta.className = "s-target-meta";
|
|
1748
2813
|
var parts = [];
|
|
2814
|
+
// \u540C\u4E00\u5730\u5740\u53EF\u914D\u591A\u884C\uFF08\u4E0D\u540C API Key = \u4E0D\u540C\u8D26\u53F7/\u79DF\u6237\uFF09\uFF0C\u5FC5\u987B\u8BA9\u7528\u6237\u770B\u5F97\u51FA\u662F\u54EA\u4E00\u6761
|
|
2815
|
+
if (item.userKeyMasked) parts.push(item.userKeyMasked);
|
|
1749
2816
|
if (item.instanceName) parts.push(item.instanceName);
|
|
1750
2817
|
if (item.instanceId) parts.push("ID: " + item.instanceId);
|
|
2818
|
+
if (item.tenantId) parts.push(t("schedTenantShort") + ": " + item.tenantId);
|
|
1751
2819
|
if (item.tunnel === true) parts.push("tunnel");
|
|
1752
2820
|
else if (item.tunnel === false && item.registerAddr) parts.push("direct");
|
|
1753
2821
|
meta.textContent = parts.join(" \xB7 ");
|
|
@@ -1755,6 +2823,18 @@ function renderSchedTargetList() {
|
|
|
1755
2823
|
main.appendChild(meta);
|
|
1756
2824
|
var actions = document.createElement("div");
|
|
1757
2825
|
actions.className = "s-target-actions";
|
|
2826
|
+
// \u590D\u5236\u8FDE\u63A5\u5BC6\u94A5\uFF1A\u7528\u6237\u8981\u5728 AwS \u9762\u677F\u767B\u8BB0\u8FD9\u53F0 bridge \u65F6\u5FC5\u987B\u586B\u5B83\uFF0C\u800C\u5217\u8868\u53EA\u663E\u793A\u63A9\u7801\uFF0C
|
|
2827
|
+
// \u6CA1\u6709\u8FD9\u4E2A\u5165\u53E3\u5C31\u53EA\u80FD\u53BB\u673A\u5668\u4E0A cat config.json\u3002\u4EC5\u5728\u5DF2\u914D\u7F6E\u5BC6\u94A5\u65F6\u51FA\u73B0\u3002
|
|
2828
|
+
if (item.connectionKeySet) {
|
|
2829
|
+
var copyBtn = document.createElement("button");
|
|
2830
|
+
copyBtn.className = "s-btn s-btn-sm s-btn-secondary";
|
|
2831
|
+
copyBtn.textContent = t("schedCopyConnKey");
|
|
2832
|
+
copyBtn.onclick = function(ev) {
|
|
2833
|
+
ev.stopPropagation();
|
|
2834
|
+
copyTargetConnectionKey(item.targetKey, copyBtn);
|
|
2835
|
+
};
|
|
2836
|
+
actions.appendChild(copyBtn);
|
|
2837
|
+
}
|
|
1758
2838
|
var editBtn = document.createElement("button");
|
|
1759
2839
|
editBtn.className = "s-btn s-btn-sm s-btn-secondary";
|
|
1760
2840
|
editBtn.textContent = t("schedEditTarget");
|
|
@@ -1768,6 +2848,60 @@ function renderSchedTargetList() {
|
|
|
1768
2848
|
}
|
|
1769
2849
|
}
|
|
1770
2850
|
|
|
2851
|
+
// \u6309 targetKey \u53D6\u8BE5\u76EE\u6807\u7684\u660E\u6587\u8FDE\u63A5\u5BC6\u94A5\u5E76\u590D\u5236\u5230\u526A\u8D34\u677F\u3002
|
|
2852
|
+
// \u53EA\u5728\u9762\u677F\u5DF2\u767B\u5F55\u65F6\u53EF\u7528\uFF08\u540E\u7AEF requirePanelAuth\uFF09\uFF1B\u6309\u94AE\u6587\u6848\u5728\u6210\u529F/\u5931\u8D25\u540E\u77ED\u6682\u53D8\u5316\u7ED9\u51FA\u53CD\u9988\u3002
|
|
2853
|
+
function copyTargetConnectionKey(targetKey, btn) {
|
|
2854
|
+
var original = btn.textContent;
|
|
2855
|
+
btn.disabled = true;
|
|
2856
|
+
fetch("/runtime/server-config/connection-key?targetKey=" + encodeURIComponent(targetKey), {
|
|
2857
|
+
credentials: "same-origin",
|
|
2858
|
+
})
|
|
2859
|
+
.then(function(response) {
|
|
2860
|
+
if (!response.ok) {
|
|
2861
|
+
return response.json().then(function(data) {
|
|
2862
|
+
throw new Error((data && data.error) || ("HTTP " + response.status));
|
|
2863
|
+
});
|
|
2864
|
+
}
|
|
2865
|
+
return response.json();
|
|
2866
|
+
})
|
|
2867
|
+
.then(function(data) {
|
|
2868
|
+
var key = String((data && data.connectionKey) || "");
|
|
2869
|
+
if (!key) throw new Error("empty key");
|
|
2870
|
+
return copyTextToClipboard(key).then(function() {
|
|
2871
|
+
btn.textContent = "\u2713";
|
|
2872
|
+
setTimeout(function() { btn.textContent = original; btn.disabled = false; }, 1500);
|
|
2873
|
+
});
|
|
2874
|
+
})
|
|
2875
|
+
.catch(function(error) {
|
|
2876
|
+
btn.textContent = "\u590D\u5236\u5931\u8D25";
|
|
2877
|
+
console.warn("[Panel] \u590D\u5236\u8FDE\u63A5\u5BC6\u94A5\u5931\u8D25:", error);
|
|
2878
|
+
setTimeout(function() { btn.textContent = original; btn.disabled = false; }, 2000);
|
|
2879
|
+
});
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
// \u4E0E\u6D4F\u89C8\u5668\u526A\u8D34\u677F API \u4EA4\u4E92\uFF1B\u4E0D\u53EF\u7528\u65F6\u56DE\u9000\u5230\u9690\u85CF textarea + execCommand\u3002
|
|
2883
|
+
function copyTextToClipboard(text) {
|
|
2884
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
2885
|
+
return navigator.clipboard.writeText(text);
|
|
2886
|
+
}
|
|
2887
|
+
return new Promise(function(resolve, reject) {
|
|
2888
|
+
try {
|
|
2889
|
+
var area = document.createElement("textarea");
|
|
2890
|
+
area.value = text;
|
|
2891
|
+
area.setAttribute("readonly", "");
|
|
2892
|
+
area.style.position = "fixed";
|
|
2893
|
+
area.style.opacity = "0";
|
|
2894
|
+
document.body.appendChild(area);
|
|
2895
|
+
area.select();
|
|
2896
|
+
var ok = document.execCommand("copy");
|
|
2897
|
+
document.body.removeChild(area);
|
|
2898
|
+
ok ? resolve() : reject(new Error("execCommand copy failed"));
|
|
2899
|
+
} catch (error) {
|
|
2900
|
+
reject(error);
|
|
2901
|
+
}
|
|
2902
|
+
});
|
|
2903
|
+
}
|
|
2904
|
+
|
|
1771
2905
|
// \u5C06\u6307\u5B9A target \u8F7D\u5165\u8868\u5355\uFF08\u7F16\u8F91\u6A21\u5F0F\uFF09\uFF1A\u5BC6\u94A5\u7559\u7A7A=\u4FDD\u6301\u4E0D\u53D8\uFF08placeholder \u663E\u793A\u63A9\u7801\uFF09
|
|
1772
2906
|
function loadTargetToForm(idx) {
|
|
1773
2907
|
schedEditIndex = idx;
|
|
@@ -1786,9 +2920,66 @@ function loadTargetToForm(idx) {
|
|
|
1786
2920
|
ck.placeholder = item.connectionKeyMasked || "";
|
|
1787
2921
|
document.getElementById("valSchedInstanceId").textContent = item.instanceId || "-";
|
|
1788
2922
|
document.getElementById("btnDeleteTarget").style.display = "";
|
|
2923
|
+
// \u7F16\u8F91\u5DF2\u914D\u7F6E\u8FC7\u53EF\u9009\u9879\u7684\u76EE\u6807\u65F6\u81EA\u52A8\u5C55\u5F00\uFF0C\u8BA9\u7528\u6237\u770B\u5230\u73B0\u72B6\uFF1B\u5168\u65B0\u586B\u5199\u4FDD\u6301\u6536\u7EB3
|
|
2924
|
+
setSchedAdvancedOpen(countFilledAdvanced(item) > 0);
|
|
2925
|
+
updateSchedAdvancedBadge();
|
|
1789
2926
|
renderSchedTargetList();
|
|
1790
2927
|
}
|
|
1791
2928
|
|
|
2929
|
+
// \u2500\u2500 \u9AD8\u7EA7\u8BBE\u7F6E\u6298\u53E0\u533A \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2930
|
+
|
|
2931
|
+
/** \u7EDF\u8BA1\u53EF\u9009\u9879\u91CC\u5DF2\u6709\u5185\u5BB9\u7684\u5B57\u6BB5\u6570\uFF08\u5B9E\u4F8B\u540D\u79F0\u5728\u4E3B\u8868\u5355\u3001\u5B9E\u4F8B ID \u53EA\u8BFB\u56DE\u663E\uFF0C\u5747\u4E0D\u8BA1\u5165\uFF09\u3002 */
|
|
2932
|
+
function countFilledAdvanced(target) {
|
|
2933
|
+
var n = 0;
|
|
2934
|
+
if (target && String(target.tenantId || "").trim()) n += 1;
|
|
2935
|
+
if (target && String(target.connectionKeyMasked || "")) n += 1;
|
|
2936
|
+
if (target && String(target.registerAddr || "").trim()) n += 1;
|
|
2937
|
+
if (target && target.tunnel !== undefined && target.tunnel !== "auto" && target.tunnel !== null) n += 1;
|
|
2938
|
+
return n;
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
function isSchedAdvancedOpen() {
|
|
2942
|
+
var body = document.getElementById("schedAdvancedBody");
|
|
2943
|
+
return Boolean(body && !body.hidden);
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
function setSchedAdvancedOpen(open) {
|
|
2947
|
+
var body = document.getElementById("schedAdvancedBody");
|
|
2948
|
+
var head = document.getElementById("schedAdvancedHead");
|
|
2949
|
+
if (!body || !head) return;
|
|
2950
|
+
body.hidden = !open;
|
|
2951
|
+
head.setAttribute("aria-expanded", open ? "true" : "false");
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
function toggleSchedAdvanced() {
|
|
2955
|
+
setSchedAdvancedOpen(!isSchedAdvancedOpen());
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
// \u9AD8\u7EA7\u533A\u5185\u4EFB\u4F55\u8F93\u5165/\u9009\u62E9\u53D8\u5316\u90FD\u5237\u65B0\u6298\u53E0\u5934\u5FBD\u6807\uFF08\u4E8B\u4EF6\u59D4\u6258\uFF0C\u5B57\u6BB5\u589E\u51CF\u4E0D\u7528\u91CD\u6302\uFF09
|
|
2959
|
+
document.addEventListener("input", function(e) {
|
|
2960
|
+
if (!e.target || !e.target.closest) return;
|
|
2961
|
+
if (e.target.closest("#schedAdvancedBody")) updateSchedAdvancedBadge();
|
|
2962
|
+
});
|
|
2963
|
+
document.addEventListener("change", function(e) {
|
|
2964
|
+
if (!e.target || !e.target.closest) return;
|
|
2965
|
+
if (e.target.closest("#schedAdvancedBody")) updateSchedAdvancedBadge();
|
|
2966
|
+
});
|
|
2967
|
+
|
|
2968
|
+
/** \u6298\u53E0\u5934\u5FBD\u6807\uFF1A\u6536\u7EB3\u72B6\u6001\u4E0B\u63D0\u793A"\u91CC\u9762\u6709\u51E0\u4E2A\u53EF\u9009\u9879\u5DF2\u586B"\uFF0C\u8BA9\u7528\u6237\u4E0D\u5C55\u5F00\u4E5F\u77E5\u9053\u73B0\u72B6\u3002 */
|
|
2969
|
+
function updateSchedAdvancedBadge() {
|
|
2970
|
+
var el = document.getElementById("schedAdvancedCount");
|
|
2971
|
+
if (!el) return;
|
|
2972
|
+
var filled = countFilledAdvanced({
|
|
2973
|
+
tenantId: document.getElementById("cfgTenantId").value,
|
|
2974
|
+
connectionKeyMasked: document.getElementById("cfgConnectionKey").placeholder,
|
|
2975
|
+
registerAddr: document.getElementById("cfgRegisterAddr").value,
|
|
2976
|
+
tunnel: document.getElementById("cfgTunnel").value,
|
|
2977
|
+
});
|
|
2978
|
+
el.textContent = filled > 0
|
|
2979
|
+
? t("schedAdvancedN").replace("{n}", String(filled))
|
|
2980
|
+
: "";
|
|
2981
|
+
}
|
|
2982
|
+
|
|
1792
2983
|
// \u8FDB\u5165\u65B0\u589E\u6A21\u5F0F\uFF1A\u6E05\u7A7A\u8868\u5355
|
|
1793
2984
|
function startAddTarget() {
|
|
1794
2985
|
schedEditIndex = -1;
|
|
@@ -1800,10 +2991,76 @@ function startAddTarget() {
|
|
|
1800
2991
|
document.getElementById("cfgTunnel").value = "auto";
|
|
1801
2992
|
document.getElementById("valSchedInstanceId").textContent = "-";
|
|
1802
2993
|
document.getElementById("btnDeleteTarget").style.display = "none";
|
|
2994
|
+
// \u65B0\u589E\uFF1A\u53EF\u9009\u9879\u5168\u90E8\u4E3A\u7A7A\uFF0C\u9ED8\u8BA4\u6536\u7EB3\uFF08\u9884\u586B\u53EF\u80FD\u968F\u540E\u5C55\u5F00\uFF09
|
|
2995
|
+
setSchedAdvancedOpen(false);
|
|
2996
|
+
updateSchedAdvancedBadge();
|
|
1803
2997
|
hideSchedMsg();
|
|
2998
|
+
applyPrefillIfPending();
|
|
1804
2999
|
renderSchedTargetList();
|
|
1805
3000
|
}
|
|
1806
3001
|
|
|
3002
|
+
/**
|
|
3003
|
+
* Dashboard\u300C\u5F15\u5BFC\u914D\u7F6E\u300D\u8DF3\u8F6C\u53C2\u6570\uFF08URL hash\uFF09\uFF1A
|
|
3004
|
+
* #sched=add&serverUrl=...&instanceName=...&tunnel=auto
|
|
3005
|
+
*
|
|
3006
|
+
* \u5B89\u5168\u7EA6\u675F\uFF1A**\u7EDD\u4E0D\u63A5\u53D7 userKey / connectionKey**\u3002\u5BC6\u94A5\u53EA\u8D70\u7528\u6237\u624B\u8F93\u6216 config.json\uFF0C
|
|
3007
|
+
* \u653E\u8FDB URL \u4F1A\u7559\u5728\u6D4F\u89C8\u5668\u5386\u53F2\u3001\u4EE3\u7406\u65E5\u5FD7\u4E0E\u622A\u56FE\u91CC\u3002
|
|
3008
|
+
*/
|
|
3009
|
+
var schedPrefill = null;
|
|
3010
|
+
|
|
3011
|
+
function readSchedPrefill() {
|
|
3012
|
+
var raw = String(window.location.hash || "").replace(/^#/, "");
|
|
3013
|
+
if (!raw) return null;
|
|
3014
|
+
var params = {};
|
|
3015
|
+
raw.split("&").forEach(function(pair) {
|
|
3016
|
+
var idx = pair.indexOf("=");
|
|
3017
|
+
if (idx < 0) return;
|
|
3018
|
+
var key = decodeURIComponent(pair.slice(0, idx));
|
|
3019
|
+
// \u6CE8\u610F\uFF1A\u672C\u6BB5\u4F4D\u4E8E\u6A21\u677F\u5B57\u7B26\u4E32\u5185\uFF0C\u6B63\u5219\u91CC\u7684\u53CD\u659C\u6760\u5FC5\u987B\u5199\u4E24\u904D\uFF08\u5426\u5219\u4F1A\u88AB\u5F53\u6210\u8F6C\u4E49\u7B26\uFF09\u3002
|
|
3020
|
+
var value = decodeURIComponent(pair.slice(idx + 1).replace(/\\+/g, " "));
|
|
3021
|
+
params[key] = value;
|
|
3022
|
+
});
|
|
3023
|
+
if (params.sched !== "add") return null;
|
|
3024
|
+
var serverUrl = String(params.serverUrl || "").trim();
|
|
3025
|
+
if (!serverUrl) return null;
|
|
3026
|
+
var tunnel = String(params.tunnel || "auto");
|
|
3027
|
+
if (tunnel !== "true" && tunnel !== "false" && tunnel !== "auto") tunnel = "auto";
|
|
3028
|
+
return {
|
|
3029
|
+
serverUrl: serverUrl,
|
|
3030
|
+
instanceName: String(params.instanceName || "").trim(),
|
|
3031
|
+
tunnel: tunnel,
|
|
3032
|
+
};
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
/** \u628A\u5E26\u5165\u503C\u5199\u8FDB\u300C\u65B0\u589E\u300D\u8868\u5355\uFF08\u4EC5\u5728\u7528\u6237\u8FD8\u6CA1\u8F93\u5165\u5185\u5BB9\u65F6\u8986\u76D6\uFF0C\u907F\u514D\u6253\u65AD\u624B\u52A8\u7F16\u8F91\uFF09 */
|
|
3036
|
+
function applyPrefillIfPending() {
|
|
3037
|
+
if (!schedPrefill || schedEditIndex >= 0) return;
|
|
3038
|
+
var serverUrlInput = document.getElementById("cfgServerUrl");
|
|
3039
|
+
if (!serverUrlInput) return;
|
|
3040
|
+
if (!serverUrlInput.value.trim()) serverUrlInput.value = schedPrefill.serverUrl;
|
|
3041
|
+
var nameInput = document.getElementById("cfgInstanceName");
|
|
3042
|
+
if (nameInput && !nameInput.value.trim() && schedPrefill.instanceName) {
|
|
3043
|
+
nameInput.value = schedPrefill.instanceName;
|
|
3044
|
+
}
|
|
3045
|
+
var tunnelSelect = document.getElementById("cfgTunnel");
|
|
3046
|
+
if (tunnelSelect) tunnelSelect.value = schedPrefill.tunnel;
|
|
3047
|
+
updateSchedAdvancedBadge();
|
|
3048
|
+
showPrefillNotice();
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
function showPrefillNotice() {
|
|
3052
|
+
var notice = document.getElementById("schedPrefillNotice");
|
|
3053
|
+
if (!notice || !schedPrefill) return;
|
|
3054
|
+
notice.textContent =
|
|
3055
|
+
t("schedPrefillNotice").replace("{0}", schedPrefill.serverUrl);
|
|
3056
|
+
notice.style.display = "block";
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
function hidePrefillNotice() {
|
|
3060
|
+
var notice = document.getElementById("schedPrefillNotice");
|
|
3061
|
+
if (notice) notice.style.display = "none";
|
|
3062
|
+
}
|
|
3063
|
+
|
|
1807
3064
|
function hideSchedMsg() {
|
|
1808
3065
|
var msg = document.getElementById("schedMsg");
|
|
1809
3066
|
msg.className = "s-msg";
|
|
@@ -1816,14 +3073,16 @@ function showSchedMsg(text, ok) {
|
|
|
1816
3073
|
msg.className = "s-msg " + (ok ? "success" : "error");
|
|
1817
3074
|
}
|
|
1818
3075
|
|
|
1819
|
-
// \u8868\u5355 \u2192 target \u5BF9\u8C61\uFF08\u5BC6\u94A5\u539F\u6837\u643A\u5E26\uFF0C\u7559\u7A7A\u7531\u540E\u7AEF\u6309
|
|
3076
|
+
// \u8868\u5355 \u2192 target \u5BF9\u8C61\uFF08\u5BC6\u94A5\u539F\u6837\u643A\u5E26\uFF0C\u7559\u7A7A\u7531\u540E\u7AEF\u6309 originalTargetKey/\u539F\u5730\u5740\u7EE7\u627F\uFF09
|
|
1820
3077
|
function formToTarget() {
|
|
3078
|
+
var editing = schedEditIndex >= 0 ? schedTargets[schedEditIndex] : null;
|
|
1821
3079
|
return {
|
|
1822
3080
|
serverUrl: document.getElementById("cfgServerUrl").value.trim(),
|
|
1823
|
-
// \u7F16\u8F91\u6A21\u5F0F\u643A\u5E26\u539F\
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
3081
|
+
// \u7F16\u8F91\u6A21\u5F0F\u643A\u5E26\u539F\u8EAB\u4EFD\uFF1A\u540C\u5740\u53EF\u80FD\u6709\u591A\u884C\uFF08\u4E0D\u540C API Key\uFF09\uFF0C
|
|
3082
|
+
// originalTargetKey \u8BA9\u540E\u7AEF\u7CBE\u786E\u77E5\u9053\u8981\u7EE7\u627F\u54EA\u4E00\u6761\u7684 instanceId / \u672A\u66B4\u9732\u5B57\u6BB5
|
|
3083
|
+
originalTargetKey: editing ? (editing.targetKey || "") : "",
|
|
3084
|
+
// \u517C\u5BB9\u65E7\u540E\u7AEF/\u6539\u5730\u5740\u573A\u666F\uFF1A\u5730\u5740\u53D8\u66F4\u65F6\u6309\u539F\u5730\u5740\u7EE7\u627F\uFF08\u540C\u4E00\u5B9E\u4F8B\u6362\u5730\u5740\uFF0C\u65E0\u9700\u91CD\u65B0\u6CE8\u518C\uFF09
|
|
3085
|
+
originalServerUrl: editing ? (editing.serverUrl || "") : "",
|
|
1827
3086
|
userKey: document.getElementById("cfgUserKey").value,
|
|
1828
3087
|
tenantId: document.getElementById("cfgTenantId").value.trim(),
|
|
1829
3088
|
instanceName: document.getElementById("cfgInstanceName").value.trim(),
|
|
@@ -1844,6 +3103,40 @@ function refreshServerConfig() {
|
|
|
1844
3103
|
}).catch(function() {});
|
|
1845
3104
|
}
|
|
1846
3105
|
|
|
3106
|
+
/**
|
|
3107
|
+
* \u5F52\u4E00\u5316\u5730\u5740\u7528\u4E8E\u300C\u65B0\u589E\u65F6\u649E\u5DF2\u6709\u5730\u5740\u300D\u7684\u6BD4\u8F83\uFF08\u4E0E\u540E\u7AEF\u4E00\u81F4\uFF0C\u5FFD\u7565\u5C3E\u659C\u6760/\u534F\u8BAE\u5927\u5C0F\u5199/localhost\uFF09\u3002
|
|
3108
|
+
* \u6CE8\u610F\uFF1A\u672C\u6BB5\u4F4D\u4E8E\u6A21\u677F\u5B57\u7B26\u4E32\u5185\uFF0C\u6B63\u5219\u91CC\u7684\u53CD\u659C\u6760\u5FC5\u987B\u5199\u4E24\u904D\uFF08\u5426\u5219\u4F1A\u88AB\u5F53\u6210\u8F6C\u4E49\u7B26\uFF09\u3002
|
|
3109
|
+
*/
|
|
3110
|
+
function normalizeTargetUrlForCompare(raw) {
|
|
3111
|
+
var value = String(raw || "").trim();
|
|
3112
|
+
if (!value) return "";
|
|
3113
|
+
try {
|
|
3114
|
+
var url = new URL(value);
|
|
3115
|
+
url.protocol = url.protocol.toLowerCase();
|
|
3116
|
+
if (url.hostname.toLowerCase() === "localhost") url.hostname = "127.0.0.1";
|
|
3117
|
+
return url.origin;
|
|
3118
|
+
} catch (e) {
|
|
3119
|
+
return value.replace(/\\/+$/, "");
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
/**
|
|
3124
|
+
* \u65B0\u589E\u65F6\u67E5\u627E\u300C\u540C\u5730\u5740 + \u540C API Key\u300D\u7684\u5DF2\u6709\u6761\u76EE\uFF08-1 = \u65E0\u51B2\u7A81\uFF09\u3002
|
|
3125
|
+
* \u540C\u5730\u5740\u4E0D\u540C Key \u5C5E\u4E8E\u4E0D\u540C\u8D26\u53F7/\u79DF\u6237\uFF0C\u662F\u5408\u6CD5\u914D\u7F6E\uFF0C\u4E0D\u7B97\u91CD\u590D\uFF1B
|
|
3126
|
+
* \u53EA\u6709\u5730\u5740\u4E0E Key \u90FD\u76F8\u540C\u624D\u662F\u540C\u4E00\u4E2A\u76EE\u6807\u6CE8\u518C\u4E24\u6B21\u3002
|
|
3127
|
+
*/
|
|
3128
|
+
function findDuplicateTargetIndex(serverUrl, userKey) {
|
|
3129
|
+
var urlKey = normalizeTargetUrlForCompare(serverUrl);
|
|
3130
|
+
var keyText = String(userKey || "").trim();
|
|
3131
|
+
if (!urlKey || !keyText) return -1;
|
|
3132
|
+
for (var i = 0; i < schedTargets.length; i++) {
|
|
3133
|
+
if (normalizeTargetUrlForCompare(schedTargets[i].serverUrl) !== urlKey) continue;
|
|
3134
|
+
// \u5DF2\u6709\u6761\u76EE\u53EA\u56DE\u4F20\u63A9\u7801\uFF0C\u65E0\u6CD5\u6BD4\u5BF9\u660E\u6587 Key\uFF1A\u5730\u5740\u76F8\u540C\u5373\u63D0\u793A\uFF08\u7531\u540E\u7AEF\u505A\u6700\u7EC8\u5224\u5B9A\uFF09
|
|
3135
|
+
return i;
|
|
3136
|
+
}
|
|
3137
|
+
return -1;
|
|
3138
|
+
}
|
|
3139
|
+
|
|
1847
3140
|
// \u5168\u91CF\u63D0\u4EA4 targets\uFF1BrestartAfter \u4E3A true \u65F6\u4FDD\u5B58\u6210\u529F\u540E\u91CD\u542F Bridge
|
|
1848
3141
|
function saveServerConfig(restartAfter) {
|
|
1849
3142
|
var form = formToTarget();
|
|
@@ -1852,6 +3145,17 @@ function saveServerConfig(restartAfter) {
|
|
|
1852
3145
|
return Promise.resolve(false);
|
|
1853
3146
|
}
|
|
1854
3147
|
|
|
3148
|
+
// \u65B0\u589E\u6A21\u5F0F\u4E0B\u82E5\u5730\u5740\u5DF2\u5B58\u5728\uFF1A\u540C\u5740\u5141\u8BB8\u65B0\u589E\uFF08\u4E0D\u540C API Key = \u4E0D\u540C\u8D26\u53F7/\u79DF\u6237\uFF09\uFF0C
|
|
3149
|
+
// \u4F46\u540C\u4E00\u4E2A Key \u518D\u5B58\u4E00\u6B21\u6CA1\u6709\u610F\u4E49\u3002\u5DF2\u6709\u6761\u76EE\u53EA\u56DE\u4F20\u63A9\u7801\uFF0C\u65E0\u6CD5\u6BD4\u5BF9\u65B0\u586B\u7684\u660E\u6587 Key\uFF0C
|
|
3150
|
+
// \u56E0\u6B64\u8FD9\u91CC\u53EA\u505A\u63D0\u793A\uFF0C\u6700\u7EC8\u4EE5\u670D\u52A1\u7AEF\u6309\u300C\u5730\u5740 + userKey\u300D\u7684\u8EAB\u4EFD\u6821\u9A8C\u4E3A\u51C6\u3002
|
|
3151
|
+
if (schedEditIndex < 0) {
|
|
3152
|
+
var sameUrlIndex = findDuplicateTargetIndex(form.serverUrl, form.userKey);
|
|
3153
|
+
if (sameUrlIndex >= 0 && !form.userKey) {
|
|
3154
|
+
showSchedMsg(t("schedDupNoKey"), false);
|
|
3155
|
+
return Promise.resolve(false);
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
|
|
1855
3159
|
// \u7EC4\u88C5\u5168\u91CF\u5217\u8868\uFF1A\u7F16\u8F91\u6A21\u5F0F\u66FF\u6362\u5BF9\u5E94\u9879\uFF0C\u65B0\u589E\u6A21\u5F0F\u8FFD\u52A0\uFF1B\u5176\u4F59\u9879\u4ECE\u7F13\u5B58\u89C6\u56FE\u539F\u6837\u5E26\u4E0A\uFF08\u5BC6\u94A5\u7559\u7A7A\u7531\u540E\u7AEF\u7EE7\u627F\uFF09
|
|
1856
3160
|
var list = schedTargets.map(function(item) {
|
|
1857
3161
|
return {
|
|
@@ -1883,23 +3187,15 @@ function saveServerConfig(restartAfter) {
|
|
|
1883
3187
|
refreshServerConfig();
|
|
1884
3188
|
schedEditIndex = keepIndex;
|
|
1885
3189
|
if (restartAfter) {
|
|
3190
|
+
// \u300C\u4FDD\u5B58\u5E76\u91CD\u542F\u300D\u5165\u53E3\uFF08\u7EF4\u62A4\u9875\u7B49\uFF09\uFF1A\u4FDD\u6301\u539F\u6709\u7ACB\u5373\u91CD\u542F\u8BED\u4E49
|
|
1886
3191
|
showSchedMsg(t("schedSaved"), true);
|
|
1887
3192
|
doRestartBridge();
|
|
1888
3193
|
return true;
|
|
1889
3194
|
}
|
|
1890
|
-
// \
|
|
1891
|
-
// \
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
return runReRegister().then(function(reg) {
|
|
1895
|
-
if (reg.ok) {
|
|
1896
|
-
showSchedMsg(t("schedReRegistered"), true);
|
|
1897
|
-
refreshNodeInfo();
|
|
1898
|
-
} else {
|
|
1899
|
-
showSchedMsg(t("schedReRegisterFail") + (reg.error ? ": " + reg.error : ""), false);
|
|
1900
|
-
}
|
|
1901
|
-
return reg.ok;
|
|
1902
|
-
});
|
|
3195
|
+
// \u4FDD\u5B58\u6210\u529F\uFF1A\u5F39\u6846\u8BA9\u7528\u6237\u9009\u62E9\u5982\u4F55\u5E94\u7528\uFF08\u91CD\u65B0\u6CE8\u518C\u4E3A\u63A8\u8350\u9879\uFF0C\u4E5F\u53EF\u8DF3\u8FC7\uFF09\uFF0C
|
|
3196
|
+
// \u4E0D\u518D\u81EA\u52A8\u9759\u9ED8\u70ED\u5207\u6362\u2014\u2014\u907F\u514D\u7528\u6237\u8FD8\u6CA1\u770B\u6E05\u63D0\u793A\uFF0C\u6CE8\u518C\u5C31\u5DF2\u7ECF\u53D1\u751F
|
|
3197
|
+
showAfterSaveModal();
|
|
3198
|
+
return true;
|
|
1903
3199
|
}
|
|
1904
3200
|
showSchedMsg(t("schedSaveFail") + (d.error ? ": " + d.error : ""), false);
|
|
1905
3201
|
return false;
|
|
@@ -1915,10 +3211,64 @@ function saveServerConfig(restartAfter) {
|
|
|
1915
3211
|
});
|
|
1916
3212
|
}
|
|
1917
3213
|
|
|
1918
|
-
// \u4FDD\u5B58\
|
|
1919
|
-
function
|
|
1920
|
-
|
|
1921
|
-
|
|
3214
|
+
// \u2500\u2500 \u4FDD\u5B58\u6210\u529F\u540E\u7684\u300C\u5E94\u7528\u65B9\u5F0F\u300D\u5F39\u6846 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3215
|
+
function showAfterSaveModal() {
|
|
3216
|
+
document.getElementById("aftersaveTitle").textContent = t("aftersaveTitle");
|
|
3217
|
+
document.getElementById("aftersaveDesc").textContent = t("aftersaveDesc");
|
|
3218
|
+
var regBtn = document.getElementById("aftersaveBtnRegister");
|
|
3219
|
+
regBtn.textContent = t("aftersaveRegister");
|
|
3220
|
+
regBtn.setAttribute("data-recommended", t("aftersaveRegisterRec"));
|
|
3221
|
+
regBtn.disabled = false;
|
|
3222
|
+
var restartBtn = document.getElementById("aftersaveBtnRestart");
|
|
3223
|
+
restartBtn.textContent = t("aftersaveRestart");
|
|
3224
|
+
restartBtn.title = t("aftersaveRestartHint");
|
|
3225
|
+
restartBtn.disabled = false;
|
|
3226
|
+
document.getElementById("aftersaveBtnSkip").textContent = t("aftersaveSkip");
|
|
3227
|
+
document.getElementById("afterSaveModal").style.display = "flex";
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
function closeAfterSave(e) {
|
|
3231
|
+
// \u70B9\u906E\u7F69\u7B49\u540C\u300C\u6682\u4E0D\u300D\uFF1A\u4FDD\u5B58\u5DF2\u6210\u529F\uFF0C\u6CA1\u6709\u53EF\u53D6\u6D88\u7684\u64CD\u4F5C
|
|
3232
|
+
if (e && e.target !== e.currentTarget) return;
|
|
3233
|
+
afterSaveAction("skip");
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
function afterSaveAction(action) {
|
|
3237
|
+
var modal = document.getElementById("afterSaveModal");
|
|
3238
|
+
var regBtn = document.getElementById("aftersaveBtnRegister");
|
|
3239
|
+
var restartBtn = document.getElementById("aftersaveBtnRestart");
|
|
3240
|
+
var msg = document.getElementById("schedMsg");
|
|
3241
|
+
|
|
3242
|
+
if (action === "register") {
|
|
3243
|
+
regBtn.disabled = true;
|
|
3244
|
+
regBtn.textContent = t("aftersaveRegistering");
|
|
3245
|
+
runReRegister().then(function(reg) {
|
|
3246
|
+
modal.style.display = "none";
|
|
3247
|
+
regBtn.disabled = false;
|
|
3248
|
+
regBtn.textContent = t("aftersaveRegister");
|
|
3249
|
+
if (reg.ok) {
|
|
3250
|
+
showSchedMsg(t("aftersaveRegisterOk"), true);
|
|
3251
|
+
refreshNodeInfo();
|
|
3252
|
+
} else {
|
|
3253
|
+
showSchedMsg(t("aftersaveRegisterFail") + (reg.error ? ": " + reg.error : ""), false);
|
|
3254
|
+
}
|
|
3255
|
+
});
|
|
3256
|
+
return;
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
if (action === "restart") {
|
|
3260
|
+
restartBtn.disabled = true;
|
|
3261
|
+
restartBtn.textContent = t("aftersaveRestarting");
|
|
3262
|
+
modal.style.display = "none";
|
|
3263
|
+
restartBtn.disabled = false;
|
|
3264
|
+
restartBtn.textContent = t("aftersaveRestart");
|
|
3265
|
+
doRestartBridge();
|
|
3266
|
+
return;
|
|
3267
|
+
}
|
|
3268
|
+
|
|
3269
|
+
// skip\uFF1A\u914D\u7F6E\u4FDD\u6301\u843D\u76D8\u72B6\u6001\uFF0C\u4E0B\u6B21 Bridge \u542F\u52A8\u65F6\u751F\u6548
|
|
3270
|
+
modal.style.display = "none";
|
|
3271
|
+
showSchedMsg(t("aftersaveSkipDone"), true);
|
|
1922
3272
|
}
|
|
1923
3273
|
|
|
1924
3274
|
// \u5220\u9664\u5F53\u524D\u7F16\u8F91\u7684 target\uFF1A\u4ECE\u5217\u8868\u79FB\u9664\u540E\u5168\u91CF\u63D0\u4EA4
|
|
@@ -1972,37 +3322,13 @@ function deleteTargetFromList() {
|
|
|
1972
3322
|
});
|
|
1973
3323
|
}
|
|
1974
3324
|
|
|
1975
|
-
// \u53D1\u8D77\u91CD\u65B0\u6CE8\u518C\u8BF7\u6C42\uFF08\u4E0D\u7BA1\u7406\u6309\u94AE\u72B6\u6001\uFF0C\u4F9B\
|
|
3325
|
+
// \u53D1\u8D77\u91CD\u65B0\u6CE8\u518C\u8BF7\u6C42\uFF08\u4E0D\u7BA1\u7406\u6309\u94AE\u72B6\u6001\uFF0C\u4F9B\u5F39\u6846\u4E0E\u5220\u9664\u540E\u7684\u70ED\u5207\u6362\u94FE\u5171\u7528\uFF09
|
|
1976
3326
|
function runReRegister() {
|
|
1977
3327
|
return fetch("/runtime/re-register", { method: "POST", headers: { "Content-Type": "application/json" } })
|
|
1978
3328
|
.then(function(r) { return r.json(); })
|
|
1979
3329
|
.catch(function() { return { ok: false }; });
|
|
1980
3330
|
}
|
|
1981
3331
|
|
|
1982
|
-
function doReRegister() {
|
|
1983
|
-
var btn = document.getElementById("btnReRegister");
|
|
1984
|
-
var msg = document.getElementById("schedMsg");
|
|
1985
|
-
msg.className = "s-msg";
|
|
1986
|
-
msg.style.display = "none";
|
|
1987
|
-
btn.disabled = true;
|
|
1988
|
-
btn.textContent = t("schedReRegistering");
|
|
1989
|
-
runReRegister()
|
|
1990
|
-
.then(function(d) {
|
|
1991
|
-
if (d.ok) {
|
|
1992
|
-
msg.textContent = t("schedReRegistered");
|
|
1993
|
-
msg.className = "s-msg success";
|
|
1994
|
-
refreshNodeInfo();
|
|
1995
|
-
} else {
|
|
1996
|
-
msg.textContent = t("schedReRegisterFail") + (d.error ? ": " + d.error : "");
|
|
1997
|
-
msg.className = "s-msg error";
|
|
1998
|
-
}
|
|
1999
|
-
})
|
|
2000
|
-
.then(function() {
|
|
2001
|
-
btn.disabled = false;
|
|
2002
|
-
btn.textContent = t("schedReRegister");
|
|
2003
|
-
});
|
|
2004
|
-
}
|
|
2005
|
-
|
|
2006
3332
|
// \u2500\u2500 Access Control \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2007
3333
|
// \u52A0\u8F7D\u5F53\u524D IP \u767D\u540D\u5355\u5230\u8868\u5355\uFF08\u7A7A\u5217\u8868 = \u9ED8\u8BA4\u5168\u653E\u884C\uFF09
|
|
2008
3334
|
function refreshAccessControl() {
|
|
@@ -2089,109 +3415,132 @@ switchSettingsPage = function(page) {
|
|
|
2089
3415
|
else if (page === "logs") { refreshLogs(); startLogAutoRefresh(); }
|
|
2090
3416
|
else if (page === "scheduler") { refreshNodeInfo(); refreshServerConfig(); }
|
|
2091
3417
|
};
|
|
3418
|
+
|
|
3419
|
+
// \u2500\u2500 Dashboard\u300C\u5F15\u5BFC\u914D\u7F6E\u300D\u5E26\u6765\u7684\u8FDE\u63A5\u4FE1\u606F\uFF08#sched=add&serverUrl=...\uFF09\u2500\u2500
|
|
3420
|
+
// \u6253\u5F00\u8BBE\u7F6E\u5E76\u5207\u5230\u300C\u8C03\u5EA6\u4E2D\u5FC3\u300D\u9875\uFF1BuserKey \u5FC5\u987B\u7531\u7528\u6237\u624B\u52A8\u7C98\u8D34\uFF08\u4E0D\u8FDB URL\uFF09\u3002
|
|
3421
|
+
schedPrefill = readSchedPrefill();
|
|
3422
|
+
if (schedPrefill) {
|
|
3423
|
+
openSettings();
|
|
3424
|
+
switchSettingsPage("scheduler");
|
|
3425
|
+
}
|
|
2092
3426
|
</script>
|
|
2093
3427
|
</body>
|
|
2094
|
-
</html>`,
|
|
3428
|
+
</html>`,Fe=`<!DOCTYPE html>
|
|
2095
3429
|
<html lang="zh-CN">
|
|
2096
3430
|
<head>
|
|
2097
3431
|
<meta charset="UTF-8">
|
|
2098
3432
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2099
3433
|
<title>Bridge Dashboard - Login</title>
|
|
3434
|
+
${O}
|
|
3435
|
+
${z}
|
|
2100
3436
|
<style>
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
--text: #e1e4f0;
|
|
2107
|
-
--text2: #8b90a8;
|
|
2108
|
-
--accent: #5b8def;
|
|
2109
|
-
--accent-hover: #4a7de0;
|
|
2110
|
-
--red: #f05454;
|
|
2111
|
-
--radius: 10px;
|
|
2112
|
-
}
|
|
2113
|
-
* { margin:0; padding:0; box-sizing:border-box; }
|
|
3437
|
+
/* \u4E0E\u4E3B\u9762\u677F\u5171\u7528\u540C\u4E00\u5957\u8BBE\u8BA1\u4EE4\u724C\uFF08\u9ED8\u8BA4\u6DF1\u8272\uFF0Chtml.light-theme \u5207\u6D45\u8272\uFF09 */
|
|
3438
|
+
${j}
|
|
3439
|
+
|
|
3440
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
3441
|
+
|
|
2114
3442
|
body {
|
|
2115
|
-
font-family: -
|
|
3443
|
+
font-family: var(--font-sans);
|
|
2116
3444
|
background: var(--bg);
|
|
2117
3445
|
color: var(--text);
|
|
2118
3446
|
min-height: 100vh;
|
|
2119
3447
|
display: flex;
|
|
2120
3448
|
align-items: center;
|
|
2121
3449
|
justify-content: center;
|
|
3450
|
+
padding: 20px;
|
|
3451
|
+
-webkit-font-smoothing: antialiased;
|
|
2122
3452
|
}
|
|
3453
|
+
|
|
2123
3454
|
.login-card {
|
|
2124
3455
|
background: var(--surface);
|
|
2125
3456
|
border: 1px solid var(--border);
|
|
2126
|
-
border-radius: var(--radius);
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
3457
|
+
border-radius: var(--radius-lg);
|
|
3458
|
+
box-shadow: var(--shadow-2);
|
|
3459
|
+
padding: 36px 32px 32px;
|
|
3460
|
+
width: 384px;
|
|
3461
|
+
max-width: 100%;
|
|
3462
|
+
}
|
|
3463
|
+
|
|
3464
|
+
.login-brand {
|
|
3465
|
+
display: flex;
|
|
3466
|
+
align-items: center;
|
|
3467
|
+
gap: 10px;
|
|
3468
|
+
justify-content: center;
|
|
3469
|
+
margin-bottom: 20px;
|
|
2130
3470
|
}
|
|
3471
|
+
.brand-mark {
|
|
3472
|
+
width: 26px;
|
|
3473
|
+
height: 26px;
|
|
3474
|
+
border-radius: 7px;
|
|
3475
|
+
object-fit: contain;
|
|
3476
|
+
flex-shrink: 0;
|
|
3477
|
+
}
|
|
3478
|
+
.login-brand-name { font-size: 15px; font-weight: 650; letter-spacing: -0.01em; }
|
|
3479
|
+
|
|
2131
3480
|
.login-card h1 {
|
|
2132
|
-
font-size:
|
|
2133
|
-
font-weight:
|
|
2134
|
-
|
|
3481
|
+
font-size: 19px;
|
|
3482
|
+
font-weight: 650;
|
|
3483
|
+
letter-spacing: -0.02em;
|
|
2135
3484
|
text-align: center;
|
|
3485
|
+
margin-bottom: 6px;
|
|
2136
3486
|
}
|
|
2137
3487
|
.login-card p.subtitle {
|
|
2138
3488
|
font-size: 13px;
|
|
2139
|
-
color: var(--
|
|
3489
|
+
color: var(--text-3);
|
|
2140
3490
|
text-align: center;
|
|
2141
|
-
margin-bottom:
|
|
2142
|
-
}
|
|
2143
|
-
.form-group {
|
|
2144
|
-
margin-bottom: 18px;
|
|
3491
|
+
margin-bottom: 26px;
|
|
2145
3492
|
}
|
|
3493
|
+
|
|
3494
|
+
.form-group { margin-bottom: 16px; }
|
|
2146
3495
|
.form-group label {
|
|
2147
3496
|
display: block;
|
|
2148
|
-
font-size:
|
|
2149
|
-
font-weight:
|
|
2150
|
-
margin-bottom:
|
|
2151
|
-
color: var(--
|
|
3497
|
+
font-size: 12.5px;
|
|
3498
|
+
font-weight: 600;
|
|
3499
|
+
margin-bottom: 7px;
|
|
3500
|
+
color: var(--text-2);
|
|
2152
3501
|
}
|
|
2153
3502
|
.form-group input {
|
|
2154
3503
|
width: 100%;
|
|
2155
3504
|
padding: 10px 12px;
|
|
2156
|
-
background: var(--
|
|
3505
|
+
background: var(--bg);
|
|
2157
3506
|
border: 1px solid var(--border);
|
|
2158
|
-
border-radius:
|
|
3507
|
+
border-radius: var(--radius-sm);
|
|
2159
3508
|
color: var(--text);
|
|
2160
|
-
font-size:
|
|
3509
|
+
font-size: 13.5px;
|
|
3510
|
+
font-family: inherit;
|
|
2161
3511
|
outline: none;
|
|
2162
|
-
transition: border-color .15s;
|
|
3512
|
+
transition: border-color .15s, box-shadow .15s;
|
|
2163
3513
|
}
|
|
2164
3514
|
.form-group input:focus {
|
|
2165
3515
|
border-color: var(--accent);
|
|
3516
|
+
box-shadow: 0 0 0 3px var(--accent-soft);
|
|
2166
3517
|
}
|
|
2167
|
-
.form-group input::placeholder {
|
|
2168
|
-
|
|
2169
|
-
opacity: 0.6;
|
|
2170
|
-
}
|
|
3518
|
+
.form-group input::placeholder { color: var(--text-3); }
|
|
3519
|
+
|
|
2171
3520
|
button {
|
|
2172
3521
|
width: 100%;
|
|
2173
3522
|
padding: 10px;
|
|
2174
3523
|
background: var(--accent);
|
|
2175
3524
|
border: none;
|
|
2176
|
-
border-radius:
|
|
3525
|
+
border-radius: var(--radius-sm);
|
|
2177
3526
|
color: #fff;
|
|
2178
|
-
font-size:
|
|
3527
|
+
font-size: 13.5px;
|
|
2179
3528
|
font-weight: 600;
|
|
3529
|
+
font-family: inherit;
|
|
2180
3530
|
cursor: pointer;
|
|
2181
|
-
transition: background .15s;
|
|
2182
|
-
margin-top:
|
|
3531
|
+
transition: background .15s, opacity .15s;
|
|
3532
|
+
margin-top: 6px;
|
|
2183
3533
|
}
|
|
2184
3534
|
button:hover { background: var(--accent-hover); }
|
|
2185
|
-
button:
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
}
|
|
3535
|
+
button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
3536
|
+
button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
3537
|
+
|
|
2189
3538
|
.error-msg {
|
|
2190
|
-
background:
|
|
2191
|
-
color: var(--
|
|
2192
|
-
padding:
|
|
2193
|
-
border-radius:
|
|
2194
|
-
font-size:
|
|
3539
|
+
background: var(--danger-soft);
|
|
3540
|
+
color: var(--danger);
|
|
3541
|
+
padding: 9px 12px;
|
|
3542
|
+
border-radius: var(--radius-sm);
|
|
3543
|
+
font-size: 12.5px;
|
|
2195
3544
|
margin-bottom: 16px;
|
|
2196
3545
|
display: none;
|
|
2197
3546
|
}
|
|
@@ -2199,23 +3548,40 @@ switchSettingsPage = function(page) {
|
|
|
2199
3548
|
</style>
|
|
2200
3549
|
</head>
|
|
2201
3550
|
<body>
|
|
3551
|
+
<button type="button" class="theme-fab" id="themeFab" onclick="toggleThemeFab()" title="\u5207\u6362\u4E3B\u9898">☽</button>
|
|
2202
3552
|
<div class="login-card">
|
|
2203
|
-
<
|
|
2204
|
-
|
|
3553
|
+
<div class="login-brand">
|
|
3554
|
+
<img class="brand-mark" src="${Y}" alt="AgentsWorkStudio Bridge">
|
|
3555
|
+
<span class="login-brand-name">AgentsWorkStudio</span>
|
|
3556
|
+
</div>
|
|
3557
|
+
<h1>Bridge \u63A7\u5236\u53F0</h1>
|
|
3558
|
+
<p class="subtitle">\u767B\u5F55\u540E\u53EF\u7BA1\u7406\u672C\u673A Bridge</p>
|
|
2205
3559
|
<div class="error-msg" id="errorMsg"></div>
|
|
2206
3560
|
<form id="loginForm" onsubmit="return doLogin(event)">
|
|
2207
3561
|
<div class="form-group">
|
|
2208
|
-
<label for="username"
|
|
2209
|
-
<input type="text" id="username" name="username" placeholder="
|
|
3562
|
+
<label for="username">\u7528\u6237\u540D</label>
|
|
3563
|
+
<input type="text" id="username" name="username" placeholder="\u8BF7\u8F93\u5165\u7528\u6237\u540D" autocomplete="username" autofocus>
|
|
2210
3564
|
</div>
|
|
2211
3565
|
<div class="form-group">
|
|
2212
|
-
<label for="password"
|
|
2213
|
-
<input type="password" id="password" name="password" placeholder="
|
|
3566
|
+
<label for="password">\u5BC6\u7801</label>
|
|
3567
|
+
<input type="password" id="password" name="password" placeholder="\u8BF7\u8F93\u5165\u5BC6\u7801" autocomplete="current-password">
|
|
2214
3568
|
</div>
|
|
2215
|
-
<button type="submit" id="loginBtn"
|
|
3569
|
+
<button type="submit" id="loginBtn">\u767B\u5F55</button>
|
|
2216
3570
|
</form>
|
|
2217
3571
|
</div>
|
|
2218
3572
|
<script>
|
|
3573
|
+
/**
|
|
3574
|
+
* \u767B\u5F55\u540E\u5E26\u7740\u539F hash \u56DE\u4E3B\u9875\u3002
|
|
3575
|
+
*
|
|
3576
|
+
* \u5F15\u5BFC\u914D\u7F6E\u8DF3\u8F6C\u5F62\u5982 /#sched=add&serverUrl=...\uFF1Ahash \u4E0D\u4F1A\u968F\u8BF7\u6C42\u53D1\u7ED9\u670D\u52A1\u7AEF\uFF0C
|
|
3577
|
+
* \u4F46\u670D\u52A1\u7AEF\u628A\u9996\u9875\u91CD\u5B9A\u5411\u5230 /login \u65F6\u5B83\u4ECD\u5728\u5730\u5740\u680F\u91CC\u3002\u6B64\u5904\u82E5\u8DF3 "/" \u4F1A\u628A\u5B83\u4E22\u6389\uFF0C
|
|
3578
|
+
* \u300C\u5E26\u4E86\u914D\u7F6E\u8FDB\u6765\u5374\u4EC0\u4E48\u90FD\u6CA1\u5E26\u5165\u300D\u3002setup \u9875\u540C\u7406\uFF08\u9996\u6B21\u767B\u5F55\u8981\u5148\u8BBE\u7F6E\u8D26\u53F7\u5BC6\u7801\uFF09\u3002
|
|
3579
|
+
*/
|
|
3580
|
+
function continueWithHash(path) {
|
|
3581
|
+
var hash = String(window.location.hash || "");
|
|
3582
|
+
window.location.href = path + hash;
|
|
3583
|
+
}
|
|
3584
|
+
|
|
2219
3585
|
async function doLogin(e) {
|
|
2220
3586
|
e.preventDefault();
|
|
2221
3587
|
const btn = document.getElementById("loginBtn");
|
|
@@ -2237,9 +3603,9 @@ async function doLogin(e) {
|
|
|
2237
3603
|
if (resp.ok) {
|
|
2238
3604
|
const data = await resp.json();
|
|
2239
3605
|
if (data.needSetup) {
|
|
2240
|
-
|
|
3606
|
+
continueWithHash("/setup");
|
|
2241
3607
|
} else {
|
|
2242
|
-
|
|
3608
|
+
continueWithHash("/");
|
|
2243
3609
|
}
|
|
2244
3610
|
return;
|
|
2245
3611
|
}
|
|
@@ -2261,27 +3627,20 @@ document.getElementById("loginForm").addEventListener("keydown", function(e) {
|
|
|
2261
3627
|
if (e.key === "Enter") doLogin(e);
|
|
2262
3628
|
});
|
|
2263
3629
|
</script>
|
|
3630
|
+
${X}
|
|
2264
3631
|
</body>
|
|
2265
|
-
</html>`,
|
|
3632
|
+
</html>`,_e=`<!DOCTYPE html>
|
|
2266
3633
|
<html lang="zh-CN">
|
|
2267
3634
|
<head>
|
|
2268
3635
|
<meta charset="UTF-8">
|
|
2269
3636
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2270
3637
|
<title>Bridge Dashboard - Setup</title>
|
|
3638
|
+
${O}
|
|
3639
|
+
${z}
|
|
2271
3640
|
<style>
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
--surface2: #232738;
|
|
2276
|
-
--border: #2d3248;
|
|
2277
|
-
--text: #e1e4f0;
|
|
2278
|
-
--text2: #8b90a8;
|
|
2279
|
-
--accent: #5b8def;
|
|
2280
|
-
--accent-hover: #4a7de0;
|
|
2281
|
-
--green: #43c6a8;
|
|
2282
|
-
--red: #f05454;
|
|
2283
|
-
--radius: 10px;
|
|
2284
|
-
}
|
|
3641
|
+
/* \u4E0E\u4E3B\u9762\u677F\u5171\u7528\u8BBE\u8BA1\u4EE4\u724C\uFF08--text2/--surface2/--green/--red \u4E3A\u517C\u5BB9\u522B\u540D\uFF09 */
|
|
3642
|
+
${j}
|
|
3643
|
+
|
|
2285
3644
|
* { margin:0; padding:0; box-sizing:border-box; }
|
|
2286
3645
|
body {
|
|
2287
3646
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", sans-serif;
|
|
@@ -2425,6 +3784,7 @@ document.getElementById("loginForm").addEventListener("keydown", function(e) {
|
|
|
2425
3784
|
</style>
|
|
2426
3785
|
</head>
|
|
2427
3786
|
<body>
|
|
3787
|
+
<button type="button" class="theme-fab" id="themeFab" onclick="toggleThemeFab()" title="\u5207\u6362\u4E3B\u9898">☽</button>
|
|
2428
3788
|
<div class="setup-card" id="setupCard">
|
|
2429
3789
|
<h1>Bridge Dashboard</h1>
|
|
2430
3790
|
<p class="subtitle">First-time setup \u2014 \u9996\u6B21\u767B\u5F55\u8BBE\u7F6E</p>
|
|
@@ -2498,7 +3858,8 @@ async function doSave(e) {
|
|
|
2498
3858
|
document.getElementById("setupForm").style.display = "none";
|
|
2499
3859
|
document.querySelector(".hint").style.display = "none";
|
|
2500
3860
|
document.querySelector(".badge").style.display = "none";
|
|
2501
|
-
|
|
3861
|
+
// \u4FDD\u7559 hash\uFF1A\u5F15\u5BFC\u914D\u7F6E\u662F /#sched=add&serverUrl=... \u2192 /login \u2192 /setup \u4E00\u8DEF\u5E26\u8FC7\u6765\u7684
|
|
3862
|
+
setTimeout(() => { window.location.href = "/" + String(window.location.hash || ""); }, 1500);
|
|
2502
3863
|
} else {
|
|
2503
3864
|
errorEl.textContent = data.error || "Save failed";
|
|
2504
3865
|
errorEl.classList.add("visible");
|
|
@@ -2531,7 +3892,8 @@ async function doSkip() {
|
|
|
2531
3892
|
document.getElementById("setupForm").style.display = "none";
|
|
2532
3893
|
document.querySelector(".hint").style.display = "none";
|
|
2533
3894
|
document.querySelector(".badge").style.display = "none";
|
|
2534
|
-
|
|
3895
|
+
// \u4FDD\u7559 hash\uFF1A\u5F15\u5BFC\u914D\u7F6E\u662F /#sched=add&serverUrl=... \u2192 /login \u2192 /setup \u4E00\u8DEF\u5E26\u8FC7\u6765\u7684
|
|
3896
|
+
setTimeout(() => { window.location.href = "/" + String(window.location.hash || ""); }, 1500);
|
|
2535
3897
|
} else {
|
|
2536
3898
|
errorEl.textContent = data.error || "Skip failed";
|
|
2537
3899
|
errorEl.classList.add("visible");
|
|
@@ -2548,6 +3910,7 @@ async function doSkip() {
|
|
|
2548
3910
|
|
|
2549
3911
|
document.getElementById("setupForm").addEventListener("submit", doSave);
|
|
2550
3912
|
</script>
|
|
3913
|
+
${X}
|
|
2551
3914
|
</body>
|
|
2552
|
-
</html>`;export{
|
|
3915
|
+
</html>`;export{f as dashboardRouter,Pe as noteDashboardActivity};
|
|
2553
3916
|
|