Files
crank/apps/ui/html/secrets.html
T
bsodfather 46892ee61c
CI / Rust Checks (push) Successful in 5m14s
CI / UI Checks (push) Successful in 4s
CI / Deployment Manifests (push) Successful in 2s
CI / Frontend E2E (push) Successful in 2m58s
CI / Deploy (push) Successful in 1m44s
Усилить безопасность веб-интерфейса
2026-07-11 17:12:50 +03:00

205 lines
11 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<base href="../">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Crank — Secrets</title>
<link rel="stylesheet" href="css/fonts.css">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/layout.css">
<link rel="stylesheet" href="css/pages.css">
<style>
.secret-value-grid {
display: grid;
gap: 14px;
}
.secret-inline-note {
font-size: 12px;
color: var(--text-muted);
line-height: 1.55;
}
#secret-json-value {
min-height: 132px;
max-height: 220px;
resize: vertical;
}
.secrets-card-list { display: none; }
@media (max-width: 720px) {
#secrets-table-wrap { display: none; }
.secrets-card-list { display: grid; }
}
</style>
<script src="%CRANK_BUNDLE_PROTECTED_CORE%"></script>
</head>
<body>
<nav class="navbar">
<a href="/" class="nav-logo">
<div class="nav-logo-mark"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28" fill="white"><path fill-rule="evenodd" d="M 10.000,1.800 L 12.287,3.962 L 15.408,3.557 L 15.987,6.650 L 18.750,8.157 L 17.400,11.000 L 18.750,13.843 L 15.987,15.350 L 15.408,18.443 L 12.287,18.038 L 10.000,20.200 L 7.713,18.038 L 4.592,18.443 L 4.013,15.350 L 1.250,13.843 L 2.600,11.000 L 1.250,8.157 L 4.013,6.650 L 4.592,3.557 L 7.713,3.962 Z M 12.800,11.000 A 2.8 2.8 0 1 0 7.200,11.000 A 2.8 2.8 0 1 0 12.800,11.000 Z"/><path fill-rule="evenodd" d="M 11.791,15.604 Q 16.083,17.179 21.375,21.154 A 2.00 2.00 0 1 1 23.625,17.846 Q 19.782,15.610 14.940,10.973 Z M 24.300,19.500 A 1.8 1.8 0 1 0 20.700,19.500 A 1.8 1.8 0 1 0 24.300,19.500 Z"/><path d="M 10.0,9.7 L 11.3,11.0 L 10.0,12.3 L 8.7,11.0 Z" fill="rgba(0,0,0,0.3)"/></svg></div>
<span class="nav-logo-text">Crank</span>
</a>
<!-- Single workspace indicator -->
<div class="ws-switcher" id="ws-switcher">
<div class="ws-switcher-trigger">
<div class="ws-dot" id="ws-dot">A</div>
<span class="ws-current-name" id="ws-current-name" data-testid="shell-workspace-name">workspace</span>
</div>
</div>
<div class="nav-links">
<a href="/" class="nav-link" data-i18n="nav.operations">Operations</a>
<a href="/agents" class="nav-link" data-i18n="nav.agents">Agents</a>
<a href="/api-keys" class="nav-link" data-i18n="nav.apikeys">API Keys</a>
<a href="/secrets" class="nav-link active" data-i18n="nav.secrets">Secrets</a>
<a href="/logs" class="nav-link" data-i18n="nav.logs">Logs</a>
<a href="/usage" class="nav-link" data-i18n="nav.usage">Usage</a>
</div>
<div class="nav-right">
<button class="nav-hamburger" data-i18n-title="nav.menu" data-i18n-aria-label="nav.menu" aria-label="Menu"><span></span><span></span><span></span></button>
<div class="nav-divider"></div>
<div class="user-menu">
<div class="nav-avatar" data-testid="shell-avatar" data-i18n-title="nav.account" title="Account">AT</div>
<div class="user-dropdown">
<div class="user-dropdown-header"><div class="user-dropdown-name" data-testid="shell-user-name">Crank</div><div class="user-dropdown-role" data-testid="shell-user-role"></div></div>
<button class="user-dropdown-item" data-action="settings-link"><svg width="13" height="13"><use href="icons/general/settings.svg#icon"/></svg><span data-i18n="nav.settings">Settings</span></button>
<div class="dropdown-divider"></div>
<button class="user-dropdown-item danger" data-action="logout"><svg width="13" height="13"><use href="icons/general/logout.svg#icon"/></svg><span data-i18n="nav.logout">Log out</span></button>
</div>
</div>
</div>
</nav>
<div class="mobile-nav" hidden>
<a href="/" class="mobile-nav-link" data-i18n="nav.operations">Operations</a>
<a href="/agents" class="mobile-nav-link" data-i18n="nav.agents">Agents</a>
<a href="/api-keys" class="mobile-nav-link" data-i18n="nav.apikeys">API Keys</a>
<a href="/secrets" class="mobile-nav-link active" data-i18n="nav.secrets">Secrets</a>
<a href="/logs" class="mobile-nav-link" data-i18n="nav.logs">Logs</a>
<a href="/usage" class="mobile-nav-link" data-i18n="nav.usage">Usage</a>
</div>
<div class="page">
<div class="page-header">
<div class="page-header-text">
<h1 class="page-title" data-i18n="secrets.title">Secrets</h1>
<p class="page-subtitle" data-i18n="secrets.subtitle">Credentials for authorization on target API hosts.</p>
</div>
<div class="page-header-actions">
<button class="btn-primary" id="btn-create-secret" data-testid="secret-create-button" type="button">
<svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor"><path d="M7.75 2a.75.75 0 01.75.75V7h4.25a.75.75 0 010 1.5H8.5v4.25a.75.75 0 01-1.5 0V8.5H2.75a.75.75 0 010-1.5H7V2.75A.75.75 0 017.75 2z"/></svg>
<span data-i18n="secrets.new">Create secret</span>
</button>
</div>
</div>
<div class="callout info">
<svg class="callout-icon" width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="var(--blue)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<circle cx="8" cy="8" r="6.5"/>
<path d="M8 11V8M8 5.5V5"/>
</svg>
<div>
<strong data-i18n="secrets.callout.title">Secret values are protected.</strong>
<span data-i18n="secrets.callout.body">After saving, secrets are encrypted with the workspace key and cannot be read back. A secret can be rotated or deleted.</span>
</div>
</div>
<div class="section-card" style="margin-top: 20px;">
<div class="section-card-header">
<div>
<div class="section-card-title" data-i18n="secrets.active.title">Workspace secrets</div>
<div class="section-card-subtitle" id="secrets-summary"></div>
</div>
<div class="filter-bar-search" style="max-width: 240px; margin: 0;">
<svg width="13" height="13" style="position:absolute; left:9px; top:50%; transform:translateY(-50%); color:var(--text-muted);" viewBox="0 0 16 16" fill="currentColor"><path d="M10.68 11.74a6 6 0 01-7.922-8.982 6 6 0 018.982 7.922l3.04 3.04a.749.749 0 01-.326 1.275.749.749 0 01-.734-.215zM11.5 7a4.499 4.499 0 11-8.997 0A4.499 4.499 0 0111.5 7z"/></svg>
<input type="text" id="secret-search" data-i18n-ph="secrets.search" placeholder="Filter secrets…" autocomplete="off">
</div>
</div>
<div class="section-card-body" style="padding-top: 0;">
<div id="secrets-table-wrap">
<table class="data-table">
<thead>
<tr>
<th data-i18n="secrets.th.name">Name</th>
<th data-i18n="secrets.th.kind">Kind</th>
<th data-i18n="secrets.th.version">Version</th>
<th data-i18n="secrets.th.last_used">Last used</th>
<th data-i18n="secrets.th.used_by">Used by</th>
<th data-i18n="secrets.th.status">Status</th>
<th></th>
</tr>
</thead>
<tbody id="secrets-tbody"></tbody>
</table>
</div>
<div class="resource-list secrets-card-list" id="secrets-card-list"></div>
</div>
</div>
</div>
<div class="modal-overlay" id="secret-modal" data-testid="secret-modal">
<div class="modal">
<div class="modal-header">
<span class="modal-title" id="secret-modal-title" data-i18n="secrets.modal.create_title">Create secret</span>
<button class="modal-close" id="secret-modal-close-btn" type="button">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round">
<line x1="1" y1="1" x2="11" y2="11"/><line x1="11" y1="1" x2="1" y2="11"/>
</svg>
</button>
</div>
<div class="modal-body">
<div class="field-group">
<label class="field-label" data-i18n="secrets.modal.name">Secret name</label>
<input class="field-input" id="secret-name" data-testid="secret-name-input" type="text" autocomplete="off">
<div class="field-hint" data-i18n="secrets.modal.name_hint">Use a clear name so you can select this secret in operation settings later.</div>
</div>
<div class="field-group">
<label class="field-label" data-i18n="secrets.modal.kind">Secret kind</label>
<select class="field-select" id="secret-kind" data-testid="secret-kind-select">
<option value="token" data-i18n="secrets.kind.token">Token</option>
<option value="username_password" data-i18n="secrets.kind.username_password">Username/password</option>
<option value="header" data-i18n="secrets.kind.header">Header value</option>
<option value="generic" data-i18n="secrets.kind.generic">Generic JSON</option>
</select>
<div class="field-hint" id="secret-kind-hint"></div>
</div>
<div class="secret-value-grid" id="secret-value-fields">
<div class="field-group" data-kind-field="string">
<label class="field-label" id="secret-string-label" data-i18n="secrets.modal.value">Token value</label>
<input class="field-input" id="secret-string-value" data-testid="secret-value-input" type="text" autocomplete="off">
</div>
<div class="field-row" data-kind-field="basic" hidden>
<div class="field-group">
<label class="field-label" data-i18n="secrets.modal.username">Username</label>
<input class="field-input" id="secret-username" data-testid="secret-username-input" type="text" autocomplete="off">
</div>
<div class="field-group">
<label class="field-label" data-i18n="secrets.modal.password">Password</label>
<input class="field-input" id="secret-password" data-testid="secret-password-input" type="password" autocomplete="new-password">
</div>
</div>
<div class="field-group" data-kind-field="json" hidden>
<label class="field-label" data-i18n="secrets.modal.json_payload">JSON</label>
<textarea class="field-textarea code-textarea" id="secret-json-value" data-testid="secret-json-input" rows="8" spellcheck="false"></textarea>
<div class="field-hint" data-i18n="secrets.modal.json_hint">Enter valid JSON. Use this type when an API needs several related values.</div>
</div>
</div>
<div class="secret-inline-note" id="secret-modal-note" data-i18n="secrets.modal.create_note">After saving, the value is encrypted and will no longer be displayed.</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" id="secret-modal-cancel-btn" type="button" data-i18n="secrets.modal.cancel">Cancel</button>
<button class="btn-primary" id="secret-modal-submit-btn" data-testid="secret-submit-button" type="button" data-i18n="secrets.modal.create_action">Create secret</button>
</div>
</div>
</div>
<script src="%CRANK_BUNDLE_SECRETS%"></script>
</body>
</html>