diff --git a/gui/src/App.tsx b/gui/src/App.tsx index 489cb4e7f6..9bb0afcf3e 100644 --- a/gui/src/App.tsx +++ b/gui/src/App.tsx @@ -11,6 +11,9 @@ import CodexSet from "./pages/CodexSet"; import Integrations from "./pages/Integrations"; import Startup from "./pages/Startup"; import RemoteWorkspace from "./pages/RemoteWorkspace"; +import { Skills } from "./pages/Skills"; +import { Security } from "./pages/Security"; +import { Credentials } from "./pages/Credentials"; import ErrorBoundary from "./components/ErrorBoundary"; import { SidebarGithubRow } from "./components/sidebar-github-row"; import { IconGrid, IconServer, IconBoxes, IconBot, IconList, IconActivity, IconHardDrive, IconCodex, IconMenu, IconSun, IconMoon, IconMonitor, IconGlobe, IconPower, IconX, IconRefresh} from "./icons"; @@ -33,6 +36,9 @@ const PAGE_TKEY: Record = { providers: "nav.providers", models: "nav.models", subagents: "nav.subagents", + skills: "nav.skills", + security: "nav.security", + credentials: "nav.credentials", logs: "nav.logs", usage: "nav.usage", storage: "nav.storage", @@ -447,6 +453,9 @@ export default function App() { {page === "providers" && } {page === "models" && } {page === "subagents" && } + {page === "skills" && } + {page === "security" && } + {page === "credentials" && } {page === "logs" && } {page === "usage" && } {page === "storage" && } diff --git a/gui/src/app-routing.ts b/gui/src/app-routing.ts index 9dbf4d28bc..25cf7bffaa 100644 --- a/gui/src/app-routing.ts +++ b/gui/src/app-routing.ts @@ -8,6 +8,9 @@ export type Page = | "providers" | "models" | "subagents" + | "skills" + | "security" + | "credentials" | "logs" | "usage" | "storage" @@ -21,6 +24,9 @@ export const VALID_PAGES = new Set([ "providers", "models", "subagents", + "skills", + "security", + "credentials", "logs", "usage", "storage", @@ -107,11 +113,52 @@ export const INTEGRATION_TAB_HASHES = [ "integrations/cline", ] as const; +export const SKILLS_TAB_HASHES = [ + "skills/marketplace", + "skills/registry", + "skills/editor", + "skills/matrix", + "skills/agents", + "skills/nodes", + "skills/drift", + "skills/reviews", + "skills/audit", +] as const; + +export const SECURITY_TAB_HASHES = [ + "security/authorizations", + "security/scopes", + "security/campaigns", + "security/approvals", + "security/findings", + "security/evidence", + "security/skills", + "security/tools", + "security/mcp", + "security/policies", + "security/audit", +] as const; + +export const CREDENTIALS_TAB_HASHES = [ + "credentials/list", + "credentials/providers", + "credentials/oauth", + "credentials/pool", + "credentials/health", + "credentials/quota", + "credentials/policies", + "credentials/approvals", + "credentials/audit", +] as const; + export function hashBelongsToPage(rawHash: string, page: Page): boolean { return rawHash === page || (page === "logs" && rawHash === "logs/debug") || (page === "codex-set" && rawHash === "codex-set/prompt") || (page === "models" && (MODELS_TAB_HASHES as readonly string[]).includes(rawHash)) + || (page === "skills" && (SKILLS_TAB_HASHES as readonly string[]).includes(rawHash)) + || (page === "security" && (SECURITY_TAB_HASHES as readonly string[]).includes(rawHash)) + || (page === "credentials" && (CREDENTIALS_TAB_HASHES as readonly string[]).includes(rawHash)) || (page === "dashboard" && (rawHash === DASHBOARD_UPDATE_HASH || (DASHBOARD_TAB_HASHES as readonly string[]).includes(rawHash))) || (page === "integrations" diff --git a/gui/src/i18n/de.ts b/gui/src/i18n/de.ts index 70b62515d9..a54848a52c 100644 --- a/gui/src/i18n/de.ts +++ b/gui/src/i18n/de.ts @@ -66,6 +66,9 @@ export const de: Record = { "nav.models": "Modelle", "nav.combos": "Combos", "nav.subagents": "Sub-Agenten", + "nav.skills": "Skills", + "nav.security": "Sicherheit", + "nav.credentials": "Anmeldedaten", // routing intelligence "routing.title": "Routing-Intelligenz (beta)", @@ -2840,4 +2843,221 @@ export const de: Record = { "remote.event.status": "Status", "remote.event.tool": "Remote-Werkzeug", "remote.event.error": "Fehler", + "skills.title": "Skill-Steuerungsebene", + "skills.subtitle": "Universelles Agent-Skill-Register, visueller Marktplatz und richtliniengesteuerte Bereitstellung.", + "skills.refresh": "Aktualisieren", + "skills.refreshing": "Aktualisiere…", + "skills.tab.overview": "Übersicht", + "skills.tab.marketplace": "Marktplatz", + "skills.tab.registry": "Register ({count})", + "skills.tab.editor": "Skill-Editor", + "skills.tab.matrix": "Bereitstellungsmatrix", + "skills.tab.agents": "Agenten", + "skills.tab.nodes": "Remote-Knoten", + "skills.tab.drift": "Drift und Rollback", + "skills.tab.reviews": "Prüfwarteschlange", + "skills.tab.audit": "Prüfprotokoll", + "skills.card.registered": "Registrierte Skills", + "skills.card.deployments": "Aktive Bereitstellungen", + "skills.card.marketplace": "Marktplatz-Einträge", + "skills.card.audit": "Prüfereignisse", + "skills.recentActivity": "Aktuelle Steuerungsebenen-Aktivität", + "skills.noEvents": "Keine aktuellen Ereignisse aufgezeichnet.", + "skills.col.timestamp": "Zeitstempel", + "skills.col.event": "Ereignis", + "skills.col.actor": "Akteur", + "skills.col.target": "Ziel", + "skills.searchPlaceholder": "Öffentlichen skills.sh-Katalog durchsuchen…", + "skills.byAuthor": "v{version} von {author}", + "skills.verified": "Verifiziert", + "skills.importToRegistry": "Ins Register importieren", + "skills.emptyRegistry": "Noch keine Skills registriert. Importieren Sie einen vom Marktplatz oder aus einem lokalen Ordner.", + "skills.col.skill": "Skill", + "skills.col.version": "Version", + "skills.col.status": "Status", + "skills.col.risk": "Risiko", + "skills.col.tags": "Tags", + "skills.col.actions": "Aktionen", + "skills.publish": "Veröffentlichen", + "skills.deployCodex": "Nach Codex bereitstellen", + "skills.editorTitle": "SKILL.md-Editor", + "skills.editorNamePlaceholder": "Skill-Name", + "skills.editorVersionPlaceholder": "Version", + "skills.editorDefaultName": "Benutzerdefinierter Assistent", + "skills.editorDefaultMarkdown": "---\nname: custom-assistant\ndisplayName: Custom Assistant\nversion: 1.0.0\ndescription: Custom guidance procedure.\ntags: custom, utility\n---\n\n# Custom Assistant\n\nInstructions for the agent.\n", + "skills.saveDraft": "Entwurf speichern", + "skills.previewTitle": "Live-Vorschau", + "skills.matrixTitle": "Flotten-Bereitstellungsmatrix", + "skills.agent.codex": "Codex", + "skills.agent.claudeCode": "Claude Code", + "skills.agent.opencode": "OpenCode", + "skills.agent.universal": "Universal", + "skills.notInstalled": "Nicht installiert", + "skills.inSync": "v{version} synchron", + "skills.blocked": "Blockiert", + "skills.agentsTitle": "Ziel-Agentenflotte", + "skills.col.agent": "Agent", + "skills.col.userRoot": "Zielwurzel (Benutzerbereich)", + "skills.col.projectRoot": "Zielwurzel (Projektbereich)", + "skills.col.adapterVersion": "Adapterversion", + "skills.agent.openaiCodex": "OpenAI Codex", + "skills.agent.universalAgent": "Universeller Agent", + "skills.statusReady": "Bereit", + "skills.nodesTitle": "Remote-Ausführungsknoten (SSH)", + "skills.nodesHelp": "Verwalten Sie Remote-VPS, GPU-Worker und Entwicklungshosts mit strikter Host-Key-Pinning.", + "skills.col.node": "Knoten", + "skills.col.type": "Typ", + "skills.col.host": "Host", + "skills.col.environment": "Umgebung", + "skills.localMachine": "Lokaler Rechner", + "skills.self": "Selbst", + "skills.vpsMain": "VPS Main", + "skills.testConnection": "Verbindung testen", + "skills.driftTitle": "Konfigurationsdrift und Rollback", + "skills.driftHelp": "Die kontinuierliche Überwachung vergleicht Live-Dateien mit unveränderlichen Registry-Hashes.", + "skills.col.deployment": "Bereitstellung", + "skills.col.targetPath": "Zielpfad", + "skills.col.driftState": "Drift-Status", + "skills.checkHash": "Hash prüfen", + "skills.reviewsTitle": "Richtlinien- und Freigabewarteschlange", + "skills.reviewsEmpty": "Keine ausstehenden erhöhten Prüfungen erfordern eine Operatorentscheidung.", + "skills.auditTitle": "Prüfprotokoll", + "skills.col.eventType": "Ereignistyp", + "skills.col.targetId": "Ziel-ID", + "skills.system": "system", + "skills.status.importing": "Importiere {id}…", + "skills.status.imported": "{id} importiert.", + "skills.status.importFailed": "Import fehlgeschlagen: {error}", + "skills.status.publishing": "Veröffentliche {id}…", + "skills.status.published": "{id} veröffentlicht.", + "skills.status.publishFailed": "Veröffentlichung fehlgeschlagen: {error}", + "skills.status.planning": "Plane Bereitstellung von {id} nach {agent}…", + "skills.status.deployed": "{id} nach {agent} bereitgestellt.", + "skills.status.deployFailed": "Bereitstellung fehlgeschlagen: {error}", + "skills.status.saved": "Skill-Entwurf im Register gespeichert.", + "skills.status.saveFailed": "Speichern fehlgeschlagen: {error}", + "skills.loadFailed": "Skill-Steuerungsebene konnte nicht geladen werden.", + "security.title": "Sicherheits-Steuerungsebene", + "security.subtitle": "Autorisierungsfirste Erkundung und Validierung. Zielgerichtete Arbeit ist standardmäßig verweigert. R3 ist blockiert. Hash-routbar; keine Sidebar-NAV-Zeile.", + "security.refresh": "Aktualisieren", + "security.refreshing": "Aktualisiere…", + "security.banner": "Nur autorisierte Ziele. Unbekannte Assets, abgelaufene Autorisierungen und ausgeschlossene Hosts werden verweigert. Live-Drittanbieter-Tools werden nicht aufgerufen; die Erkundung nutzt lokale Fixtures.", + "security.flagOff": "Feature-Flag PAO_SECURITY_CONTROL_PLANE ist aus — Änderungen werden abgelehnt.", + "security.status.posting": "POST {path}", + "security.status.ok": "OK {path}", + "security.status.failed": "Fehlgeschlagen: {error}", + "security.tab.overview": "Übersicht", + "security.tab.authorizations": "Autorisierungen", + "security.tab.scopes": "Bereiche", + "security.tab.campaigns": "Kampagnen", + "security.tab.approvals": "Freigaben", + "security.tab.findings": "Findings", + "security.tab.evidence": "Nachweise", + "security.tab.skills": "Pakete", + "security.tab.tools": "Werkzeuge", + "security.tab.mcp": "MCP", + "security.tab.policies": "Richtlinien", + "security.tab.audit": "Protokoll", + "security.card.enabled": "Aktiviert", + "security.card.campaigns": "Kampagnen", + "security.card.approvals": "Freigaben", + "security.card.leads": "Leads", + "security.card.validated": "Validiert", + "security.card.blocked": "Blockiert", + "security.card.authExpiring": "Autorisierung läuft ab", + "security.card.breakers": "Schutzschalter", + "security.yes": "ja", + "security.no": "nein", + "security.col.id": "ID", + "security.col.type": "Typ", + "security.col.status": "Status", + "security.col.validUntil": "Gültig bis", + "security.col.name": "Name", + "security.col.actions": "Aktionen", + "security.col.target": "Ziel", + "security.col.title": "Titel", + "security.col.severity": "Schwere", + "security.col.sha256": "SHA-256", + "security.col.preview": "Vorschau", + "security.col.compatibility": "Kompatibilität", + "security.col.kind": "Art", + "security.col.allowlisted": "Auf der Allowlist", + "security.col.health": "Zustand", + "security.col.credential": "Anmeldedaten", + "security.col.ceiling": "Obergrenze", + "security.col.blockR3": "R3 blockieren", + "security.col.when": "Wann", + "security.col.event": "Ereignis", + "security.col.actor": "Akteur", + "security.start": "Starten", + "security.pause": "Pausieren", + "security.reconLab": "Recon lab.local", + "security.approveOnce": "Einmal freigeben", + "security.deny": "Ablehnen", + "security.reasonBounded": "einmalig begrenzt", + "security.reasonDenied": "abgelehnt", + "security.hashes": "Lesezeichen-Hashes: {hashes}", + "security.loadFailed": "Sicherheits-Steuerungsebene konnte nicht geladen werden.", + "credentials.title": "Provider-Zugriffssteuerung", + "credentials.subtitle": "Verschlüsselte Anmeldedaten, gesundheitsgeprüfter Token-Pool, OAuth-Gateway und richtliniengesteuerte Leases. Hash-routbar; keine Sidebar-NAV-Zeile.", + "credentials.refresh": "Aktualisieren", + "credentials.refreshing": "Aktualisiere…", + "credentials.banner": "Geheimnisse werden niemals im Klartext aufgelistet. Gesundheitsprüfungen in dieser Phase sind Fixture/In-Process. Diese Ebene registriert keine Anbieterkonten.", + "credentials.flagOff": "Feature-Flag CREDENTIAL_RUNTIME_ENABLED ist aus — Mutationen werden abgelehnt.", + "credentials.status.posting": "POST {path}", + "credentials.status.ok": "OK {path}", + "credentials.status.failed": "Fehlgeschlagen: {error}", + "credentials.tab.overview": "Übersicht", + "credentials.tab.list": "Anmeldedaten", + "credentials.tab.providers": "Anbieter", + "credentials.tab.oauth": "OAuth", + "credentials.tab.pool": "Pool", + "credentials.tab.health": "Gesundheit", + "credentials.tab.quota": "Kontingent", + "credentials.tab.policies": "Richtlinien", + "credentials.tab.approvals": "Freigaben", + "credentials.tab.audit": "Audit", + "credentials.card.enabled": "Aktiviert", + "credentials.card.total": "Gesamt", + "credentials.card.healthy": "Gesund", + "credentials.card.degraded": "Beeinträchtigt", + "credentials.card.quarantined": "Quarantäne", + "credentials.card.revoked": "Widerrufen", + "credentials.card.leases": "Leases", + "credentials.card.breakers": "Unterbrecher", + "credentials.card.vault": "Tresor", + "credentials.yes": "ja", + "credentials.no": "nein", + "credentials.available": "verfügbar", + "credentials.unavailable": "nicht verfügbar", + "credentials.col.id": "ID", + "credentials.col.name": "Name", + "credentials.col.provider": "Anbieter", + "credentials.col.status": "Status", + "credentials.col.health": "Gesundheit", + "credentials.col.secret": "Geheimnis", + "credentials.col.eligible": "Berechtigt", + "credentials.col.actions": "Aktionen", + "credentials.col.score": "Score", + "credentials.col.adapter": "Adapter", + "credentials.col.oauth": "OAuth", + "credentials.col.enabled": "Aktiviert", + "credentials.col.session": "Sitzung", + "credentials.col.redirect": "Weiterleitung", + "credentials.col.budget": "Budget", + "credentials.col.priority": "Priorität", + "credentials.col.action": "Aktion", + "credentials.col.target": "Ziel", + "credentials.col.when": "Wann", + "credentials.col.event": "Ereignis", + "credentials.col.actor": "Akteur", + "credentials.col.decision": "Entscheidung", + "credentials.validate": "Validieren", + "credentials.quarantine": "Quarantäne", + "credentials.revoke": "Widerrufen", + "credentials.healthRun": "Gesundheitsprüfung starten", + "credentials.approve": "Freigeben", + "credentials.reject": "Ablehnen", + "credentials.hashes": "Lesezeichen-Hashes: {hashes}", + "credentials.loadFailed": "Anmeldedaten-Laufzeitdaten konnten nicht geladen werden.", }; diff --git a/gui/src/i18n/en.ts b/gui/src/i18n/en.ts index de2b8906f8..cc02dab63e 100644 --- a/gui/src/i18n/en.ts +++ b/gui/src/i18n/en.ts @@ -68,6 +68,9 @@ export const en = { "nav.models": "Models", "nav.combos": "Combos", "nav.subagents": "Subagents", + "nav.skills": "Skills", + "nav.security": "Security", + "nav.credentials": "Credentials", "nav.logs": "Logs & Debug", "nav.usage": "Usage", "common.github": "GitHub", @@ -2874,6 +2877,223 @@ export const en = { "remote.event.status": "Status", "remote.event.tool": "Remote tool", "remote.event.error": "Error", + "skills.title": "Skill Control Plane", + "skills.subtitle": "Universal Agent Skill Registry, visual marketplace, and policy-governed deployment.", + "skills.refresh": "Refresh", + "skills.refreshing": "Refreshing…", + "skills.tab.overview": "Overview", + "skills.tab.marketplace": "Marketplace", + "skills.tab.registry": "Registry ({count})", + "skills.tab.editor": "Skill Editor", + "skills.tab.matrix": "Deployment Matrix", + "skills.tab.agents": "Agents", + "skills.tab.nodes": "Remote Nodes", + "skills.tab.drift": "Drift & Rollback", + "skills.tab.reviews": "Review Queue", + "skills.tab.audit": "Audit Trail", + "skills.card.registered": "Registered Skills", + "skills.card.deployments": "Active Deployments", + "skills.card.marketplace": "Marketplace Items", + "skills.card.audit": "Audit Events", + "skills.recentActivity": "Recent control-plane activity", + "skills.noEvents": "No recent events recorded.", + "skills.col.timestamp": "Timestamp", + "skills.col.event": "Event", + "skills.col.actor": "Actor", + "skills.col.target": "Target", + "skills.searchPlaceholder": "Search the public skills.sh catalog…", + "skills.byAuthor": "v{version} by {author}", + "skills.verified": "Verified", + "skills.importToRegistry": "Import to registry", + "skills.emptyRegistry": "No skills registered yet. Import one from Marketplace or a local folder.", + "skills.col.skill": "Skill", + "skills.col.version": "Version", + "skills.col.status": "Status", + "skills.col.risk": "Risk", + "skills.col.tags": "Tags", + "skills.col.actions": "Actions", + "skills.publish": "Publish", + "skills.deployCodex": "Deploy to Codex", + "skills.editorTitle": "SKILL.md editor", + "skills.editorNamePlaceholder": "Skill name", + "skills.editorVersionPlaceholder": "Version", + "skills.editorDefaultName": "Custom Assistant", + "skills.editorDefaultMarkdown": "---\nname: custom-assistant\ndisplayName: Custom Assistant\nversion: 1.0.0\ndescription: Custom guidance procedure.\ntags: custom, utility\n---\n\n# Custom Assistant\n\nInstructions for the agent.\n", + "skills.saveDraft": "Save draft", + "skills.previewTitle": "Live rendered preview", + "skills.matrixTitle": "Fleet deployment matrix", + "skills.agent.codex": "Codex", + "skills.agent.claudeCode": "Claude Code", + "skills.agent.opencode": "OpenCode", + "skills.agent.universal": "Universal", + "skills.notInstalled": "Not installed", + "skills.inSync": "v{version} in sync", + "skills.blocked": "Blocked", + "skills.agentsTitle": "Target agent fleet", + "skills.col.agent": "Agent", + "skills.col.userRoot": "Target root (user scope)", + "skills.col.projectRoot": "Target root (project scope)", + "skills.col.adapterVersion": "Adapter version", + "skills.agent.openaiCodex": "OpenAI Codex", + "skills.agent.universalAgent": "Universal Agent", + "skills.statusReady": "Ready", + "skills.nodesTitle": "Remote execution nodes (SSH)", + "skills.nodesHelp": "Manage remote VPS, GPU workers, and development hosts with strict host-key pinning.", + "skills.col.node": "Node", + "skills.col.type": "Type", + "skills.col.host": "Host", + "skills.col.environment": "Environment", + "skills.localMachine": "Local machine", + "skills.self": "Self", + "skills.vpsMain": "VPS Main", + "skills.testConnection": "Test connection", + "skills.driftTitle": "Configuration drift and rollback", + "skills.driftHelp": "Continuous monitoring compares live disk files against immutable registry content hashes.", + "skills.col.deployment": "Deployment", + "skills.col.targetPath": "Target path", + "skills.col.driftState": "Drift state", + "skills.checkHash": "Check hash", + "skills.reviewsTitle": "Policy and approval queue", + "skills.reviewsEmpty": "No pending elevated reviews require an operator decision.", + "skills.auditTitle": "Audit log", + "skills.col.eventType": "Event type", + "skills.col.targetId": "Target ID", + "skills.system": "system", + "skills.status.importing": "Importing {id}…", + "skills.status.imported": "Imported {id}.", + "skills.status.importFailed": "Import failed: {error}", + "skills.status.publishing": "Publishing {id}…", + "skills.status.published": "Published {id}.", + "skills.status.publishFailed": "Publish failed: {error}", + "skills.status.planning": "Planning deployment for {id} to {agent}…", + "skills.status.deployed": "Deployed {id} to {agent}.", + "skills.status.deployFailed": "Deploy failed: {error}", + "skills.status.saved": "Skill draft saved to the registry.", + "skills.status.saveFailed": "Save failed: {error}", + "skills.loadFailed": "Could not load skill control plane data.", + "security.title": "Security Control Plane", + "security.subtitle": "Authorization-first recon and validation. Target-facing work is deny-by-default. R3 is blocked. Hash-routable; not a sidebar NAV row.", + "security.refresh": "Refresh", + "security.refreshing": "Refreshing…", + "security.banner": "Authorized targets only. Unknown assets, expired authorizations, and excluded hosts deny. Live third-party tools are not invoked; recon uses local fixtures.", + "security.flagOff": "Feature flag PAO_SECURITY_CONTROL_PLANE is off — mutations are refused.", + "security.status.posting": "POST {path}", + "security.status.ok": "OK {path}", + "security.status.failed": "Failed: {error}", + "security.tab.overview": "Overview", + "security.tab.authorizations": "Authorizations", + "security.tab.scopes": "Scopes", + "security.tab.campaigns": "Campaigns", + "security.tab.approvals": "Approvals", + "security.tab.findings": "Findings", + "security.tab.evidence": "Evidence", + "security.tab.skills": "Packages", + "security.tab.tools": "Tools", + "security.tab.mcp": "MCP", + "security.tab.policies": "Policies", + "security.tab.audit": "Audit", + "security.card.enabled": "Enabled", + "security.card.campaigns": "Campaigns", + "security.card.approvals": "Approvals", + "security.card.leads": "Leads", + "security.card.validated": "Validated", + "security.card.blocked": "Blocked", + "security.card.authExpiring": "Auth expiring", + "security.card.breakers": "Breakers", + "security.yes": "yes", + "security.no": "no", + "security.col.id": "ID", + "security.col.type": "Type", + "security.col.status": "Status", + "security.col.validUntil": "Valid until", + "security.col.name": "Name", + "security.col.actions": "Actions", + "security.col.target": "Target", + "security.col.title": "Title", + "security.col.severity": "Severity", + "security.col.sha256": "SHA-256", + "security.col.preview": "Preview", + "security.col.compatibility": "Compatibility", + "security.col.kind": "Kind", + "security.col.allowlisted": "Allowlisted", + "security.col.health": "Health", + "security.col.credential": "Credential", + "security.col.ceiling": "Ceiling", + "security.col.blockR3": "Block R3", + "security.col.when": "When", + "security.col.event": "Event", + "security.col.actor": "Actor", + "security.start": "Start", + "security.pause": "Pause", + "security.reconLab": "Recon lab.local", + "security.approveOnce": "Approve once", + "security.deny": "Deny", + "security.reasonBounded": "bounded once", + "security.reasonDenied": "denied", + "security.hashes": "Bookmarkable hashes: {hashes}", + "security.loadFailed": "Could not load security control plane data.", + "credentials.title": "Provider Access Control Plane", + "credentials.subtitle": "Encrypted credentials, health-checked token pool, OAuth gateway, and policy-governed leases. Hash-routable; not a sidebar NAV row.", + "credentials.refresh": "Refresh", + "credentials.refreshing": "Refreshing…", + "credentials.banner": "Secrets are never listed in plaintext. Health checks in this phase are fixture/in-process. This plane does not register provider accounts.", + "credentials.flagOff": "Feature flag CREDENTIAL_RUNTIME_ENABLED is off — mutations are refused.", + "credentials.status.posting": "POST {path}", + "credentials.status.ok": "OK {path}", + "credentials.status.failed": "Failed: {error}", + "credentials.tab.overview": "Overview", + "credentials.tab.list": "Credentials", + "credentials.tab.providers": "Providers", + "credentials.tab.oauth": "OAuth", + "credentials.tab.pool": "Pool", + "credentials.tab.health": "Health", + "credentials.tab.quota": "Quota", + "credentials.tab.policies": "Policies", + "credentials.tab.approvals": "Approvals", + "credentials.tab.audit": "Audit", + "credentials.card.enabled": "Enabled", + "credentials.card.total": "Total", + "credentials.card.healthy": "Healthy", + "credentials.card.degraded": "Degraded", + "credentials.card.quarantined": "Quarantined", + "credentials.card.revoked": "Revoked", + "credentials.card.leases": "Leases", + "credentials.card.breakers": "Breakers", + "credentials.card.vault": "Vault", + "credentials.yes": "yes", + "credentials.no": "no", + "credentials.available": "available", + "credentials.unavailable": "unavailable", + "credentials.col.id": "ID", + "credentials.col.name": "Name", + "credentials.col.provider": "Provider", + "credentials.col.status": "Status", + "credentials.col.health": "Health", + "credentials.col.secret": "Secret", + "credentials.col.eligible": "Eligible", + "credentials.col.actions": "Actions", + "credentials.col.score": "Score", + "credentials.col.adapter": "Adapter", + "credentials.col.oauth": "OAuth", + "credentials.col.enabled": "Enabled", + "credentials.col.session": "Session", + "credentials.col.redirect": "Redirect", + "credentials.col.budget": "Budget", + "credentials.col.priority": "Priority", + "credentials.col.action": "Action", + "credentials.col.target": "Target", + "credentials.col.when": "When", + "credentials.col.event": "Event", + "credentials.col.actor": "Actor", + "credentials.col.decision": "Decision", + "credentials.validate": "Validate", + "credentials.quarantine": "Quarantine", + "credentials.revoke": "Revoke", + "credentials.healthRun": "Run health pass", + "credentials.approve": "Approve", + "credentials.reject": "Reject", + "credentials.hashes": "Bookmarkable hashes: {hashes}", + "credentials.loadFailed": "Could not load credential runtime data.", } as const; export type TKey = keyof typeof en; diff --git a/gui/src/i18n/fr.ts b/gui/src/i18n/fr.ts index 5b9a38a588..92a238713c 100644 --- a/gui/src/i18n/fr.ts +++ b/gui/src/i18n/fr.ts @@ -65,6 +65,9 @@ export const fr: Record = { "nav.models": "Modèles", "nav.combos": "Combinaisons", "nav.subagents": "Sous-agents", + "nav.skills": "Compétences", + "nav.security": "Sécurité", + "nav.credentials": "Identifiants", "nav.logs": "Journaux et débogage", "nav.usage": "Utilisation", "common.github": "GitHub", @@ -2828,4 +2831,221 @@ export const fr: Record = { "remote.event.status": "État", "remote.event.tool": "Outil distant", "remote.event.error": "Erreur", + "skills.title": "Plan de contrôle des compétences", + "skills.subtitle": "Registre universel des compétences d’agent, marketplace visuel et déploiement gouverné par la politique.", + "skills.refresh": "Actualiser", + "skills.refreshing": "Actualisation…", + "skills.tab.overview": "Aperçu", + "skills.tab.marketplace": "Marketplace", + "skills.tab.registry": "Registre ({count})", + "skills.tab.editor": "Éditeur de compétence", + "skills.tab.matrix": "Matrice de déploiement", + "skills.tab.agents": "Agents", + "skills.tab.nodes": "Nœuds distants", + "skills.tab.drift": "Dérive et restauration", + "skills.tab.reviews": "File d’examen", + "skills.tab.audit": "Journal d’audit", + "skills.card.registered": "Compétences enregistrées", + "skills.card.deployments": "Déploiements actifs", + "skills.card.marketplace": "Éléments du marketplace", + "skills.card.audit": "Événements d’audit", + "skills.recentActivity": "Activité récente du plan de contrôle", + "skills.noEvents": "Aucun événement récent enregistré.", + "skills.col.timestamp": "Horodatage", + "skills.col.event": "Événement", + "skills.col.actor": "Acteur", + "skills.col.target": "Cible", + "skills.searchPlaceholder": "Rechercher le catalogue public skills.sh…", + "skills.byAuthor": "v{version} par {author}", + "skills.verified": "Vérifié", + "skills.importToRegistry": "Importer dans le registre", + "skills.emptyRegistry": "Aucune compétence enregistrée. Importez-en une depuis le marketplace ou un dossier local.", + "skills.col.skill": "Compétence", + "skills.col.version": "Version", + "skills.col.status": "Statut", + "skills.col.risk": "Risque", + "skills.col.tags": "Étiquettes", + "skills.col.actions": "Actions", + "skills.publish": "Publier", + "skills.deployCodex": "Déployer vers Codex", + "skills.editorTitle": "Éditeur SKILL.md", + "skills.editorNamePlaceholder": "Nom de la compétence", + "skills.editorVersionPlaceholder": "Version", + "skills.editorDefaultName": "Assistant personnalisé", + "skills.editorDefaultMarkdown": "---\nname: custom-assistant\ndisplayName: Custom Assistant\nversion: 1.0.0\ndescription: Custom guidance procedure.\ntags: custom, utility\n---\n\n# Custom Assistant\n\nInstructions for the agent.\n", + "skills.saveDraft": "Enregistrer le brouillon", + "skills.previewTitle": "Aperçu rendu en direct", + "skills.matrixTitle": "Matrice de déploiement de flotte", + "skills.agent.codex": "Codex", + "skills.agent.claudeCode": "Claude Code", + "skills.agent.opencode": "OpenCode", + "skills.agent.universal": "Universal", + "skills.notInstalled": "Non installé", + "skills.inSync": "v{version} synchronisé", + "skills.blocked": "Bloqué", + "skills.agentsTitle": "Flotte d’agents cibles", + "skills.col.agent": "Agent", + "skills.col.userRoot": "Racine cible (portée utilisateur)", + "skills.col.projectRoot": "Racine cible (portée projet)", + "skills.col.adapterVersion": "Version de l’adaptateur", + "skills.agent.openaiCodex": "OpenAI Codex", + "skills.agent.universalAgent": "Agent universel", + "skills.statusReady": "Prêt", + "skills.nodesTitle": "Nœuds d’exécution distants (SSH)", + "skills.nodesHelp": "Gérez les VPS distants, les workers GPU et les hôtes de développement avec un épinglage strict des clés d’hôte.", + "skills.col.node": "Nœud", + "skills.col.type": "Type", + "skills.col.host": "Hôte", + "skills.col.environment": "Environnement", + "skills.localMachine": "Machine locale", + "skills.self": "Soi", + "skills.vpsMain": "VPS Main", + "skills.testConnection": "Tester la connexion", + "skills.driftTitle": "Dérive de configuration et restauration", + "skills.driftHelp": "La surveillance continue compare les fichiers disque aux hachages immuables du registre.", + "skills.col.deployment": "Déploiement", + "skills.col.targetPath": "Chemin cible", + "skills.col.driftState": "État de dérive", + "skills.checkHash": "Vérifier le hachage", + "skills.reviewsTitle": "File de politique et d’approbation", + "skills.reviewsEmpty": "Aucune revue élevée en attente n’exige une décision d’opérateur.", + "skills.auditTitle": "Journal d’audit", + "skills.col.eventType": "Type d’événement", + "skills.col.targetId": "ID cible", + "skills.system": "system", + "skills.status.importing": "Importation de {id}…", + "skills.status.imported": "{id} importé.", + "skills.status.importFailed": "Échec de l’importation : {error}", + "skills.status.publishing": "Publication de {id}…", + "skills.status.published": "{id} publié.", + "skills.status.publishFailed": "Échec de la publication : {error}", + "skills.status.planning": "Planification du déploiement de {id} vers {agent}…", + "skills.status.deployed": "{id} déployé vers {agent}.", + "skills.status.deployFailed": "Échec du déploiement : {error}", + "skills.status.saved": "Brouillon de compétence enregistré dans le registre.", + "skills.status.saveFailed": "Échec de l’enregistrement : {error}", + "skills.loadFailed": "Impossible de charger les données du plan de contrôle des compétences.", + "security.title": "Plan de contrôle de sécurité", + "security.subtitle": "Reconnaissance et validation d’abord autorisées. Le travail face à une cible est refusé par défaut. R3 est bloqué. Accessible par hash ; pas une ligne NAV de barre latérale.", + "security.refresh": "Actualiser", + "security.refreshing": "Actualisation…", + "security.banner": "Cibles autorisées uniquement. Les actifs inconnus, les autorisations expirées et les hôtes exclus sont refusés. Les outils tiers en direct ne sont pas invoqués ; la reconnaissance utilise des fixtures locales.", + "security.flagOff": "Le drapeau PAO_SECURITY_CONTROL_PLANE est désactivé — les mutations sont refusées.", + "security.status.posting": "POST {path}", + "security.status.ok": "OK {path}", + "security.status.failed": "Échec : {error}", + "security.tab.overview": "Aperçu", + "security.tab.authorizations": "Autorisations", + "security.tab.scopes": "Portées", + "security.tab.campaigns": "Campagnes", + "security.tab.approvals": "Approbations", + "security.tab.findings": "Constats", + "security.tab.evidence": "Preuves", + "security.tab.skills": "Paquets", + "security.tab.tools": "Outils", + "security.tab.mcp": "MCP", + "security.tab.policies": "Politiques", + "security.tab.audit": "Audit", + "security.card.enabled": "Activé", + "security.card.campaigns": "Campagnes", + "security.card.approvals": "Approbations", + "security.card.leads": "Pistes", + "security.card.validated": "Validé", + "security.card.blocked": "Bloqué", + "security.card.authExpiring": "Autorisations expirant", + "security.card.breakers": "Disjoncteurs", + "security.yes": "oui", + "security.no": "non", + "security.col.id": "ID", + "security.col.type": "Type", + "security.col.status": "Statut", + "security.col.validUntil": "Valide jusqu’au", + "security.col.name": "Nom", + "security.col.actions": "Actions", + "security.col.target": "Cible", + "security.col.title": "Titre", + "security.col.severity": "Gravité", + "security.col.sha256": "SHA-256", + "security.col.preview": "Aperçu", + "security.col.compatibility": "Compatibilité", + "security.col.kind": "Genre", + "security.col.allowlisted": "Sur liste d’autorisation", + "security.col.health": "Santé", + "security.col.credential": "Identifiant", + "security.col.ceiling": "Plafond", + "security.col.blockR3": "Bloquer R3", + "security.col.when": "Quand", + "security.col.event": "Événement", + "security.col.actor": "Acteur", + "security.start": "Démarrer", + "security.pause": "Pause", + "security.reconLab": "Recon lab.local", + "security.approveOnce": "Approuver une fois", + "security.deny": "Refuser", + "security.reasonBounded": "une fois limitée", + "security.reasonDenied": "refusé", + "security.hashes": "Hashs favoris : {hashes}", + "security.loadFailed": "Impossible de charger les données du plan de contrôle de sécurité.", + "credentials.title": "Plan de contrôle d’accès fournisseur", + "credentials.subtitle": "Identifiants chiffrés, pool de jetons contrôlé, passerelle OAuth et baux gouvernés par politique. Routable par hash ; pas une ligne NAV.", + "credentials.refresh": "Actualiser", + "credentials.refreshing": "Actualisation…", + "credentials.banner": "Les secrets ne sont jamais listés en clair. Les contrôles de santé de cette phase sont des fixtures in-process. Ce plan n’inscrit pas de comptes fournisseur.", + "credentials.flagOff": "Le drapeau CREDENTIAL_RUNTIME_ENABLED est désactivé — les mutations sont refusées.", + "credentials.status.posting": "POST {path}", + "credentials.status.ok": "OK {path}", + "credentials.status.failed": "Échec : {error}", + "credentials.tab.overview": "Aperçu", + "credentials.tab.list": "Identifiants", + "credentials.tab.providers": "Fournisseurs", + "credentials.tab.oauth": "OAuth", + "credentials.tab.pool": "Pool", + "credentials.tab.health": "Santé", + "credentials.tab.quota": "Quota", + "credentials.tab.policies": "Politiques", + "credentials.tab.approvals": "Approbations", + "credentials.tab.audit": "Audit", + "credentials.card.enabled": "Activé", + "credentials.card.total": "Total", + "credentials.card.healthy": "Sain", + "credentials.card.degraded": "Dégradé", + "credentials.card.quarantined": "Quarantaine", + "credentials.card.revoked": "Révoqué", + "credentials.card.leases": "Baux", + "credentials.card.breakers": "Disjoncteurs", + "credentials.card.vault": "Coffre", + "credentials.yes": "oui", + "credentials.no": "non", + "credentials.available": "disponible", + "credentials.unavailable": "indisponible", + "credentials.col.id": "ID", + "credentials.col.name": "Nom", + "credentials.col.provider": "Fournisseur", + "credentials.col.status": "Statut", + "credentials.col.health": "Santé", + "credentials.col.secret": "Secret", + "credentials.col.eligible": "Éligible", + "credentials.col.actions": "Actions", + "credentials.col.score": "Score", + "credentials.col.adapter": "Adaptateur", + "credentials.col.oauth": "OAuth", + "credentials.col.enabled": "Activé", + "credentials.col.session": "Session", + "credentials.col.redirect": "Redirection", + "credentials.col.budget": "Budget", + "credentials.col.priority": "Priorité", + "credentials.col.action": "Action", + "credentials.col.target": "Cible", + "credentials.col.when": "Quand", + "credentials.col.event": "Événement", + "credentials.col.actor": "Acteur", + "credentials.col.decision": "Décision", + "credentials.validate": "Valider", + "credentials.quarantine": "Mettre en quarantaine", + "credentials.revoke": "Révoquer", + "credentials.healthRun": "Lancer le contrôle de santé", + "credentials.approve": "Approuver", + "credentials.reject": "Refuser", + "credentials.hashes": "Hashs favoris : {hashes}", + "credentials.loadFailed": "Impossible de charger les données du runtime d’identifiants.", }; diff --git a/gui/src/i18n/ja.ts b/gui/src/i18n/ja.ts index b21d4d59b7..1874c9cbd8 100644 --- a/gui/src/i18n/ja.ts +++ b/gui/src/i18n/ja.ts @@ -66,6 +66,9 @@ export const ja: Record = { "nav.models": "モデル", "nav.combos": "コンボ", "nav.subagents": "サブエージェント", + "nav.skills": "スキル", + "nav.security": "セキュリティ", + "nav.credentials": "資格情報", // routing intelligence "routing.title": "ルーティングインテリジェンス (beta)", @@ -2861,4 +2864,221 @@ export const ja: Record = { "remote.event.status": "状態", "remote.event.tool": "リモートツール", "remote.event.error": "エラー", + "skills.title": "スキル制御プレーン", + "skills.subtitle": "ユニバーサルエージェントスキル登録、ビジュアルマーケットプレイス、ポリシー管理のデプロイ。", + "skills.refresh": "更新", + "skills.refreshing": "更新中…", + "skills.tab.overview": "概要", + "skills.tab.marketplace": "マーケットプレイス", + "skills.tab.registry": "レジストリ ({count})", + "skills.tab.editor": "スキルエディタ", + "skills.tab.matrix": "デプロイ行列", + "skills.tab.agents": "エージェント", + "skills.tab.nodes": "リモートノード", + "skills.tab.drift": "ドリフトとロールバック", + "skills.tab.reviews": "レビュー待ち", + "skills.tab.audit": "監査ログ", + "skills.card.registered": "登録済みスキル", + "skills.card.deployments": "稼働中のデプロイ", + "skills.card.marketplace": "マーケットプレイス項目", + "skills.card.audit": "監査イベント", + "skills.recentActivity": "最近の制御プレーン活動", + "skills.noEvents": "最近のイベントはありません。", + "skills.col.timestamp": "時刻", + "skills.col.event": "イベント", + "skills.col.actor": "アクター", + "skills.col.target": "対象", + "skills.searchPlaceholder": "公開 skills.sh カタログを検索…", + "skills.byAuthor": "v{version} / {author}", + "skills.verified": "検証済み", + "skills.importToRegistry": "レジストリへ取り込み", + "skills.emptyRegistry": "スキルはまだ登録されていません。マーケットプレイスまたはローカルフォルダから取り込んでください。", + "skills.col.skill": "スキル", + "skills.col.version": "バージョン", + "skills.col.status": "状態", + "skills.col.risk": "リスク", + "skills.col.tags": "タグ", + "skills.col.actions": "操作", + "skills.publish": "公開", + "skills.deployCodex": "Codex へデプロイ", + "skills.editorTitle": "SKILL.md エディタ", + "skills.editorNamePlaceholder": "スキル名", + "skills.editorVersionPlaceholder": "バージョン", + "skills.editorDefaultName": "カスタムアシスタント", + "skills.editorDefaultMarkdown": "---\nname: custom-assistant\ndisplayName: Custom Assistant\nversion: 1.0.0\ndescription: Custom guidance procedure.\ntags: custom, utility\n---\n\n# Custom Assistant\n\nInstructions for the agent.\n", + "skills.saveDraft": "下書きを保存", + "skills.previewTitle": "ライブプレビュー", + "skills.matrixTitle": "フリートデプロイ行列", + "skills.agent.codex": "Codex", + "skills.agent.claudeCode": "Claude Code", + "skills.agent.opencode": "OpenCode", + "skills.agent.universal": "Universal", + "skills.notInstalled": "未インストール", + "skills.inSync": "v{version} 同期済み", + "skills.blocked": "ブロック", + "skills.agentsTitle": "対象エージェントフリート", + "skills.col.agent": "エージェント", + "skills.col.userRoot": "対象ルート(ユーザースコープ)", + "skills.col.projectRoot": "対象ルート(プロジェクトスコープ)", + "skills.col.adapterVersion": "アダプタ版", + "skills.agent.openaiCodex": "OpenAI Codex", + "skills.agent.universalAgent": "ユニバーサルエージェント", + "skills.statusReady": "準備完了", + "skills.nodesTitle": "リモート実行ノード (SSH)", + "skills.nodesHelp": "ホスト鍵のピン留めを厳守して、リモート VPS、GPU ワーカー、開発ホストを管理します。", + "skills.col.node": "ノード", + "skills.col.type": "種類", + "skills.col.host": "ホスト", + "skills.col.environment": "環境", + "skills.localMachine": "ローカルマシン", + "skills.self": "自身", + "skills.vpsMain": "VPS Main", + "skills.testConnection": "接続テスト", + "skills.driftTitle": "構成ドリフトとロールバック", + "skills.driftHelp": "継続監視はライブディスクファイルを不変のレジストリハッシュと比較します。", + "skills.col.deployment": "デプロイ", + "skills.col.targetPath": "対象パス", + "skills.col.driftState": "ドリフト状態", + "skills.checkHash": "ハッシュ確認", + "skills.reviewsTitle": "ポリシーと承認キュー", + "skills.reviewsEmpty": "オペレータ判断が必要な保留中の昇格レビューはありません。", + "skills.auditTitle": "監査ログ", + "skills.col.eventType": "イベント種別", + "skills.col.targetId": "対象 ID", + "skills.system": "system", + "skills.status.importing": "{id} を取り込み中…", + "skills.status.imported": "{id} を取り込みました。", + "skills.status.importFailed": "取り込み失敗: {error}", + "skills.status.publishing": "{id} を公開中…", + "skills.status.published": "{id} を公開しました。", + "skills.status.publishFailed": "公開失敗: {error}", + "skills.status.planning": "{id} を {agent} へデプロイする計画中…", + "skills.status.deployed": "{id} を {agent} へデプロイしました。", + "skills.status.deployFailed": "デプロイ失敗: {error}", + "skills.status.saved": "スキル下書きをレジストリに保存しました。", + "skills.status.saveFailed": "保存失敗: {error}", + "skills.loadFailed": "スキル制御プレーンのデータを読み込めませんでした。", + "security.title": "セキュリティ制御プレーン", + "security.subtitle": "認可優先の偵察と検証。対象向け作業は既定で拒否。R3 はブロック。ハッシュで到達可能。サイドバー NAV 行ではありません。", + "security.refresh": "更新", + "security.refreshing": "更新中…", + "security.banner": "認可済み対象のみ。未知の資産、期限切れの認可、除外ホストは拒否されます。第三者のライブツールは呼び出しません。偵察はローカルフィクスチャを使います。", + "security.flagOff": "機能フラグ PAO_SECURITY_CONTROL_PLANE はオフです。変更は拒否されます。", + "security.status.posting": "POST {path}", + "security.status.ok": "OK {path}", + "security.status.failed": "失敗: {error}", + "security.tab.overview": "概要", + "security.tab.authorizations": "認可", + "security.tab.scopes": "スコープ", + "security.tab.campaigns": "キャンペーン", + "security.tab.approvals": "承認", + "security.tab.findings": "検出", + "security.tab.evidence": "証拠", + "security.tab.skills": "パッケージ", + "security.tab.tools": "ツール", + "security.tab.mcp": "MCP", + "security.tab.policies": "ポリシー", + "security.tab.audit": "監査", + "security.card.enabled": "有効", + "security.card.campaigns": "キャンペーン", + "security.card.approvals": "承認", + "security.card.leads": "リード", + "security.card.validated": "検証済み", + "security.card.blocked": "ブロック", + "security.card.authExpiring": "認可の期限切れ間近", + "security.card.breakers": "サーキットブレーカ", + "security.yes": "はい", + "security.no": "いいえ", + "security.col.id": "ID", + "security.col.type": "種類", + "security.col.status": "状態", + "security.col.validUntil": "有効期限", + "security.col.name": "名前", + "security.col.actions": "操作", + "security.col.target": "対象", + "security.col.title": "タイトル", + "security.col.severity": "重大度", + "security.col.sha256": "SHA-256", + "security.col.preview": "プレビュー", + "security.col.compatibility": "互換性", + "security.col.kind": "種別", + "security.col.allowlisted": "許可リスト", + "security.col.health": "健全性", + "security.col.credential": "資格情報", + "security.col.ceiling": "上限", + "security.col.blockR3": "R3 をブロック", + "security.col.when": "日時", + "security.col.event": "イベント", + "security.col.actor": "アクター", + "security.start": "開始", + "security.pause": "一時停止", + "security.reconLab": "Recon lab.local", + "security.approveOnce": "一度だけ承認", + "security.deny": "拒否", + "security.reasonBounded": "一度限り", + "security.reasonDenied": "拒否", + "security.hashes": "ブックマーク可能なハッシュ: {hashes}", + "security.loadFailed": "セキュリティ制御プレーンのデータを読み込めませんでした。", + "credentials.title": "プロバイダーアクセス制御プレーン", + "credentials.subtitle": "暗号化された資格情報、ヘルスチェック済みトークンプール、OAuth ゲートウェイ、ポリシー管理リース。ハッシュで到達可能。サイドバー NAV 行ではありません。", + "credentials.refresh": "更新", + "credentials.refreshing": "更新中…", + "credentials.banner": "秘密は平文では一覧表示されません。このフェーズのヘルスチェックはフィクスチャ/インプロセスです。このプレーンはプロバイダーアカウントを登録しません。", + "credentials.flagOff": "機能フラグ CREDENTIAL_RUNTIME_ENABLED はオフです。変更は拒否されます。", + "credentials.status.posting": "POST {path}", + "credentials.status.ok": "OK {path}", + "credentials.status.failed": "失敗: {error}", + "credentials.tab.overview": "概要", + "credentials.tab.list": "資格情報", + "credentials.tab.providers": "プロバイダー", + "credentials.tab.oauth": "OAuth", + "credentials.tab.pool": "プール", + "credentials.tab.health": "健全性", + "credentials.tab.quota": "クォータ", + "credentials.tab.policies": "ポリシー", + "credentials.tab.approvals": "承認", + "credentials.tab.audit": "監査", + "credentials.card.enabled": "有効", + "credentials.card.total": "合計", + "credentials.card.healthy": "正常", + "credentials.card.degraded": "低下", + "credentials.card.quarantined": "隔離", + "credentials.card.revoked": "取り消済み", + "credentials.card.leases": "リース", + "credentials.card.breakers": "ブレーカ", + "credentials.card.vault": "ボールト", + "credentials.yes": "はい", + "credentials.no": "いいえ", + "credentials.available": "利用可", + "credentials.unavailable": "利用不可", + "credentials.col.id": "ID", + "credentials.col.name": "名前", + "credentials.col.provider": "プロバイダー", + "credentials.col.status": "状態", + "credentials.col.health": "健全性", + "credentials.col.secret": "秘密", + "credentials.col.eligible": "適格", + "credentials.col.actions": "操作", + "credentials.col.score": "スコア", + "credentials.col.adapter": "アダプター", + "credentials.col.oauth": "OAuth", + "credentials.col.enabled": "有効", + "credentials.col.session": "セッション", + "credentials.col.redirect": "リダイレクト", + "credentials.col.budget": "予算", + "credentials.col.priority": "優先度", + "credentials.col.action": "アクション", + "credentials.col.target": "対象", + "credentials.col.when": "日時", + "credentials.col.event": "イベント", + "credentials.col.actor": "アクター", + "credentials.col.decision": "判定", + "credentials.validate": "検証", + "credentials.quarantine": "隔離", + "credentials.revoke": "取り消し", + "credentials.healthRun": "ヘルスパスを実行", + "credentials.approve": "承認", + "credentials.reject": "拒否", + "credentials.hashes": "ブックマーク可能なハッシュ: {hashes}", + "credentials.loadFailed": "資格情報ランタイムのデータを読み込めませんでした。", }; diff --git a/gui/src/i18n/ko.ts b/gui/src/i18n/ko.ts index a6e76eb7b1..304f90290a 100644 --- a/gui/src/i18n/ko.ts +++ b/gui/src/i18n/ko.ts @@ -66,6 +66,9 @@ export const ko: Record = { "nav.models": "모델", "nav.combos": "콤보", "nav.subagents": "서브에이전트", + "nav.skills": "스킬", + "nav.security": "보안", + "nav.credentials": "자격 증명", // routing intelligence "routing.title": "라우팅 인텔리전스 (beta)", @@ -2862,4 +2865,221 @@ export const ko: Record = { "remote.event.status": "상태", "remote.event.tool": "원격 도구", "remote.event.error": "오류", + "skills.title": "스킬 제어 플레인", + "skills.subtitle": "범용 에이전트 스킬 레지스트리, 시각 마켓플레이스, 정책 기반 배포.", + "skills.refresh": "새로고침", + "skills.refreshing": "새로고침 중…", + "skills.tab.overview": "개요", + "skills.tab.marketplace": "마켓플레이스", + "skills.tab.registry": "레지스트리 ({count})", + "skills.tab.editor": "스킬 편집기", + "skills.tab.matrix": "배포 매트릭스", + "skills.tab.agents": "에이전트", + "skills.tab.nodes": "원격 노드", + "skills.tab.drift": "드리프트 및 롤백", + "skills.tab.reviews": "검토 대기열", + "skills.tab.audit": "감사 기록", + "skills.card.registered": "등록된 스킬", + "skills.card.deployments": "활성 배포", + "skills.card.marketplace": "마켓플레이스 항목", + "skills.card.audit": "감사 이벤트", + "skills.recentActivity": "최근 제어 플레인 활동", + "skills.noEvents": "최근 이벤트가 없습니다.", + "skills.col.timestamp": "시각", + "skills.col.event": "이벤트", + "skills.col.actor": "행위자", + "skills.col.target": "대상", + "skills.searchPlaceholder": "공개 skills.sh 카탈로그 검색…", + "skills.byAuthor": "v{version} · {author}", + "skills.verified": "검증됨", + "skills.importToRegistry": "레지스트리로 가져오기", + "skills.emptyRegistry": "등록된 스킬이 없습니다. 마켓플레이스 또는 로컬 폴더에서 가져오세요.", + "skills.col.skill": "스킬", + "skills.col.version": "버전", + "skills.col.status": "상태", + "skills.col.risk": "위험", + "skills.col.tags": "태그", + "skills.col.actions": "작업", + "skills.publish": "게시", + "skills.deployCodex": "Codex에 배포", + "skills.editorTitle": "SKILL.md 편집기", + "skills.editorNamePlaceholder": "스킬 이름", + "skills.editorVersionPlaceholder": "버전", + "skills.editorDefaultName": "사용자 지정 어시스턴트", + "skills.editorDefaultMarkdown": "---\nname: custom-assistant\ndisplayName: Custom Assistant\nversion: 1.0.0\ndescription: Custom guidance procedure.\ntags: custom, utility\n---\n\n# Custom Assistant\n\nInstructions for the agent.\n", + "skills.saveDraft": "초안 저장", + "skills.previewTitle": "실시간 미리보기", + "skills.matrixTitle": "플릿 배포 매트릭스", + "skills.agent.codex": "Codex", + "skills.agent.claudeCode": "Claude Code", + "skills.agent.opencode": "OpenCode", + "skills.agent.universal": "Universal", + "skills.notInstalled": "설치되지 않음", + "skills.inSync": "v{version} 동기화됨", + "skills.blocked": "차단됨", + "skills.agentsTitle": "대상 에이전트 플릿", + "skills.col.agent": "에이전트", + "skills.col.userRoot": "대상 루트(사용자 범위)", + "skills.col.projectRoot": "대상 루트(프로젝트 범위)", + "skills.col.adapterVersion": "어댑터 버전", + "skills.agent.openaiCodex": "OpenAI Codex", + "skills.agent.universalAgent": "범용 에이전트", + "skills.statusReady": "준비됨", + "skills.nodesTitle": "원격 실행 노드(SSH)", + "skills.nodesHelp": "호스트 키 핀을 엄격히 적용하여 원격 VPS, GPU 워커, 개발 호스트를 관리합니다.", + "skills.col.node": "노드", + "skills.col.type": "유형", + "skills.col.host": "호스트", + "skills.col.environment": "환경", + "skills.localMachine": "로컬 머신", + "skills.self": "자신", + "skills.vpsMain": "VPS Main", + "skills.testConnection": "연결 테스트", + "skills.driftTitle": "구성 드리프트 및 롤백", + "skills.driftHelp": "지속 모니터링은 라이브 디스크 파일을 불변 레지스트리 콘텐츠 해시와 비교합니다.", + "skills.col.deployment": "배포", + "skills.col.targetPath": "대상 경로", + "skills.col.driftState": "드리프트 상태", + "skills.checkHash": "해시 확인", + "skills.reviewsTitle": "정책 및 승인 대기열", + "skills.reviewsEmpty": "운영자 결정이 필요한 보류 중인 승격 검토가 없습니다.", + "skills.auditTitle": "감사 로그", + "skills.col.eventType": "이벤트 유형", + "skills.col.targetId": "대상 ID", + "skills.system": "system", + "skills.status.importing": "{id} 가져오는 중…", + "skills.status.imported": "{id}을(를) 가져왔습니다.", + "skills.status.importFailed": "가져오기 실패: {error}", + "skills.status.publishing": "{id} 게시 중…", + "skills.status.published": "{id}을(를) 게시했습니다.", + "skills.status.publishFailed": "게시 실패: {error}", + "skills.status.planning": "{id}을(를) {agent}에 배포할 계획 중…", + "skills.status.deployed": "{id}을(를) {agent}에 배포했습니다.", + "skills.status.deployFailed": "배포 실패: {error}", + "skills.status.saved": "스킬 초안을 레지스트리에 저장했습니다.", + "skills.status.saveFailed": "저장 실패: {error}", + "skills.loadFailed": "스킬 제어 플레인 데이터를 불러오지 못했습니다.", + "security.title": "보안 제어 플레인", + "security.subtitle": "인가 우선 정찰 및 검증. 대상 작업은 기본적으로 거부됩니다. R3는 차단됩니다. 해시로 이동 가능하며 사이드바 NAV 행이 아닙니다.", + "security.refresh": "새로고침", + "security.refreshing": "새로고침 중…", + "security.banner": "인가된 대상만. 알 수 없는 자산, 만료된 인가, 제외된 호스트는 거부됩니다. 라이브 타사 도구는 호출하지 않으며 정찰은 로컬 픽스처를 사용합니다.", + "security.flagOff": "기능 플래그 PAO_SECURITY_CONTROL_PLANE이 꺼져 있어 변경이 거부됩니다.", + "security.status.posting": "POST {path}", + "security.status.ok": "OK {path}", + "security.status.failed": "실패: {error}", + "security.tab.overview": "개요", + "security.tab.authorizations": "인가", + "security.tab.scopes": "범위", + "security.tab.campaigns": "캠페인", + "security.tab.approvals": "승인", + "security.tab.findings": "발견", + "security.tab.evidence": "증거", + "security.tab.skills": "패키지", + "security.tab.tools": "도구", + "security.tab.mcp": "MCP", + "security.tab.policies": "정책", + "security.tab.audit": "감사", + "security.card.enabled": "사용", + "security.card.campaigns": "캠페인", + "security.card.approvals": "승인", + "security.card.leads": "리드", + "security.card.validated": "검증됨", + "security.card.blocked": "차단됨", + "security.card.authExpiring": "인가 만료 임박", + "security.card.breakers": "회로 차단기", + "security.yes": "예", + "security.no": "아니요", + "security.col.id": "ID", + "security.col.type": "유형", + "security.col.status": "상태", + "security.col.validUntil": "유효 기간", + "security.col.name": "이름", + "security.col.actions": "작업", + "security.col.target": "대상", + "security.col.title": "제목", + "security.col.severity": "심각도", + "security.col.sha256": "SHA-256", + "security.col.preview": "미리보기", + "security.col.compatibility": "호환성", + "security.col.kind": "종류", + "security.col.allowlisted": "허용 목록", + "security.col.health": "상태", + "security.col.credential": "자격 증명", + "security.col.ceiling": "상한", + "security.col.blockR3": "R3 차단", + "security.col.when": "시각", + "security.col.event": "이벤트", + "security.col.actor": "행위자", + "security.start": "시작", + "security.pause": "일시 중지", + "security.reconLab": "Recon lab.local", + "security.approveOnce": "한 번만 승인", + "security.deny": "거부", + "security.reasonBounded": "한 번으로 제한", + "security.reasonDenied": "거부됨", + "security.hashes": "북마크 가능한 해시: {hashes}", + "security.loadFailed": "보안 제어 플레인 데이터를 불러오지 못했습니다.", + "credentials.title": "프로바이더 액세스 제어 플레인", + "credentials.subtitle": "암호화된 자격 증명, 상태 점검 토큰 풀, OAuth 게이트웨이, 정책 기반 리스. 해시로 이동 가능하며 사이드바 NAV 행이 아닙니다.", + "credentials.refresh": "새로 고침", + "credentials.refreshing": "새로 고치는 중…", + "credentials.banner": "비밀은 평문으로 목록에 표시되지 않습니다. 이 단계의 상태 점검은 픽스처/인프로세스입니다. 이 플레인은 프로바이더 계정을 등록하지 않습니다.", + "credentials.flagOff": "기능 플래그 CREDENTIAL_RUNTIME_ENABLED가 꺼져 있어 변경이 거부됩니다.", + "credentials.status.posting": "POST {path}", + "credentials.status.ok": "OK {path}", + "credentials.status.failed": "실패: {error}", + "credentials.tab.overview": "개요", + "credentials.tab.list": "자격 증명", + "credentials.tab.providers": "프로바이더", + "credentials.tab.oauth": "OAuth", + "credentials.tab.pool": "풀", + "credentials.tab.health": "상태", + "credentials.tab.quota": "할당량", + "credentials.tab.policies": "정책", + "credentials.tab.approvals": "승인", + "credentials.tab.audit": "감사", + "credentials.card.enabled": "사용", + "credentials.card.total": "전체", + "credentials.card.healthy": "정상", + "credentials.card.degraded": "저하", + "credentials.card.quarantined": "격리", + "credentials.card.revoked": "취소됨", + "credentials.card.leases": "리스", + "credentials.card.breakers": "차단기", + "credentials.card.vault": "볼트", + "credentials.yes": "예", + "credentials.no": "아니요", + "credentials.available": "사용 가능", + "credentials.unavailable": "사용 불가", + "credentials.col.id": "ID", + "credentials.col.name": "이름", + "credentials.col.provider": "프로바이더", + "credentials.col.status": "상태", + "credentials.col.health": "상태", + "credentials.col.secret": "비밀", + "credentials.col.eligible": "적격", + "credentials.col.actions": "작업", + "credentials.col.score": "점수", + "credentials.col.adapter": "어댑터", + "credentials.col.oauth": "OAuth", + "credentials.col.enabled": "사용", + "credentials.col.session": "세션", + "credentials.col.redirect": "리디렉션", + "credentials.col.budget": "예산", + "credentials.col.priority": "우선순위", + "credentials.col.action": "작업", + "credentials.col.target": "대상", + "credentials.col.when": "시각", + "credentials.col.event": "이벤트", + "credentials.col.actor": "행위자", + "credentials.col.decision": "결정", + "credentials.validate": "검증", + "credentials.quarantine": "격리", + "credentials.revoke": "취소", + "credentials.healthRun": "상태 점검 실행", + "credentials.approve": "승인", + "credentials.reject": "거부", + "credentials.hashes": "북마크 가능한 해시: {hashes}", + "credentials.loadFailed": "자격 증명 런타임 데이터를 불러오지 못했습니다.", }; diff --git a/gui/src/i18n/ru.ts b/gui/src/i18n/ru.ts index c28f4464c3..126c07c17d 100644 --- a/gui/src/i18n/ru.ts +++ b/gui/src/i18n/ru.ts @@ -66,6 +66,9 @@ export const ru: Record = { "nav.models": "Модели", "nav.combos": "Комбо", "nav.subagents": "Подагенты", + "nav.skills": "Навыки", + "nav.security": "Безопасность", + "nav.credentials": "Учётные данные", // routing intelligence "routing.title": "Интеллект маршрутизации (beta)", @@ -2863,4 +2866,221 @@ export const ru: Record = { "remote.event.status": "Состояние", "remote.event.tool": "Удалённый инструмент", "remote.event.error": "Ошибка", + "skills.title": "Плоскость управления навыками", + "skills.subtitle": "Универсальный реестр навыков агентов, визуальная витрина и развёртывание под управлением политик.", + "skills.refresh": "Обновить", + "skills.refreshing": "Обновление…", + "skills.tab.overview": "Обзор", + "skills.tab.marketplace": "Витрина", + "skills.tab.registry": "Реестр ({count})", + "skills.tab.editor": "Редактор навыка", + "skills.tab.matrix": "Матрица развёртывания", + "skills.tab.agents": "Агенты", + "skills.tab.nodes": "Удалённые узлы", + "skills.tab.drift": "Дрейф и откат", + "skills.tab.reviews": "Очередь проверки", + "skills.tab.audit": "Журнал аудита", + "skills.card.registered": "Зарегистрированные навыки", + "skills.card.deployments": "Активные развёртывания", + "skills.card.marketplace": "Элементы витрины", + "skills.card.audit": "События аудита", + "skills.recentActivity": "Недавняя активность плоскости управления", + "skills.noEvents": "Недавних событий нет.", + "skills.col.timestamp": "Время", + "skills.col.event": "Событие", + "skills.col.actor": "Субъект", + "skills.col.target": "Цель", + "skills.searchPlaceholder": "Поиск в публичном каталоге skills.sh…", + "skills.byAuthor": "v{version} · {author}", + "skills.verified": "Проверено", + "skills.importToRegistry": "Импортировать в реестр", + "skills.emptyRegistry": "Навыки ещё не зарегистрированы. Импортируйте из витрины или локальной папки.", + "skills.col.skill": "Навык", + "skills.col.version": "Версия", + "skills.col.status": "Статус", + "skills.col.risk": "Риск", + "skills.col.tags": "Теги", + "skills.col.actions": "Действия", + "skills.publish": "Опубликовать", + "skills.deployCodex": "Развернуть в Codex", + "skills.editorTitle": "Редактор SKILL.md", + "skills.editorNamePlaceholder": "Имя навыка", + "skills.editorVersionPlaceholder": "Версия", + "skills.editorDefaultName": "Пользовательский помощник", + "skills.editorDefaultMarkdown": "---\nname: custom-assistant\ndisplayName: Custom Assistant\nversion: 1.0.0\ndescription: Custom guidance procedure.\ntags: custom, utility\n---\n\n# Custom Assistant\n\nInstructions for the agent.\n", + "skills.saveDraft": "Сохранить черновик", + "skills.previewTitle": "Предпросмотр в реальном времени", + "skills.matrixTitle": "Матрица развёртывания флота", + "skills.agent.codex": "Codex", + "skills.agent.claudeCode": "Claude Code", + "skills.agent.opencode": "OpenCode", + "skills.agent.universal": "Universal", + "skills.notInstalled": "Не установлено", + "skills.inSync": "v{version} синхронизировано", + "skills.blocked": "Заблокировано", + "skills.agentsTitle": "Целевой флот агентов", + "skills.col.agent": "Агент", + "skills.col.userRoot": "Корневой путь (пользовательский)", + "skills.col.projectRoot": "Корневой путь (проектный)", + "skills.col.adapterVersion": "Версия адаптера", + "skills.agent.openaiCodex": "OpenAI Codex", + "skills.agent.universalAgent": "Универсальный агент", + "skills.statusReady": "Готово", + "skills.nodesTitle": "Удалённые узлы выполнения (SSH)", + "skills.nodesHelp": "Управляйте удалёнными VPS, GPU-воркерами и хостами разработки со строгой привязкой ключей хоста.", + "skills.col.node": "Узел", + "skills.col.type": "Тип", + "skills.col.host": "Хост", + "skills.col.environment": "Среда", + "skills.localMachine": "Локальная машина", + "skills.self": "Сама", + "skills.vpsMain": "VPS Main", + "skills.testConnection": "Проверить соединение", + "skills.driftTitle": "Дрейф конфигурации и откат", + "skills.driftHelp": "Непрерывный мониторинг сравнивает файлы на диске с неизменяемыми хешами реестра.", + "skills.col.deployment": "Развёртывание", + "skills.col.targetPath": "Целевой путь", + "skills.col.driftState": "Состояние дрейфа", + "skills.checkHash": "Проверить хеш", + "skills.reviewsTitle": "Очередь политик и утверждений", + "skills.reviewsEmpty": "Нет ожидающих повышенных проверок, требующих решения оператора.", + "skills.auditTitle": "Журнал аудита", + "skills.col.eventType": "Тип события", + "skills.col.targetId": "ID цели", + "skills.system": "system", + "skills.status.importing": "Импорт {id}…", + "skills.status.imported": "{id} импортирован.", + "skills.status.importFailed": "Ошибка импорта: {error}", + "skills.status.publishing": "Публикация {id}…", + "skills.status.published": "{id} опубликован.", + "skills.status.publishFailed": "Ошибка публикации: {error}", + "skills.status.planning": "Планирование развёртывания {id} в {agent}…", + "skills.status.deployed": "{id} развёрнут в {agent}.", + "skills.status.deployFailed": "Ошибка развёртывания: {error}", + "skills.status.saved": "Черновик навыка сохранён в реестре.", + "skills.status.saveFailed": "Ошибка сохранения: {error}", + "skills.loadFailed": "Не удалось загрузить данные плоскости управления навыками.", + "security.title": "Плоскость управления безопасностью", + "security.subtitle": "Разведка и проверка с приоритетом авторизации. Работа с целью по умолчанию запрещена. R3 заблокирован. Доступен по хешу; не строка боковой NAV.", + "security.refresh": "Обновить", + "security.refreshing": "Обновление…", + "security.banner": "Только авторизованные цели. Неизвестные активы, истёкшие авторизации и исключённые хосты отклоняются. Живые сторонние инструменты не вызываются; разведка использует локальные фикстуры.", + "security.flagOff": "Флаг PAO_SECURITY_CONTROL_PLANE выключен — изменения отклоняются.", + "security.status.posting": "POST {path}", + "security.status.ok": "OK {path}", + "security.status.failed": "Ошибка: {error}", + "security.tab.overview": "Обзор", + "security.tab.authorizations": "Авторизации", + "security.tab.scopes": "Области", + "security.tab.campaigns": "Кампании", + "security.tab.approvals": "Утверждения", + "security.tab.findings": "Находки", + "security.tab.evidence": "Доказательства", + "security.tab.skills": "Пакеты", + "security.tab.tools": "Инструменты", + "security.tab.mcp": "MCP", + "security.tab.policies": "Политики", + "security.tab.audit": "Аудит", + "security.card.enabled": "Включено", + "security.card.campaigns": "Кампании", + "security.card.approvals": "Утверждения", + "security.card.leads": "Лиды", + "security.card.validated": "Проверено", + "security.card.blocked": "Заблокировано", + "security.card.authExpiring": "Авторизация истекает", + "security.card.breakers": "Автоматы", + "security.yes": "да", + "security.no": "нет", + "security.col.id": "ID", + "security.col.type": "Тип", + "security.col.status": "Статус", + "security.col.validUntil": "Действует до", + "security.col.name": "Имя", + "security.col.actions": "Действия", + "security.col.target": "Цель", + "security.col.title": "Заголовок", + "security.col.severity": "Серьёзность", + "security.col.sha256": "SHA-256", + "security.col.preview": "Предпросмотр", + "security.col.compatibility": "Совместимость", + "security.col.kind": "Вид", + "security.col.allowlisted": "В списке разрешённых", + "security.col.health": "Состояние", + "security.col.credential": "Учётные данные", + "security.col.ceiling": "Потолок", + "security.col.blockR3": "Блокировать R3", + "security.col.when": "Когда", + "security.col.event": "Событие", + "security.col.actor": "Субъект", + "security.start": "Запустить", + "security.pause": "Пауза", + "security.reconLab": "Recon lab.local", + "security.approveOnce": "Утвердить один раз", + "security.deny": "Отклонить", + "security.reasonBounded": "один раз", + "security.reasonDenied": "отклонено", + "security.hashes": "Хеши для закладок: {hashes}", + "security.loadFailed": "Не удалось загрузить данные плоскости управления безопасностью.", + "credentials.title": "Плоскость управления доступом к провайдерам", + "credentials.subtitle": "Зашифрованные учётные данные, пул токенов с проверкой здоровья, шлюз OAuth и аренды по политике. Доступ по хешу; не строка боковой NAV.", + "credentials.refresh": "Обновить", + "credentials.refreshing": "Обновление…", + "credentials.banner": "Секреты никогда не показываются открытым текстом. Проверки здоровья на этом этапе — фикстуры in-process. Эта плоскость не регистрирует аккаунты провайдеров.", + "credentials.flagOff": "Флаг CREDENTIAL_RUNTIME_ENABLED выключен — изменения отклоняются.", + "credentials.status.posting": "POST {path}", + "credentials.status.ok": "OK {path}", + "credentials.status.failed": "Сбой: {error}", + "credentials.tab.overview": "Обзор", + "credentials.tab.list": "Учётные данные", + "credentials.tab.providers": "Провайдеры", + "credentials.tab.oauth": "OAuth", + "credentials.tab.pool": "Пул", + "credentials.tab.health": "Состояние", + "credentials.tab.quota": "Квота", + "credentials.tab.policies": "Политики", + "credentials.tab.approvals": "Утверждения", + "credentials.tab.audit": "Аудит", + "credentials.card.enabled": "Включено", + "credentials.card.total": "Всего", + "credentials.card.healthy": "Здорово", + "credentials.card.degraded": "Деградация", + "credentials.card.quarantined": "Карантин", + "credentials.card.revoked": "Отозвано", + "credentials.card.leases": "Аренды", + "credentials.card.breakers": "Автоматы", + "credentials.card.vault": "Хранилище", + "credentials.yes": "да", + "credentials.no": "нет", + "credentials.available": "доступно", + "credentials.unavailable": "недоступно", + "credentials.col.id": "ID", + "credentials.col.name": "Имя", + "credentials.col.provider": "Провайдер", + "credentials.col.status": "Статус", + "credentials.col.health": "Состояние", + "credentials.col.secret": "Секрет", + "credentials.col.eligible": "Допущен", + "credentials.col.actions": "Действия", + "credentials.col.score": "Оценка", + "credentials.col.adapter": "Адаптер", + "credentials.col.oauth": "OAuth", + "credentials.col.enabled": "Включено", + "credentials.col.session": "Сессия", + "credentials.col.redirect": "Перенаправление", + "credentials.col.budget": "Бюджет", + "credentials.col.priority": "Приоритет", + "credentials.col.action": "Действие", + "credentials.col.target": "Цель", + "credentials.col.when": "Когда", + "credentials.col.event": "Событие", + "credentials.col.actor": "Субъект", + "credentials.col.decision": "Решение", + "credentials.validate": "Проверить", + "credentials.quarantine": "Карантин", + "credentials.revoke": "Отозвать", + "credentials.healthRun": "Запустить проверку", + "credentials.approve": "Утвердить", + "credentials.reject": "Отклонить", + "credentials.hashes": "Хеши для закладок: {hashes}", + "credentials.loadFailed": "Не удалось загрузить данные среды учётных данных.", }; diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index c6671c3e28..34216f4698 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -67,6 +67,9 @@ export const tr: Record = { "nav.models": "Modeller", "nav.combos": "Kombolar", "nav.subagents": "Alt Ajanlar", + "nav.skills": "Yetenekler", + "nav.security": "Güvenlik", + "nav.credentials": "Kimlik bilgileri", "nav.logs": "Günlükler & Hata Ayıklama", "nav.usage": "Kullanım", "common.github": "GitHub", @@ -2863,4 +2866,221 @@ export const tr: Record = { "remote.event.status": "Durum", "remote.event.tool": "Uzak araç", "remote.event.error": "Hata", + "skills.title": "Yetenek kontrol düzlemi", + "skills.subtitle": "Evrensel ajan yetenek kaydı, görsel pazar yeri ve politika yönetimli dağıtım.", + "skills.refresh": "Yenile", + "skills.refreshing": "Yenileniyor…", + "skills.tab.overview": "Genel bakış", + "skills.tab.marketplace": "Pazar yeri", + "skills.tab.registry": "Kayıt ({count})", + "skills.tab.editor": "Yetenek düzenleyici", + "skills.tab.matrix": "Dağıtım matrisi", + "skills.tab.agents": "Ajanlar", + "skills.tab.nodes": "Uzak düğümler", + "skills.tab.drift": "Sapma ve geri alma", + "skills.tab.reviews": "İnceleme kuyruğu", + "skills.tab.audit": "Denetim izi", + "skills.card.registered": "Kayıtlı yetenekler", + "skills.card.deployments": "Etkin dağıtımlar", + "skills.card.marketplace": "Pazar yeri öğeleri", + "skills.card.audit": "Denetim olayları", + "skills.recentActivity": "Son kontrol düzlemi etkinliği", + "skills.noEvents": "Son olay kaydı yok.", + "skills.col.timestamp": "Zaman", + "skills.col.event": "Olay", + "skills.col.actor": "Aktör", + "skills.col.target": "Hedef", + "skills.searchPlaceholder": "Herkese açık skills.sh kataloğunu ara…", + "skills.byAuthor": "v{version} · {author}", + "skills.verified": "Doğrulandı", + "skills.importToRegistry": "Kayıta içe aktar", + "skills.emptyRegistry": "Henüz kayıtlı yetenek yok. Pazar yerinden veya yerel klasörden içe aktarın.", + "skills.col.skill": "Yetenek", + "skills.col.version": "Sürüm", + "skills.col.status": "Durum", + "skills.col.risk": "Risk", + "skills.col.tags": "Etiketler", + "skills.col.actions": "Eylemler", + "skills.publish": "Yayımla", + "skills.deployCodex": "Codex’e dağıt", + "skills.editorTitle": "SKILL.md düzenleyici", + "skills.editorNamePlaceholder": "Yetenek adı", + "skills.editorVersionPlaceholder": "Sürüm", + "skills.editorDefaultName": "Özel asistan", + "skills.editorDefaultMarkdown": "---\nname: custom-assistant\ndisplayName: Custom Assistant\nversion: 1.0.0\ndescription: Custom guidance procedure.\ntags: custom, utility\n---\n\n# Custom Assistant\n\nInstructions for the agent.\n", + "skills.saveDraft": "Taslağı kaydet", + "skills.previewTitle": "Canlı önizleme", + "skills.matrixTitle": "Filo dağıtım matrisi", + "skills.agent.codex": "Codex", + "skills.agent.claudeCode": "Claude Code", + "skills.agent.opencode": "OpenCode", + "skills.agent.universal": "Universal", + "skills.notInstalled": "Yüklü değil", + "skills.inSync": "v{version} senkron", + "skills.blocked": "Engellendi", + "skills.agentsTitle": "Hedef ajan filosu", + "skills.col.agent": "Ajan", + "skills.col.userRoot": "Hedef kök (kullanıcı kapsamı)", + "skills.col.projectRoot": "Hedef kök (proje kapsamı)", + "skills.col.adapterVersion": "Bağdaştırıcı sürümü", + "skills.agent.openaiCodex": "OpenAI Codex", + "skills.agent.universalAgent": "Evrensel ajan", + "skills.statusReady": "Hazır", + "skills.nodesTitle": "Uzak yürütme düğümleri (SSH)", + "skills.nodesHelp": "Sıkı anahtar sabitlemesiyle uzak VPS, GPU işçileri ve geliştirme ana makinelerini yönetin.", + "skills.col.node": "Düğüm", + "skills.col.type": "Tür", + "skills.col.host": "Ana makine", + "skills.col.environment": "Ortam", + "skills.localMachine": "Yerel makine", + "skills.self": "Kendisi", + "skills.vpsMain": "VPS Main", + "skills.testConnection": "Bağlantıyı sına", + "skills.driftTitle": "Yapılandırma sapması ve geri alma", + "skills.driftHelp": "Sürekli izleme, diskteki canlı dosyaları değişmez kayıt içerik özetleriyle karşılaştırır.", + "skills.col.deployment": "Dağıtım", + "skills.col.targetPath": "Hedef yol", + "skills.col.driftState": "Sapma durumu", + "skills.checkHash": "Özeti denetle", + "skills.reviewsTitle": "Politika ve onay kuyruğu", + "skills.reviewsEmpty": "Operatör kararı gerektiren bekleyen yükseltilmiş inceleme yok.", + "skills.auditTitle": "Denetim günlüğü", + "skills.col.eventType": "Olay türü", + "skills.col.targetId": "Hedef kimliği", + "skills.system": "system", + "skills.status.importing": "{id} içe aktarılıyor…", + "skills.status.imported": "{id} içe aktarıldı.", + "skills.status.importFailed": "İçe aktarma başarısız: {error}", + "skills.status.publishing": "{id} yayımlanıyor…", + "skills.status.published": "{id} yayımlandı.", + "skills.status.publishFailed": "Yayımlama başarısız: {error}", + "skills.status.planning": "{id} {agent} için dağıtım planlanıyor…", + "skills.status.deployed": "{id} {agent} üzerine dağıtıldı.", + "skills.status.deployFailed": "Dağıtım başarısız: {error}", + "skills.status.saved": "Yetenek taslağı kayda kaydedildi.", + "skills.status.saveFailed": "Kaydetme başarısız: {error}", + "skills.loadFailed": "Yetenek kontrol düzlemi verileri yüklenemedi.", + "security.title": "Güvenlik kontrol düzlemi", + "security.subtitle": "Yetkilendirme öncelikli keşif ve doğrulama. Hedefe yönelik iş varsayılan olarak reddedilir. R3 engellenir. Hash ile erişilebilir; kenar çubuğu NAV satırı değildir.", + "security.refresh": "Yenile", + "security.refreshing": "Yenileniyor…", + "security.banner": "Yalnızca yetkilendirilmiş hedefler. Bilinmeyen varlıklar, süresi dolmuş yetkiler ve dışlanan ana makineler reddedilir. Canlı üçüncü taraf araçlar çağrılmaz; keşif yerel fikstürler kullanır.", + "security.flagOff": "PAO_SECURITY_CONTROL_PLANE özelliği kapalı — değişiklikler reddedilir.", + "security.status.posting": "POST {path}", + "security.status.ok": "OK {path}", + "security.status.failed": "Başarısız: {error}", + "security.tab.overview": "Genel bakış", + "security.tab.authorizations": "Yetkiler", + "security.tab.scopes": "Kapsamlar", + "security.tab.campaigns": "Kampanyalar", + "security.tab.approvals": "Onaylar", + "security.tab.findings": "Bulgular", + "security.tab.evidence": "Kanıt", + "security.tab.skills": "Paketler", + "security.tab.tools": "Araçlar", + "security.tab.mcp": "MCP", + "security.tab.policies": "Politikalar", + "security.tab.audit": "Denetim", + "security.card.enabled": "Etkin", + "security.card.campaigns": "Kampanyalar", + "security.card.approvals": "Onaylar", + "security.card.leads": "Adaylar", + "security.card.validated": "Doğrulandı", + "security.card.blocked": "Engellendi", + "security.card.authExpiring": "Yetki süresi doluyor", + "security.card.breakers": "Kesiciler", + "security.yes": "evet", + "security.no": "hayır", + "security.col.id": "ID", + "security.col.type": "Tür", + "security.col.status": "Durum", + "security.col.validUntil": "Geçerlilik sonu", + "security.col.name": "Ad", + "security.col.actions": "Eylemler", + "security.col.target": "Hedef", + "security.col.title": "Başlık", + "security.col.severity": "Önem", + "security.col.sha256": "SHA-256", + "security.col.preview": "Önizleme", + "security.col.compatibility": "Uyumluluk", + "security.col.kind": "Tür", + "security.col.allowlisted": "İzin listesinde", + "security.col.health": "Sağlık", + "security.col.credential": "Kimlik bilgisi", + "security.col.ceiling": "Tavan", + "security.col.blockR3": "R3’ü engelle", + "security.col.when": "Ne zaman", + "security.col.event": "Olay", + "security.col.actor": "Aktör", + "security.start": "Başlat", + "security.pause": "Duraklat", + "security.reconLab": "Recon lab.local", + "security.approveOnce": "Bir kez onayla", + "security.deny": "Reddet", + "security.reasonBounded": "bir kez sınırlı", + "security.reasonDenied": "reddedildi", + "security.hashes": "Yer imi hash’leri: {hashes}", + "security.loadFailed": "Güvenlik kontrol düzlemi verileri yüklenemedi.", + "credentials.title": "Sağlayıcı erişim kontrol düzlemi", + "credentials.subtitle": "Şifreli kimlik bilgileri, sağlık denetimli jeton havuzu, OAuth ağ geçidi ve politika yönetimli kiralamalar. Hash ile yönlendirilebilir; kenar çubuğu NAV satırı değil.", + "credentials.refresh": "Yenile", + "credentials.refreshing": "Yenileniyor…", + "credentials.banner": "Gizler asla düz metin olarak listelenmez. Bu aşamadaki sağlık denetimleri fikstür/in-process’tir. Bu düzlem sağlayıcı hesabı kaydetmez.", + "credentials.flagOff": "CREDENTIAL_RUNTIME_ENABLED bayrağı kapalı — değişiklikler reddedilir.", + "credentials.status.posting": "POST {path}", + "credentials.status.ok": "OK {path}", + "credentials.status.failed": "Başarısız: {error}", + "credentials.tab.overview": "Genel bakış", + "credentials.tab.list": "Kimlik bilgileri", + "credentials.tab.providers": "Sağlayıcılar", + "credentials.tab.oauth": "OAuth", + "credentials.tab.pool": "Havuz", + "credentials.tab.health": "Sağlık", + "credentials.tab.quota": "Kota", + "credentials.tab.policies": "Politikalar", + "credentials.tab.approvals": "Onaylar", + "credentials.tab.audit": "Denetim", + "credentials.card.enabled": "Etkin", + "credentials.card.total": "Toplam", + "credentials.card.healthy": "Sağlıklı", + "credentials.card.degraded": "Düşük", + "credentials.card.quarantined": "Karantina", + "credentials.card.revoked": "İptal", + "credentials.card.leases": "Kiralamalar", + "credentials.card.breakers": "Kesiciler", + "credentials.card.vault": "Kasa", + "credentials.yes": "evet", + "credentials.no": "hayır", + "credentials.available": "kullanılabilir", + "credentials.unavailable": "kullanılamaz", + "credentials.col.id": "ID", + "credentials.col.name": "Ad", + "credentials.col.provider": "Sağlayıcı", + "credentials.col.status": "Durum", + "credentials.col.health": "Sağlık", + "credentials.col.secret": "Giz", + "credentials.col.eligible": "Uygun", + "credentials.col.actions": "Eylemler", + "credentials.col.score": "Puan", + "credentials.col.adapter": "Bağdaştırıcı", + "credentials.col.oauth": "OAuth", + "credentials.col.enabled": "Etkin", + "credentials.col.session": "Oturum", + "credentials.col.redirect": "Yönlendirme", + "credentials.col.budget": "Bütçe", + "credentials.col.priority": "Öncelik", + "credentials.col.action": "Eylem", + "credentials.col.target": "Hedef", + "credentials.col.when": "Ne zaman", + "credentials.col.event": "Olay", + "credentials.col.actor": "Aktör", + "credentials.col.decision": "Karar", + "credentials.validate": "Doğrula", + "credentials.quarantine": "Karantinaya al", + "credentials.revoke": "İptal et", + "credentials.healthRun": "Sağlık geçişini çalıştır", + "credentials.approve": "Onayla", + "credentials.reject": "Reddet", + "credentials.hashes": "Yer imi hash’leri: {hashes}", + "credentials.loadFailed": "Kimlik bilgisi çalışma zamanı verileri yüklenemedi.", }; diff --git a/gui/src/i18n/zh-TW.ts b/gui/src/i18n/zh-TW.ts index 9e154fa1b2..1f4cbf037b 100644 --- a/gui/src/i18n/zh-TW.ts +++ b/gui/src/i18n/zh-TW.ts @@ -59,6 +59,9 @@ export const zhTW: Record = { "nav.models": "模型", "nav.combos": "組合", "nav.subagents": "子代理", + "nav.skills": "技能", + "nav.security": "安全", + "nav.credentials": "憑證", "nav.logs": "日誌與除錯", "nav.usage": "用量", "common.github": "GitHub", @@ -2826,4 +2829,221 @@ export const zhTW: Record = { "remote.event.status": "狀態", "remote.event.tool": "遠端工具", "remote.event.error": "錯誤", + "skills.title": "技能控制平面", + "skills.subtitle": "通用代理技能登錄、視覺化市集與政策治理部署。", + "skills.refresh": "重新整理", + "skills.refreshing": "正在重新整理…", + "skills.tab.overview": "總覽", + "skills.tab.marketplace": "市集", + "skills.tab.registry": "登錄({count})", + "skills.tab.editor": "技能編輯器", + "skills.tab.matrix": "部署矩陣", + "skills.tab.agents": "代理", + "skills.tab.nodes": "遠端節點", + "skills.tab.drift": "漂移與回滾", + "skills.tab.reviews": "審核佇列", + "skills.tab.audit": "稽核紀錄", + "skills.card.registered": "已登錄技能", + "skills.card.deployments": "進行中的部署", + "skills.card.marketplace": "市集項目", + "skills.card.audit": "稽核事件", + "skills.recentActivity": "最近的控制平面活動", + "skills.noEvents": "尚無最近事件。", + "skills.col.timestamp": "時間", + "skills.col.event": "事件", + "skills.col.actor": "操作者", + "skills.col.target": "目標", + "skills.searchPlaceholder": "搜尋公開 skills.sh 目錄…", + "skills.byAuthor": "v{version} · {author}", + "skills.verified": "已驗證", + "skills.importToRegistry": "匯入登錄", + "skills.emptyRegistry": "尚未登錄技能。請從市集或本機資料夾匯入。", + "skills.col.skill": "技能", + "skills.col.version": "版本", + "skills.col.status": "狀態", + "skills.col.risk": "風險", + "skills.col.tags": "標籤", + "skills.col.actions": "動作", + "skills.publish": "發佈", + "skills.deployCodex": "部署到 Codex", + "skills.editorTitle": "SKILL.md 編輯器", + "skills.editorNamePlaceholder": "技能名稱", + "skills.editorVersionPlaceholder": "版本", + "skills.editorDefaultName": "自訂助理", + "skills.editorDefaultMarkdown": "---\nname: custom-assistant\ndisplayName: Custom Assistant\nversion: 1.0.0\ndescription: Custom guidance procedure.\ntags: custom, utility\n---\n\n# Custom Assistant\n\nInstructions for the agent.\n", + "skills.saveDraft": "儲存草稿", + "skills.previewTitle": "即時預覽", + "skills.matrixTitle": "機群部署矩陣", + "skills.agent.codex": "Codex", + "skills.agent.claudeCode": "Claude Code", + "skills.agent.opencode": "OpenCode", + "skills.agent.universal": "Universal", + "skills.notInstalled": "未安裝", + "skills.inSync": "v{version} 已同步", + "skills.blocked": "已封鎖", + "skills.agentsTitle": "目標代理機群", + "skills.col.agent": "代理", + "skills.col.userRoot": "目標根目錄(使用者範圍)", + "skills.col.projectRoot": "目標根目錄(專案範圍)", + "skills.col.adapterVersion": "轉接器版本", + "skills.agent.openaiCodex": "OpenAI Codex", + "skills.agent.universalAgent": "通用代理", + "skills.statusReady": "就緒", + "skills.nodesTitle": "遠端執行節點(SSH)", + "skills.nodesHelp": "以嚴格主機金鑰釘選管理遠端 VPS、GPU 工作節點與開發主機。", + "skills.col.node": "節點", + "skills.col.type": "類型", + "skills.col.host": "主機", + "skills.col.environment": "環境", + "skills.localMachine": "本機", + "skills.self": "自身", + "skills.vpsMain": "VPS Main", + "skills.testConnection": "測試連線", + "skills.driftTitle": "組態漂移與回滾", + "skills.driftHelp": "持續監控會將磁碟上的即時檔案與不可變登錄內容雜湊進行比較。", + "skills.col.deployment": "部署", + "skills.col.targetPath": "目標路徑", + "skills.col.driftState": "漂移狀態", + "skills.checkHash": "檢查雜湊", + "skills.reviewsTitle": "政策與核准佇列", + "skills.reviewsEmpty": "沒有需要操作員決策的待處理升級審核。", + "skills.auditTitle": "稽核紀錄", + "skills.col.eventType": "事件類型", + "skills.col.targetId": "目標 ID", + "skills.system": "system", + "skills.status.importing": "正在匯入 {id}…", + "skills.status.imported": "已匯入 {id}。", + "skills.status.importFailed": "匯入失敗:{error}", + "skills.status.publishing": "正在發佈 {id}…", + "skills.status.published": "已發佈 {id}。", + "skills.status.publishFailed": "發佈失敗:{error}", + "skills.status.planning": "正在規劃將 {id} 部署到 {agent}…", + "skills.status.deployed": "已將 {id} 部署到 {agent}。", + "skills.status.deployFailed": "部署失敗:{error}", + "skills.status.saved": "技能草稿已儲存到登錄。", + "skills.status.saveFailed": "儲存失敗:{error}", + "skills.loadFailed": "無法載入技能控制平面資料。", + "security.title": "安全控制平面", + "security.subtitle": "授權優先的偵察與驗證。面向目標的工作預設拒絕。R3 已封鎖。可用雜湊前往,不是側欄 NAV 列。", + "security.refresh": "重新整理", + "security.refreshing": "正在重新整理…", + "security.banner": "僅限已授權目標。未知資產、過期授權與排除主機一律拒絕。不會呼叫即時第三方工具;偵察使用本機夾具。", + "security.flagOff": "功能旗標 PAO_SECURITY_CONTROL_PLANE 已關閉 — 變更會被拒絕。", + "security.status.posting": "POST {path}", + "security.status.ok": "OK {path}", + "security.status.failed": "失敗:{error}", + "security.tab.overview": "總覽", + "security.tab.authorizations": "授權", + "security.tab.scopes": "範圍", + "security.tab.campaigns": "活動", + "security.tab.approvals": "核准", + "security.tab.findings": "發現", + "security.tab.evidence": "證據", + "security.tab.skills": "套件", + "security.tab.tools": "工具", + "security.tab.mcp": "MCP", + "security.tab.policies": "政策", + "security.tab.audit": "稽核", + "security.card.enabled": "已啟用", + "security.card.campaigns": "活動", + "security.card.approvals": "核准", + "security.card.leads": "線索", + "security.card.validated": "已驗證", + "security.card.blocked": "已封鎖", + "security.card.authExpiring": "授權即將過期", + "security.card.breakers": "斷路器", + "security.yes": "是", + "security.no": "否", + "security.col.id": "ID", + "security.col.type": "類型", + "security.col.status": "狀態", + "security.col.validUntil": "有效期限", + "security.col.name": "名稱", + "security.col.actions": "動作", + "security.col.target": "目標", + "security.col.title": "標題", + "security.col.severity": "嚴重性", + "security.col.sha256": "SHA-256", + "security.col.preview": "預覽", + "security.col.compatibility": "相容性", + "security.col.kind": "種類", + "security.col.allowlisted": "已加入允許清單", + "security.col.health": "健康", + "security.col.credential": "憑證", + "security.col.ceiling": "上限", + "security.col.blockR3": "封鎖 R3", + "security.col.when": "時間", + "security.col.event": "事件", + "security.col.actor": "操作者", + "security.start": "開始", + "security.pause": "暫停", + "security.reconLab": "Recon lab.local", + "security.approveOnce": "核准一次", + "security.deny": "拒絕", + "security.reasonBounded": "僅限一次", + "security.reasonDenied": "已拒絕", + "security.hashes": "可加入書籤的雜湊:{hashes}", + "security.loadFailed": "無法載入安全控制平面資料。", + "credentials.title": "供應商存取控制平面", + "credentials.subtitle": "加密憑證、經健康檢查的權杖池、OAuth 閘道與政策治理租約。可透過雜湊路由;不是側邊欄 NAV 列。", + "credentials.refresh": "重新整理", + "credentials.refreshing": "重新整理中…", + "credentials.banner": "密鑰絕不以明文列出。本階段的健康檢查為夾具/行程內。此平面不註冊供應商帳戶。", + "credentials.flagOff": "功能旗標 CREDENTIAL_RUNTIME_ENABLED 已關閉 — 拒絕變更。", + "credentials.status.posting": "POST {path}", + "credentials.status.ok": "OK {path}", + "credentials.status.failed": "失敗:{error}", + "credentials.tab.overview": "總覽", + "credentials.tab.list": "憑證", + "credentials.tab.providers": "供應商", + "credentials.tab.oauth": "OAuth", + "credentials.tab.pool": "池", + "credentials.tab.health": "健康", + "credentials.tab.quota": "配額", + "credentials.tab.policies": "政策", + "credentials.tab.approvals": "核准", + "credentials.tab.audit": "稽核", + "credentials.card.enabled": "已啟用", + "credentials.card.total": "總計", + "credentials.card.healthy": "健康", + "credentials.card.degraded": "降級", + "credentials.card.quarantined": "隔離", + "credentials.card.revoked": "已撤銷", + "credentials.card.leases": "租約", + "credentials.card.breakers": "斷路器", + "credentials.card.vault": "保險庫", + "credentials.yes": "是", + "credentials.no": "否", + "credentials.available": "可用", + "credentials.unavailable": "不可用", + "credentials.col.id": "ID", + "credentials.col.name": "名稱", + "credentials.col.provider": "供應商", + "credentials.col.status": "狀態", + "credentials.col.health": "健康", + "credentials.col.secret": "密鑰", + "credentials.col.eligible": "可路由", + "credentials.col.actions": "操作", + "credentials.col.score": "分數", + "credentials.col.adapter": "轉接器", + "credentials.col.oauth": "OAuth", + "credentials.col.enabled": "已啟用", + "credentials.col.session": "工作階段", + "credentials.col.redirect": "重新導向", + "credentials.col.budget": "預算", + "credentials.col.priority": "優先順序", + "credentials.col.action": "動作", + "credentials.col.target": "目標", + "credentials.col.when": "時間", + "credentials.col.event": "事件", + "credentials.col.actor": "操作者", + "credentials.col.decision": "決定", + "credentials.validate": "驗證", + "credentials.quarantine": "隔離", + "credentials.revoke": "撤銷", + "credentials.healthRun": "執行健康檢查", + "credentials.approve": "核准", + "credentials.reject": "拒絕", + "credentials.hashes": "可加入書籤的雜湊:{hashes}", + "credentials.loadFailed": "無法載入憑證執行階段資料。", }; diff --git a/gui/src/i18n/zh.ts b/gui/src/i18n/zh.ts index 018f6e251b..f1fef58cf6 100644 --- a/gui/src/i18n/zh.ts +++ b/gui/src/i18n/zh.ts @@ -66,6 +66,9 @@ export const zh: Record = { "nav.models": "模型", "nav.combos": "组合", "nav.subagents": "子代理", + "nav.skills": "技能", + "nav.security": "安全", + "nav.credentials": "凭据", // routing intelligence "routing.title": "路由智能 (beta)", @@ -2861,4 +2864,221 @@ export const zh: Record = { "remote.event.status": "状态", "remote.event.tool": "远程工具", "remote.event.error": "错误", + "skills.title": "技能控制平面", + "skills.subtitle": "通用智能体技能注册表、可视化市场与策略治理部署。", + "skills.refresh": "刷新", + "skills.refreshing": "正在刷新…", + "skills.tab.overview": "概览", + "skills.tab.marketplace": "市场", + "skills.tab.registry": "注册表({count})", + "skills.tab.editor": "技能编辑器", + "skills.tab.matrix": "部署矩阵", + "skills.tab.agents": "智能体", + "skills.tab.nodes": "远程节点", + "skills.tab.drift": "漂移与回滚", + "skills.tab.reviews": "审核队列", + "skills.tab.audit": "审计记录", + "skills.card.registered": "已注册技能", + "skills.card.deployments": "活跃部署", + "skills.card.marketplace": "市场条目", + "skills.card.audit": "审计事件", + "skills.recentActivity": "最近的控制平面活动", + "skills.noEvents": "暂无最近事件。", + "skills.col.timestamp": "时间", + "skills.col.event": "事件", + "skills.col.actor": "操作者", + "skills.col.target": "目标", + "skills.searchPlaceholder": "搜索公开 skills.sh 目录…", + "skills.byAuthor": "v{version} · {author}", + "skills.verified": "已验证", + "skills.importToRegistry": "导入到注册表", + "skills.emptyRegistry": "尚未注册技能。请从市场或本地文件夹导入。", + "skills.col.skill": "技能", + "skills.col.version": "版本", + "skills.col.status": "状态", + "skills.col.risk": "风险", + "skills.col.tags": "标签", + "skills.col.actions": "操作", + "skills.publish": "发布", + "skills.deployCodex": "部署到 Codex", + "skills.editorTitle": "SKILL.md 编辑器", + "skills.editorNamePlaceholder": "技能名称", + "skills.editorVersionPlaceholder": "版本", + "skills.editorDefaultName": "自定义助手", + "skills.editorDefaultMarkdown": "---\nname: custom-assistant\ndisplayName: Custom Assistant\nversion: 1.0.0\ndescription: Custom guidance procedure.\ntags: custom, utility\n---\n\n# Custom Assistant\n\nInstructions for the agent.\n", + "skills.saveDraft": "保存草稿", + "skills.previewTitle": "实时预览", + "skills.matrixTitle": "机群部署矩阵", + "skills.agent.codex": "Codex", + "skills.agent.claudeCode": "Claude Code", + "skills.agent.opencode": "OpenCode", + "skills.agent.universal": "Universal", + "skills.notInstalled": "未安装", + "skills.inSync": "v{version} 已同步", + "skills.blocked": "已阻止", + "skills.agentsTitle": "目标智能体机群", + "skills.col.agent": "智能体", + "skills.col.userRoot": "目标根目录(用户范围)", + "skills.col.projectRoot": "目标根目录(项目范围)", + "skills.col.adapterVersion": "适配器版本", + "skills.agent.openaiCodex": "OpenAI Codex", + "skills.agent.universalAgent": "通用智能体", + "skills.statusReady": "就绪", + "skills.nodesTitle": "远程执行节点(SSH)", + "skills.nodesHelp": "以严格主机密钥固定管理远程 VPS、GPU 工作节点和开发主机。", + "skills.col.node": "节点", + "skills.col.type": "类型", + "skills.col.host": "主机", + "skills.col.environment": "环境", + "skills.localMachine": "本机", + "skills.self": "自身", + "skills.vpsMain": "VPS Main", + "skills.testConnection": "测试连接", + "skills.driftTitle": "配置漂移与回滚", + "skills.driftHelp": "持续监控会将磁盘上的实时文件与不可变注册表内容哈希进行比较。", + "skills.col.deployment": "部署", + "skills.col.targetPath": "目标路径", + "skills.col.driftState": "漂移状态", + "skills.checkHash": "检查哈希", + "skills.reviewsTitle": "策略与审批队列", + "skills.reviewsEmpty": "没有需要操作员决策的待处理升级审核。", + "skills.auditTitle": "审计日志", + "skills.col.eventType": "事件类型", + "skills.col.targetId": "目标 ID", + "skills.system": "system", + "skills.status.importing": "正在导入 {id}…", + "skills.status.imported": "已导入 {id}。", + "skills.status.importFailed": "导入失败:{error}", + "skills.status.publishing": "正在发布 {id}…", + "skills.status.published": "已发布 {id}。", + "skills.status.publishFailed": "发布失败:{error}", + "skills.status.planning": "正在规划将 {id} 部署到 {agent}…", + "skills.status.deployed": "已将 {id} 部署到 {agent}。", + "skills.status.deployFailed": "部署失败:{error}", + "skills.status.saved": "技能草稿已保存到注册表。", + "skills.status.saveFailed": "保存失败:{error}", + "skills.loadFailed": "无法加载技能控制平面数据。", + "security.title": "安全控制平面", + "security.subtitle": "授权优先的侦察与验证。面向目标的工作默认拒绝。R3 已阻止。可通过哈希访问,不是侧栏 NAV 行。", + "security.refresh": "刷新", + "security.refreshing": "正在刷新…", + "security.banner": "仅限已授权目标。未知资产、过期授权和排除主机一律拒绝。不会调用实时第三方工具;侦察使用本地夹具。", + "security.flagOff": "功能开关 PAO_SECURITY_CONTROL_PLANE 已关闭 — 变更会被拒绝。", + "security.status.posting": "POST {path}", + "security.status.ok": "OK {path}", + "security.status.failed": "失败:{error}", + "security.tab.overview": "概览", + "security.tab.authorizations": "授权", + "security.tab.scopes": "范围", + "security.tab.campaigns": "活动", + "security.tab.approvals": "审批", + "security.tab.findings": "发现", + "security.tab.evidence": "证据", + "security.tab.skills": "包", + "security.tab.tools": "工具", + "security.tab.mcp": "MCP", + "security.tab.policies": "策略", + "security.tab.audit": "审计", + "security.card.enabled": "已启用", + "security.card.campaigns": "活动", + "security.card.approvals": "审批", + "security.card.leads": "线索", + "security.card.validated": "已验证", + "security.card.blocked": "已阻止", + "security.card.authExpiring": "授权即将过期", + "security.card.breakers": "断路器", + "security.yes": "是", + "security.no": "否", + "security.col.id": "ID", + "security.col.type": "类型", + "security.col.status": "状态", + "security.col.validUntil": "有效期至", + "security.col.name": "名称", + "security.col.actions": "操作", + "security.col.target": "目标", + "security.col.title": "标题", + "security.col.severity": "严重性", + "security.col.sha256": "SHA-256", + "security.col.preview": "预览", + "security.col.compatibility": "兼容性", + "security.col.kind": "种类", + "security.col.allowlisted": "已加入允许列表", + "security.col.health": "健康", + "security.col.credential": "凭据", + "security.col.ceiling": "上限", + "security.col.blockR3": "阻止 R3", + "security.col.when": "时间", + "security.col.event": "事件", + "security.col.actor": "操作者", + "security.start": "开始", + "security.pause": "暂停", + "security.reconLab": "Recon lab.local", + "security.approveOnce": "批准一次", + "security.deny": "拒绝", + "security.reasonBounded": "仅限一次", + "security.reasonDenied": "已拒绝", + "security.hashes": "可收藏哈希:{hashes}", + "security.loadFailed": "无法加载安全控制平面数据。", + "credentials.title": "提供商访问控制平面", + "credentials.subtitle": "加密凭据、经健康检查的令牌池、OAuth 网关以及策略治理的租约。可通过哈希路由;不是侧边栏 NAV 行。", + "credentials.refresh": "刷新", + "credentials.refreshing": "正在刷新…", + "credentials.banner": "密钥绝不以明文列出。本阶段的健康检查为夹具/进程内。此平面不注册提供商账户。", + "credentials.flagOff": "功能开关 CREDENTIAL_RUNTIME_ENABLED 已关闭 — 拒绝变更。", + "credentials.status.posting": "POST {path}", + "credentials.status.ok": "OK {path}", + "credentials.status.failed": "失败:{error}", + "credentials.tab.overview": "概览", + "credentials.tab.list": "凭据", + "credentials.tab.providers": "提供商", + "credentials.tab.oauth": "OAuth", + "credentials.tab.pool": "池", + "credentials.tab.health": "健康", + "credentials.tab.quota": "配额", + "credentials.tab.policies": "策略", + "credentials.tab.approvals": "审批", + "credentials.tab.audit": "审计", + "credentials.card.enabled": "已启用", + "credentials.card.total": "总计", + "credentials.card.healthy": "健康", + "credentials.card.degraded": "降级", + "credentials.card.quarantined": "隔离", + "credentials.card.revoked": "已吊销", + "credentials.card.leases": "租约", + "credentials.card.breakers": "断路器", + "credentials.card.vault": "保险库", + "credentials.yes": "是", + "credentials.no": "否", + "credentials.available": "可用", + "credentials.unavailable": "不可用", + "credentials.col.id": "ID", + "credentials.col.name": "名称", + "credentials.col.provider": "提供商", + "credentials.col.status": "状态", + "credentials.col.health": "健康", + "credentials.col.secret": "密钥", + "credentials.col.eligible": "可路由", + "credentials.col.actions": "操作", + "credentials.col.score": "分数", + "credentials.col.adapter": "适配器", + "credentials.col.oauth": "OAuth", + "credentials.col.enabled": "已启用", + "credentials.col.session": "会话", + "credentials.col.redirect": "重定向", + "credentials.col.budget": "预算", + "credentials.col.priority": "优先级", + "credentials.col.action": "操作", + "credentials.col.target": "目标", + "credentials.col.when": "时间", + "credentials.col.event": "事件", + "credentials.col.actor": "操作者", + "credentials.col.decision": "决定", + "credentials.validate": "验证", + "credentials.quarantine": "隔离", + "credentials.revoke": "吊销", + "credentials.healthRun": "运行健康检查", + "credentials.approve": "批准", + "credentials.reject": "拒绝", + "credentials.hashes": "可收藏哈希:{hashes}", + "credentials.loadFailed": "无法加载凭据运行时数据。", }; diff --git a/gui/src/pages/Credentials.tsx b/gui/src/pages/Credentials.tsx new file mode 100644 index 0000000000..327d4f927b --- /dev/null +++ b/gui/src/pages/Credentials.tsx @@ -0,0 +1,411 @@ +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import "../styles-credentials-workspace.css"; +import { CREDENTIALS_TAB_HASHES } from "../app-routing"; +import { useDataSurface } from "../data-surface"; +import { readJsonIfOk } from "../fetch-json"; +import { navigateHash, normalizeHashPath } from "../hash-routing"; +import { useT, type TKey } from "../i18n/shared"; + +export interface CredentialsProps { + apiBase: string; +} + +type TabType = + | "overview" + | "list" + | "providers" + | "oauth" + | "pool" + | "health" + | "quota" + | "policies" + | "approvals" + | "audit"; + +const TAB_FROM_HASH: Record = { + credentials: "overview", + "credentials/list": "list", + "credentials/providers": "providers", + "credentials/oauth": "oauth", + "credentials/pool": "pool", + "credentials/health": "health", + "credentials/quota": "quota", + "credentials/policies": "policies", + "credentials/approvals": "approvals", + "credentials/audit": "audit", +}; + +function readTabFromHash(): TabType { + const raw = normalizeHashPath(typeof window !== "undefined" ? window.location.hash : "credentials"); + return TAB_FROM_HASH[raw] ?? "overview"; +} + +function hashForTab(tab: TabType): string { + return tab === "overview" ? "credentials" : `credentials/${tab}`; +} + +interface Overview { + enabled: boolean; + total: number; + healthy: number; + degraded: number; + quarantined: number; + revoked: number; + active_leases: number; + circuit_open: number; + vault_available: boolean; +} + +type Row = Record; + +interface CredentialsWorkspace { + overview: Overview | null; + credentials: Row[]; + providers: Row[]; + oauth: Row[]; + pool: Row[]; + health: Row[]; + quota: Row[]; + policies: Row[]; + approvals: Row[]; + audit: Row[]; +} + +const EMPTY_WORKSPACE: CredentialsWorkspace = { + overview: null, + credentials: [], + providers: [], + oauth: [], + pool: [], + health: [], + quota: [], + policies: [], + approvals: [], + audit: [], +}; + +const TABS: Array<{ id: TabType; labelKey: TKey }> = [ + { id: "overview", labelKey: "credentials.tab.overview" }, + { id: "list", labelKey: "credentials.tab.list" }, + { id: "providers", labelKey: "credentials.tab.providers" }, + { id: "oauth", labelKey: "credentials.tab.oauth" }, + { id: "pool", labelKey: "credentials.tab.pool" }, + { id: "health", labelKey: "credentials.tab.health" }, + { id: "quota", labelKey: "credentials.tab.quota" }, + { id: "policies", labelKey: "credentials.tab.policies" }, + { id: "approvals", labelKey: "credentials.tab.approvals" }, + { id: "audit", labelKey: "credentials.tab.audit" }, +]; + +async function readData(res: Response, fallback: T): Promise { + const payload = await readJsonIfOk<{ data?: T }>(res); + return payload?.data ?? fallback; +} + +function cell(value: unknown): string { + if (value == null) return ""; + return String(value); +} + +export function Credentials({ apiBase }: CredentialsProps): React.JSX.Element { + const t = useT(); + const [tab, setTab] = useState(readTabFromHash); + const [statusMessage, setStatusMessage] = useState(null); + + const selectTab = (next: TabType) => { + setTab(next); + navigateHash(hashForTab(next)); + }; + + useEffect(() => { + const onHash = () => setTab(readTabFromHash()); + window.addEventListener("hashchange", onHash); + return () => window.removeEventListener("hashchange", onHash); + }, []); + + const loadWorkspace = useCallback(async (signal: AbortSignal): Promise => { + const [ + overviewRes, + credentialsRes, + providersRes, + oauthRes, + poolRes, + healthRes, + quotaRes, + policiesRes, + approvalsRes, + auditRes, + ] = await Promise.all([ + fetch(`${apiBase}/api/credentials/overview`, { signal }), + fetch(`${apiBase}/api/credentials`, { signal }), + fetch(`${apiBase}/api/credentials/providers`, { signal }), + fetch(`${apiBase}/api/credentials/oauth/sessions`, { signal }), + fetch(`${apiBase}/api/credentials/pool`, { signal }), + fetch(`${apiBase}/api/credentials/health`, { signal }), + fetch(`${apiBase}/api/credentials/quota`, { signal }), + fetch(`${apiBase}/api/credentials/policies`, { signal }), + fetch(`${apiBase}/api/credentials/approvals`, { signal }), + fetch(`${apiBase}/api/credentials/audit`, { signal }), + ]); + return { + overview: await readData(overviewRes, null), + credentials: await readData(credentialsRes, []), + providers: await readData(providersRes, []), + oauth: await readData(oauthRes, []), + pool: await readData(poolRes, []), + health: await readData(healthRes, []), + quota: await readData(quotaRes, []), + policies: await readData(policiesRes, []), + approvals: await readData(approvalsRes, []), + audit: await readData(auditRes, []), + }; + }, [apiBase]); + + const resource = useDataSurface( + `credentials-workspace:${apiBase}`, + [apiBase], + loadWorkspace, + { isEmpty: () => false }, + ); + const workspace = resource.state.data ?? EMPTY_WORKSPACE; + const { + overview, + credentials, + providers, + oauth, + pool, + health, + quota, + policies, + approvals, + audit, + } = workspace; + const loading = resource.state.refreshing || resource.state.showSkeleton; + const hashList = useMemo(() => ["credentials", ...CREDENTIALS_TAB_HASHES].join(", "), []); + + const post = async (path: string, body?: Record) => { + setStatusMessage(t("credentials.status.posting", { path })); + const res = await fetch(`${apiBase}${path}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body ?? { actor: "gui-operator" }), + }); + const data = await res.json().catch(() => ({})) as { error?: unknown }; + const error = typeof data.error === "string" ? data.error : String(res.status); + setStatusMessage(res.ok ? t("credentials.status.ok", { path }) : t("credentials.status.failed", { error })); + resource.refresh(); + }; + + return ( +
+
+
+

{t("credentials.title")}

+

{t("credentials.subtitle")}

+
+ +
+ +
+ {t("credentials.banner")} + {overview && !overview.enabled ? ` ${t("credentials.flagOff")}` : ""} +
+ + {resource.state.showError &&
{t("credentials.loadFailed")}
} + {statusMessage &&
{statusMessage}
} + +
+ {TABS.map(entry => ( + + ))} +
+ + {tab === "overview" && overview && ( + <> +
+
{t("credentials.card.enabled")}{overview.enabled ? t("credentials.yes") : t("credentials.no")}
+
{t("credentials.card.total")}{overview.total}
+
{t("credentials.card.healthy")}{overview.healthy}
+
{t("credentials.card.degraded")}{overview.degraded}
+
{t("credentials.card.quarantined")}{overview.quarantined}
+
{t("credentials.card.revoked")}{overview.revoked}
+
{t("credentials.card.leases")}{overview.active_leases}
+
{t("credentials.card.breakers")}{overview.circuit_open}
+
{t("credentials.card.vault")}{overview.vault_available ? t("credentials.available") : t("credentials.unavailable")}
+
+
+ +
+ + )} + + {tab === "list" && ( + + + + {credentials.map(row => ( + + + + + + + + + + + ))} + +
{t("credentials.col.id")}{t("credentials.col.name")}{t("credentials.col.provider")}{t("credentials.col.status")}{t("credentials.col.health")}{t("credentials.col.secret")}{t("credentials.col.eligible")}{t("credentials.col.actions")}
{cell(row.id)}{cell(row.name)}{cell(row.provider_slug)}{cell(row.status)}{cell(row.health_status)}{cell(row.secret)}{row.routing_eligible === true ? t("credentials.yes") : t("credentials.no")} + + + +
+ )} + + {tab === "providers" && ( + + + + {providers.map(row => ( + + + + + + + + ))} + +
{t("credentials.col.id")}{t("credentials.col.name")}{t("credentials.col.adapter")}{t("credentials.col.oauth")}{t("credentials.col.enabled")}
{cell(row.slug)}{cell(row.name)}{cell(row.adapter_type)}{row.oauth_supported === true ? t("credentials.yes") : t("credentials.no")}{row.enabled === true ? t("credentials.yes") : t("credentials.no")}
+ )} + + {tab === "oauth" && ( + + + + {oauth.map(row => ( + + + + + + ))} + +
{t("credentials.col.session")}{t("credentials.col.status")}{t("credentials.col.redirect")}
{cell(row.id)}{cell(row.status)}{cell(row.redirect_uri)}
+ )} + + {tab === "pool" && ( + + + + {pool.map(row => ( + + + + + + + + ))} + +
{t("credentials.col.id")}{t("credentials.col.provider")}{t("credentials.col.score")}{t("credentials.col.health")}{t("credentials.col.status")}
{cell(row.credential_id)}{cell(row.provider)}{cell(row.routing_score)}{cell(row.health_status)}{cell(row.status)}
+ )} + + {tab === "health" && ( + + + + {health.map(row => ( + + + + + + + + ))} + +
{t("credentials.col.id")}{t("credentials.col.health")}{t("credentials.col.score")}{t("credentials.col.status")}{t("credentials.col.eligible")}
{cell(row.id)}{cell(row.health_status)}{cell(row.health_score)}{cell(row.status)}{row.routing_eligible === true ? t("credentials.yes") : t("credentials.no")}
+ )} + + {tab === "quota" && ( + + + + {quota.map(row => ( + + + + + + ))} + +
{t("credentials.col.id")}{t("credentials.col.budget")}{t("credentials.col.health")}
{cell(row.id)}{`${cell(row.remaining_budget)} / ${cell(row.budget_limit)}`}{cell(row.health_status)}
+ )} + + {tab === "policies" && ( + + + + {policies.map(row => ( + + + + + + ))} + +
{t("credentials.col.id")}{t("credentials.col.name")}{t("credentials.col.priority")}
{cell(row.id)}{cell(row.name)}{cell(row.priority)}
+ )} + + {tab === "approvals" && ( + + + + {approvals.map(row => ( + + + + + + + + ))} + +
{t("credentials.col.id")}{t("credentials.col.action")}{t("credentials.col.target")}{t("credentials.col.status")}{t("credentials.col.actions")}
{cell(row.id)}{cell(row.action)}{cell(row.target_id)}{cell(row.status)} + + +
+ )} + + {tab === "audit" && ( + + + + {audit.map(row => ( + + + + + + + ))} + +
{t("credentials.col.when")}{t("credentials.col.event")}{t("credentials.col.actor")}{t("credentials.col.decision")}
{cell(row.created_at)}{cell(row.action)}{cell(row.actor_id)}{cell(row.decision)}
+ )} + +

{t("credentials.hashes", { hashes: hashList })}

+
+ ); +} + diff --git a/gui/src/pages/Security.tsx b/gui/src/pages/Security.tsx new file mode 100644 index 0000000000..26b0d308be --- /dev/null +++ b/gui/src/pages/Security.tsx @@ -0,0 +1,446 @@ +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import "../styles-security-workspace.css"; +import { SECURITY_TAB_HASHES } from "../app-routing"; +import { useDataSurface } from "../data-surface"; +import { readJsonIfOk } from "../fetch-json"; +import { navigateHash, normalizeHashPath } from "../hash-routing"; +import { useT, type TKey } from "../i18n/shared"; + +export interface SecurityProps { + apiBase: string; +} + +type TabType = + | "overview" + | "authorizations" + | "scopes" + | "campaigns" + | "approvals" + | "findings" + | "evidence" + | "skills" + | "tools" + | "mcp" + | "policies" + | "audit"; + +const TAB_FROM_HASH: Record = { + security: "overview", + "security/authorizations": "authorizations", + "security/scopes": "scopes", + "security/campaigns": "campaigns", + "security/approvals": "approvals", + "security/findings": "findings", + "security/evidence": "evidence", + "security/skills": "skills", + "security/tools": "tools", + "security/mcp": "mcp", + "security/policies": "policies", + "security/audit": "audit", +}; + +function readTabFromHash(): TabType { + const raw = normalizeHashPath(typeof window !== "undefined" ? window.location.hash : "security"); + return TAB_FROM_HASH[raw] ?? "overview"; +} + +function hashForTab(tab: TabType): string { + return tab === "overview" ? "security" : `security/${tab}`; +} + +interface Overview { + enabled: boolean; + campaigns_active: number; + approvals_pending: number; + leads_open: number; + findings_validated: number; + blocked_actions: number; + authorizations_expiring: number; + circuit_breakers_open: number; +} + +type Row = Record; + +interface SecurityWorkspace { + overview: Overview | null; + authorizations: Row[]; + scopes: Row[]; + campaigns: Row[]; + approvals: Row[]; + findings: Row[]; + evidence: Row[]; + packages: Row[]; + tools: Row[]; + mcp: Row[]; + policies: Row[]; + audit: Row[]; +} + +const EMPTY_WORKSPACE: SecurityWorkspace = { + overview: null, + authorizations: [], + scopes: [], + campaigns: [], + approvals: [], + findings: [], + evidence: [], + packages: [], + tools: [], + mcp: [], + policies: [], + audit: [], +}; + +const TABS: Array<{ id: TabType; labelKey: TKey }> = [ + { id: "overview", labelKey: "security.tab.overview" }, + { id: "authorizations", labelKey: "security.tab.authorizations" }, + { id: "scopes", labelKey: "security.tab.scopes" }, + { id: "campaigns", labelKey: "security.tab.campaigns" }, + { id: "approvals", labelKey: "security.tab.approvals" }, + { id: "findings", labelKey: "security.tab.findings" }, + { id: "evidence", labelKey: "security.tab.evidence" }, + { id: "skills", labelKey: "security.tab.skills" }, + { id: "tools", labelKey: "security.tab.tools" }, + { id: "mcp", labelKey: "security.tab.mcp" }, + { id: "policies", labelKey: "security.tab.policies" }, + { id: "audit", labelKey: "security.tab.audit" }, +]; + +async function readData(res: Response, fallback: T): Promise { + const payload = await readJsonIfOk<{ data?: T }>(res); + return payload?.data ?? fallback; +} + +export function Security({ apiBase }: SecurityProps): React.JSX.Element { + const t = useT(); + const [tab, setTab] = useState(readTabFromHash); + const [statusMessage, setStatusMessage] = useState(null); + + const selectTab = (next: TabType) => { + setTab(next); + navigateHash(hashForTab(next)); + }; + + useEffect(() => { + const onHash = () => setTab(readTabFromHash()); + window.addEventListener("hashchange", onHash); + return () => window.removeEventListener("hashchange", onHash); + }, []); + + const loadWorkspace = useCallback(async (signal: AbortSignal): Promise => { + const [ + overviewRes, + authorizationsRes, + scopesRes, + campaignsRes, + approvalsRes, + findingsRes, + packagesRes, + toolsRes, + mcpRes, + policiesRes, + auditRes, + ] = await Promise.all([ + fetch(`${apiBase}/api/security/overview`, { signal }), + fetch(`${apiBase}/api/security/authorizations`, { signal }), + fetch(`${apiBase}/api/security/scopes`, { signal }), + fetch(`${apiBase}/api/security/campaigns`, { signal }), + fetch(`${apiBase}/api/security/approvals`, { signal }), + fetch(`${apiBase}/api/security/findings`, { signal }), + fetch(`${apiBase}/api/security/skills`, { signal }), + fetch(`${apiBase}/api/security/tools`, { signal }), + fetch(`${apiBase}/api/security/mcp`, { signal }), + fetch(`${apiBase}/api/security/policies`, { signal }), + fetch(`${apiBase}/api/security/audit`, { signal }), + ]); + const campaigns = await readData(campaignsRes, []); + const campaignId = typeof campaigns[0]?.id === "string" ? campaigns[0].id : ""; + const evidence = campaignId + ? await readData( + await fetch(`${apiBase}/api/security/campaigns/${encodeURIComponent(campaignId)}/evidence`, { signal }), + [], + ) + : []; + return { + overview: await readData(overviewRes, null), + authorizations: await readData(authorizationsRes, []), + scopes: await readData(scopesRes, []), + campaigns, + approvals: await readData(approvalsRes, []), + findings: await readData(findingsRes, []), + evidence, + packages: await readData(packagesRes, []), + tools: await readData(toolsRes, []), + mcp: await readData(mcpRes, []), + policies: await readData(policiesRes, []), + audit: await readData(auditRes, []), + }; + }, [apiBase]); + + const resource = useDataSurface( + `security-workspace:${apiBase}`, + [apiBase], + loadWorkspace, + { isEmpty: () => false }, + ); + const workspace = resource.state.data ?? EMPTY_WORKSPACE; + const { + overview, + authorizations, + scopes, + campaigns, + approvals, + findings, + evidence, + packages, + tools, + mcp, + policies, + audit, + } = workspace; + const loading = resource.state.refreshing || resource.state.showSkeleton; + const hashList = useMemo(() => ["security", ...SECURITY_TAB_HASHES].join(", "), []); + + const post = async (path: string, body?: Record) => { + setStatusMessage(t("security.status.posting", { path })); + const res = await fetch(`${apiBase}${path}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body ?? { actor: "gui-reviewer" }), + }); + const data = await res.json().catch(() => ({})) as { error?: unknown }; + const error = typeof data.error === "string" ? data.error : String(res.status); + setStatusMessage(res.ok ? t("security.status.ok", { path }) : t("security.status.failed", { error })); + resource.refresh(); + }; + + return ( +
+
+
+

{t("security.title")}

+

{t("security.subtitle")}

+
+ +
+ +
+ {t("security.banner")} + {overview && !overview.enabled ? ` ${t("security.flagOff")}` : ""} +
+ + {statusMessage &&
{statusMessage}
} + +
+ {TABS.map(entry => ( + + ))} +
+ + {tab === "overview" && overview && ( +
+
{t("security.card.enabled")}{overview.enabled ? t("security.yes") : t("security.no")}
+
{t("security.card.campaigns")}{overview.campaigns_active}
+
{t("security.card.approvals")}{overview.approvals_pending}
+
{t("security.card.leads")}{overview.leads_open}
+
{t("security.card.validated")}{overview.findings_validated}
+
{t("security.card.blocked")}{overview.blocked_actions}
+
{t("security.card.authExpiring")}{overview.authorizations_expiring}
+
{t("security.card.breakers")}{overview.circuit_breakers_open}
+
+ )} + + {tab === "authorizations" && ( + + + + {authorizations.map(row => ( + + + + + + + ))} + +
{t("security.col.id")}{t("security.col.type")}{t("security.col.status")}{t("security.col.validUntil")}
{String(row.id)}{String(row.type)}{String(row.status)}{String(row.valid_until)}
+ )} + + {tab === "scopes" && ( + + + + {scopes.map(row => ( + + + + + + ))} + +
{t("security.col.id")}{t("security.col.name")}{t("security.col.status")}
{String(row.id)}{String(row.name)}{String(row.status)}
+ )} + + {tab === "campaigns" && ( + + + + {campaigns.map(row => ( + + + + + + + ))} + +
{t("security.col.id")}{t("security.col.name")}{t("security.col.status")}{t("security.col.actions")}
{String(row.id)}{String(row.name)}{String(row.status)} + + + +
+ )} + + {tab === "approvals" && ( + + + + {approvals.map(row => ( + + + + + + + ))} + +
{t("security.col.id")}{t("security.col.target")}{t("security.col.status")}{t("security.col.actions")}
{String(row.id)}{String(row.target)}{String(row.status)} + + +
+ )} + + {tab === "findings" && ( + + + + {findings.map(row => ( + + + + + + + ))} + +
{t("security.col.id")}{t("security.col.title")}{t("security.col.status")}{t("security.col.severity")}
{String(row.id)}{String(row.title)}{String(row.status)}{String(row.severity)}
+ )} + + {tab === "evidence" && ( + + + + {evidence.map(row => ( + + + + + + + ))} + +
{t("security.col.id")}{t("security.col.type")}{t("security.col.sha256")}{t("security.col.preview")}
{String(row.id)}{String(row.type)}{String(row.sha256).slice(0, 12)}…{String(row.redacted_preview ?? "")}
+ )} + + {tab === "skills" && ( + + + + {packages.map(row => ( + + + + + + ))} + +
{t("security.col.id")}{t("security.col.name")}{t("security.col.compatibility")}
{String(row.id)}{String(row.name)}{String(row.compatibility)}
+ )} + + {tab === "tools" && ( + + + + {tools.map(row => ( + + + + + + + ))} + +
{t("security.col.id")}{t("security.col.name")}{t("security.col.kind")}{t("security.col.allowlisted")}
{String(row.id)}{String(row.name)}{String(row.kind)}{String(row.allowlisted)}
+ )} + + {tab === "mcp" && ( + + + + {mcp.map(row => ( + + + + + + + ))} + +
{t("security.col.id")}{t("security.col.name")}{t("security.col.health")}{t("security.col.credential")}
{String(row.id)}{String(row.name)}{String(row.health_state)}{String(row.credential_ref ?? "")}
+ )} + + {tab === "policies" && ( + + + + {policies.map(row => ( + + + + + + + ))} + +
{t("security.col.id")}{t("security.col.name")}{t("security.col.ceiling")}{t("security.col.blockR3")}
{String(row.id)}{String(row.name)}{String(row.default_risk_ceiling)}{String(row.block_r3)}
+ )} + + {tab === "audit" && ( + + + + {audit.map(row => ( + + + + + + + ))} + +
{t("security.col.when")}{t("security.col.event")}{t("security.col.actor")}{t("security.col.target")}
{String(row.created_at)}{String(row.event_type)}{String(row.actor_id)}{String(row.target ?? "")}
+ )} + +

{t("security.hashes", { hashes: hashList })}

+
+ ); +} diff --git a/gui/src/pages/Skills.tsx b/gui/src/pages/Skills.tsx new file mode 100644 index 0000000000..391728333d --- /dev/null +++ b/gui/src/pages/Skills.tsx @@ -0,0 +1,691 @@ +import React, { useCallback, useEffect, useState } from "react"; +import { useDataSurface } from "../data-surface"; +import { readJsonIfOk } from "../fetch-json"; +import { navigateHash, normalizeHashPath } from "../hash-routing"; +import { useT, type TKey } from "../i18n/shared"; +import "../styles-skills-workspace.css"; + +export interface SkillsProps { + apiBase: string; +} + +type TabType = + | "overview" + | "marketplace" + | "registry" + | "editor" + | "matrix" + | "agents" + | "nodes" + | "drift" + | "reviews" + | "audit"; + +interface SkillItem { + id: string; + namespace: string; + slug: string; + display_name: string; + description: string; + status: string; + current_version: string; + trust_level: string; + risk_level?: string; + tags: string[]; +} + +interface MarketplaceItem { + id: string; + name: string; + description: string; + version: string; + author: string; + license: string; + tags: string[]; + sourceUrl: string; + verified: boolean; +} + +interface DeploymentItem { + id: string; + skill_version_id: string; + node_id: string; + agent_id: string; + scope: string; + target_path: string; + status: string; + desired_sha256: string; + actual_sha256?: string; +} + +interface AuditEventItem { + id: string; + event_type: string; + actor_type: string; + actor_id?: string; + skill_id?: string; + created_at: string; + metadata?: Record; +} + +const TAB_FROM_HASH: Record = { + skills: "overview", + "skills/marketplace": "marketplace", + "skills/registry": "registry", + "skills/editor": "editor", + "skills/matrix": "matrix", + "skills/agents": "agents", + "skills/nodes": "nodes", + "skills/drift": "drift", + "skills/reviews": "reviews", + "skills/audit": "audit", +}; + +function readTabFromHash(): TabType { + const raw = normalizeHashPath(typeof window !== "undefined" ? window.location.hash : "skills"); + return TAB_FROM_HASH[raw] ?? "overview"; +} + +function hashForTab(tab: TabType): string { + return tab === "overview" ? "skills" : `skills/${tab}`; +} + +interface SkillsWorkspace { + skills: SkillItem[]; + marketplaceItems: MarketplaceItem[]; + deployments: DeploymentItem[]; + auditEvents: AuditEventItem[]; +} + +const EMPTY_WORKSPACE: SkillsWorkspace = { + skills: [], + marketplaceItems: [], + deployments: [], + auditEvents: [], +}; + +const TABS: Array<{ id: TabType; labelKey: TKey; count?: boolean }> = [ + { id: "overview", labelKey: "skills.tab.overview" }, + { id: "marketplace", labelKey: "skills.tab.marketplace" }, + { id: "registry", labelKey: "skills.tab.registry", count: true }, + { id: "editor", labelKey: "skills.tab.editor" }, + { id: "matrix", labelKey: "skills.tab.matrix" }, + { id: "agents", labelKey: "skills.tab.agents" }, + { id: "nodes", labelKey: "skills.tab.nodes" }, + { id: "drift", labelKey: "skills.tab.drift" }, + { id: "reviews", labelKey: "skills.tab.reviews" }, + { id: "audit", labelKey: "skills.tab.audit" }, +]; + +async function readData(res: Response, fallback: T): Promise { + const payload = await readJsonIfOk<{ data?: T }>(res); + return payload?.data ?? fallback; +} + +export function Skills({ apiBase }: SkillsProps): React.JSX.Element { + const t = useT(); + const [tab, setTab] = useState(readTabFromHash); + const [searchQuery, setSearchQuery] = useState(""); + const [statusMessage, setStatusMessage] = useState(null); + const [editorName, setEditorName] = useState(() => t("skills.editorDefaultName")); + const [editorVersion, setEditorVersion] = useState("1.0.0"); + const [editorMarkdown, setEditorMarkdown] = useState(() => t("skills.editorDefaultMarkdown")); + + const selectTab = (next: TabType) => { + setTab(next); + navigateHash(hashForTab(next)); + }; + + useEffect(() => { + const onHash = () => setTab(readTabFromHash()); + window.addEventListener("hashchange", onHash); + return () => window.removeEventListener("hashchange", onHash); + }, []); + + const loadWorkspace = useCallback(async (signal: AbortSignal): Promise => { + const [skillsRes, marketplaceRes, deploymentsRes, auditRes] = await Promise.all([ + fetch(`${apiBase}/api/skills`, { signal }), + fetch(`${apiBase}/api/skill-marketplace/search?q=${encodeURIComponent(searchQuery)}`, { signal }), + fetch(`${apiBase}/api/skill-deployments`, { signal }), + fetch(`${apiBase}/api/skill-audit`, { signal }), + ]); + return { + skills: await readData(skillsRes, []), + marketplaceItems: await readData(marketplaceRes, []), + deployments: await readData(deploymentsRes, []), + auditEvents: await readData(auditRes, []), + }; + }, [apiBase, searchQuery]); + + const resource = useDataSurface( + `skills-workspace:${apiBase}:${searchQuery}`, + [apiBase, searchQuery], + loadWorkspace, + { isEmpty: () => false }, + ); + const workspace = resource.state.data ?? EMPTY_WORKSPACE; + const { skills, marketplaceItems, deployments, auditEvents } = workspace; + const loading = resource.state.refreshing || resource.state.showSkeleton; + + const handleImportMarketplace = async (refId: string) => { + try { + setStatusMessage(t("skills.status.importing", { id: refId })); + await fetch(`${apiBase}/api/skill-imports`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ type: "marketplace", ref: refId }), + }); + setStatusMessage(t("skills.status.imported", { id: refId })); + resource.refresh(); + } catch (e) { + setStatusMessage(t("skills.status.importFailed", { error: e instanceof Error ? e.message : String(e) })); + } + }; + + const handlePublish = async (skillId: string) => { + try { + setStatusMessage(t("skills.status.publishing", { id: skillId })); + await fetch(`${apiBase}/api/skills/${encodeURIComponent(skillId)}/publish`, { method: "POST" }); + setStatusMessage(t("skills.status.published", { id: skillId })); + resource.refresh(); + } catch (e) { + setStatusMessage(t("skills.status.publishFailed", { error: e instanceof Error ? e.message : String(e) })); + } + }; + + const handleDeployQuick = async (skillId: string, version: string, agentType: string) => { + try { + setStatusMessage(t("skills.status.planning", { id: skillId, agent: agentType })); + const planRes = await fetch(`${apiBase}/api/skill-deployments/plan`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ skillVersionId: `${skillId}@${version}`, agentType, scope: "user", nodeId: "local" }), + }).then(r => r.json()) as { plan?: { planId?: string } }; + + if (planRes?.plan?.planId) { + await fetch(`${apiBase}/api/skill-deployments`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ planId: planRes.plan.planId }), + }); + setStatusMessage(t("skills.status.deployed", { id: skillId, agent: agentType })); + resource.refresh(); + } + } catch (e) { + setStatusMessage(t("skills.status.deployFailed", { error: e instanceof Error ? e.message : String(e) })); + } + }; + + const saveDraft = async () => { + try { + await fetch(`${apiBase}/api/skills`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name: editorName, markdown: editorMarkdown }), + }); + setStatusMessage(t("skills.status.saved")); + resource.refresh(); + } catch (e) { + setStatusMessage(t("skills.status.saveFailed", { error: String(e) })); + } + }; + + const agentStatus = (skill: SkillItem, agent: string) => { + const dep = deployments.find(d => d.agent_id === agent && d.skill_version_id.startsWith(skill.id)); + if (!dep) return { label: t("skills.notInstalled"), cls: "empty" }; + if (dep.status === "DEPLOYED") return { label: t("skills.inSync", { version: skill.current_version }), cls: "in-sync" }; + if (dep.status === "FAILED") return { label: t("skills.blocked"), cls: "blocked" }; + return { label: dep.status, cls: "drift" }; + }; + + return ( +
+
+
+

{t("skills.title")}

+

{t("skills.subtitle")}

+
+
+ +
+
+ + {statusMessage && ( +
+ {statusMessage} +
+ )} + +
+ {TABS.map(entry => ( + + ))} +
+ + {tab === "overview" && ( +
+
+
+ {t("skills.card.registered")} + {skills.length} +
+
+ {t("skills.card.deployments")} + {deployments.filter(d => d.status === "DEPLOYED").length} +
+
+ {t("skills.card.marketplace")} + {marketplaceItems.length} +
+
+ {t("skills.card.audit")} + {auditEvents.length} +
+
+ +
+

{t("skills.recentActivity")}

+ {auditEvents.length === 0 ? ( +

{t("skills.noEvents")}

+ ) : ( + + + + + + + + + + + {auditEvents.slice(0, 8).map(ev => ( + + + + + + + ))} + +
{t("skills.col.timestamp")}{t("skills.col.event")}{t("skills.col.actor")}{t("skills.col.target")}
{new Date(ev.created_at).toLocaleTimeString()}{ev.event_type}{ev.actor_type}{ev.skill_id ?? t("skills.system")}
+ )} +
+
+ )} + + {/* TAB 2: MARKETPLACE */} + {tab === "marketplace" && ( +
+
+ setSearchQuery(e.target.value)} + style={{ + flexGrow: 1, + background: "#0f1115", + border: "1px solid var(--border, #262933)", + color: "#fff", + padding: "10px 14px", + borderRadius: 6, + fontSize: 14, + }} + /> +
+ +
+ {marketplaceItems.map(item => ( +
+
+
+

{item.name}

+ {t("skills.byAuthor", { version: item.version, author: item.author })} +
+ {item.verified && ( + + {t("skills.verified")} + + )} +
+

{item.description}

+
+ {item.tags.map(tag => ( + {tag} + ))} +
+
+ +
+
+ ))} +
+
+ )} + + {/* TAB 3: REGISTRY */} + {tab === "registry" && ( +
+ {skills.length === 0 ? ( +
+

{t("skills.emptyRegistry")}

+
+ ) : ( +
+ + + + + + + + + + + + + {skills.map(s => ( + + + + + + + + + ))} + +
{t("skills.col.skill")}{t("skills.col.version")}{t("skills.col.status")}{t("skills.col.risk")}{t("skills.col.tags")}{t("skills.col.actions")}
+ {s.display_name}
+ {s.id} +
v{s.current_version} + {s.status} + + {s.risk_level ?? "low"} + + {s.tags.slice(0, 3).map(tag => ( + {tag} + ))} + +
+ {s.status !== "PUBLISHED" && ( + + )} + +
+
+
+ )} +
+ )} + + {/* TAB 4: SKILL EDITOR */} + {tab === "editor" && ( +
+
+

{t("skills.editorTitle")}

+
+ setEditorName(e.target.value)} + style={{ flex: 1, background: "#0f1115", border: "1px solid var(--border, #262933)", color: "#fff", padding: "6px 10px", borderRadius: 4, fontSize: 13 }} + /> + setEditorVersion(e.target.value)} + style={{ width: 80, background: "#0f1115", border: "1px solid var(--border, #262933)", color: "#fff", padding: "6px 10px", borderRadius: 4, fontSize: 13 }} + /> +
+