710 lines
24 KiB
JavaScript
710 lines
24 KiB
JavaScript
function initSecretsPage() {
|
|
var state = {
|
|
workspaceId: null,
|
|
secrets: [],
|
|
profiles: [],
|
|
derived: {
|
|
activeSecretCount: 0,
|
|
secretsById: {},
|
|
usageBySecret: {},
|
|
},
|
|
search: '',
|
|
loading: false,
|
|
error: '',
|
|
modalMode: 'create',
|
|
modalSecretId: null,
|
|
};
|
|
|
|
var modal = document.getElementById('secret-modal');
|
|
var tbody = document.getElementById('secrets-tbody');
|
|
var cardList = document.getElementById('secrets-card-list');
|
|
var profilesList = document.getElementById('auth-profiles-list');
|
|
var summary = document.getElementById('secrets-summary');
|
|
var profilesSummary = document.getElementById('secret-profiles-summary');
|
|
var searchInput = document.getElementById('secret-search');
|
|
var modalTitle = document.getElementById('secret-modal-title');
|
|
var modalName = document.getElementById('secret-name');
|
|
var modalKind = document.getElementById('secret-kind');
|
|
var modalNote = document.getElementById('secret-modal-note');
|
|
var modalSubmit = document.getElementById('secret-modal-submit-btn');
|
|
var kindHint = document.getElementById('secret-kind-hint');
|
|
var stringLabel = document.getElementById('secret-string-label');
|
|
var stringField = document.getElementById('secret-string-value');
|
|
var usernameField = document.getElementById('secret-username');
|
|
var passwordField = document.getElementById('secret-password');
|
|
var jsonField = document.getElementById('secret-json-value');
|
|
|
|
function tKey(key) {
|
|
return typeof t === 'function' ? t(key) : key;
|
|
}
|
|
|
|
function tfKey(key, vars) {
|
|
return typeof tf === 'function' ? tf(key, vars) : tKey(key);
|
|
}
|
|
|
|
function currentWorkspaceId() {
|
|
var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null;
|
|
return workspace ? workspace.id : null;
|
|
}
|
|
|
|
function currentWorkspace() {
|
|
return window.getCurrentWorkspace ? window.getCurrentWorkspace() : null;
|
|
}
|
|
|
|
function secretKindLabel(kind) {
|
|
return tKey('secrets.kind.' + String(kind || '').toLowerCase());
|
|
}
|
|
|
|
function authKindLabel(kind) {
|
|
return tKey('secrets.auth_kind.' + String(kind || '').toLowerCase());
|
|
}
|
|
|
|
function formatDate(value) {
|
|
if (!value) return '—';
|
|
return new Date(value).toLocaleDateString();
|
|
}
|
|
|
|
function secretIdsForProfile(profile) {
|
|
var config = profile && profile.config ? profile.config : {};
|
|
if (config.bearer) return [config.bearer.secret_id];
|
|
if (config.basic) return [config.basic.username_secret_id, config.basic.password_secret_id];
|
|
if (config.api_key_header) return [config.api_key_header.secret_id];
|
|
if (config.api_key_query) return [config.api_key_query.secret_id];
|
|
return [];
|
|
}
|
|
|
|
function profileSummary(profile, secretsById) {
|
|
var config = profile && profile.config ? profile.config : {};
|
|
if (config.bearer) {
|
|
return tfKey('secrets.profiles.summary_bearer', {
|
|
header: config.bearer.header_name,
|
|
secret: secretName(secretsById, config.bearer.secret_id),
|
|
});
|
|
}
|
|
if (config.basic) {
|
|
return tfKey('secrets.profiles.summary_basic', {
|
|
username: secretName(secretsById, config.basic.username_secret_id),
|
|
password: secretName(secretsById, config.basic.password_secret_id),
|
|
});
|
|
}
|
|
if (config.api_key_header) {
|
|
return tfKey('secrets.profiles.summary_api_key_header', {
|
|
header: config.api_key_header.header_name,
|
|
secret: secretName(secretsById, config.api_key_header.secret_id),
|
|
});
|
|
}
|
|
if (config.api_key_query) {
|
|
return tfKey('secrets.profiles.summary_api_key_query', {
|
|
param: config.api_key_query.param_name,
|
|
secret: secretName(secretsById, config.api_key_query.secret_id),
|
|
});
|
|
}
|
|
return tKey('secrets.profiles.summary_unknown');
|
|
}
|
|
|
|
function secretName(secretsById, secretId) {
|
|
var secret = secretsById[secretId];
|
|
return secret ? secret.name : secretId;
|
|
}
|
|
|
|
function buildUsageBySecret() {
|
|
var usage = {};
|
|
state.profiles.forEach(function (profile) {
|
|
secretIdsForProfile(profile).forEach(function (secretId) {
|
|
if (!secretId) return;
|
|
if (!usage[secretId]) usage[secretId] = [];
|
|
usage[secretId].push(profile);
|
|
});
|
|
});
|
|
return usage;
|
|
}
|
|
|
|
function recomputeDerivedState() {
|
|
var secretsById = {};
|
|
var activeSecretCount = 0;
|
|
|
|
state.secrets.forEach(function (secret) {
|
|
secretsById[secret.id] = secret;
|
|
if (secret.status === 'active') {
|
|
activeSecretCount += 1;
|
|
}
|
|
});
|
|
|
|
state.derived = {
|
|
activeSecretCount: activeSecretCount,
|
|
secretsById: secretsById,
|
|
usageBySecret: buildUsageBySecret(),
|
|
};
|
|
}
|
|
|
|
function resetModalFields() {
|
|
modalName.value = '';
|
|
modalKind.value = 'token';
|
|
stringField.value = '';
|
|
usernameField.value = '';
|
|
passwordField.value = '';
|
|
jsonField.value = '{\n "value": ""\n}';
|
|
}
|
|
|
|
function openModal(mode, secret) {
|
|
state.modalMode = mode;
|
|
state.modalSecretId = secret ? secret.id : null;
|
|
resetModalFields();
|
|
|
|
if (mode === 'rotate' && secret) {
|
|
modalTitle.textContent = tKey('secrets.modal.rotate_title');
|
|
modalName.value = secret.name;
|
|
modalName.disabled = true;
|
|
modalKind.value = secret.kind;
|
|
modalKind.disabled = true;
|
|
modalSubmit.textContent = tKey('secrets.modal.rotate_action');
|
|
modalNote.textContent = tKey('secrets.modal.rotate_note');
|
|
} else {
|
|
modalTitle.textContent = tKey('secrets.modal.create_title');
|
|
modalName.disabled = false;
|
|
modalKind.disabled = false;
|
|
modalSubmit.textContent = tKey('secrets.modal.create_action');
|
|
modalNote.textContent = tKey('secrets.modal.create_note');
|
|
}
|
|
|
|
updateKindFields();
|
|
modal.classList.add('open');
|
|
if (window.CrankDiagnostics && typeof window.CrankDiagnostics.ensureTestId === 'function') {
|
|
window.CrankDiagnostics.ensureTestId(modal, mode === 'rotate' ? 'secret-rotate-modal' : 'secret-create-modal');
|
|
}
|
|
setTimeout(function () {
|
|
if (mode === 'rotate') {
|
|
stringField.focus();
|
|
} else {
|
|
modalName.focus();
|
|
}
|
|
}, 30);
|
|
}
|
|
|
|
function closeModal() {
|
|
modal.classList.remove('open');
|
|
state.modalMode = 'create';
|
|
state.modalSecretId = null;
|
|
}
|
|
|
|
function updateKindFields() {
|
|
var kind = modalKind.value;
|
|
document.querySelectorAll('[data-kind-field="string"]').forEach(function (element) {
|
|
element.hidden = !(kind === 'token' || kind === 'header');
|
|
});
|
|
document.querySelectorAll('[data-kind-field="basic"]').forEach(function (element) {
|
|
element.hidden = kind !== 'username_password';
|
|
});
|
|
document.querySelectorAll('[data-kind-field="json"]').forEach(function (element) {
|
|
element.hidden = kind !== 'generic';
|
|
});
|
|
|
|
if (kind === 'token') {
|
|
stringLabel.textContent = tKey('secrets.modal.value');
|
|
} else if (kind === 'header') {
|
|
stringLabel.textContent = tKey('secrets.modal.header_value');
|
|
} else {
|
|
stringLabel.textContent = tKey('secrets.modal.value');
|
|
}
|
|
kindHint.textContent = tKey('secrets.hint.' + kind);
|
|
}
|
|
|
|
function buildSecretValue() {
|
|
var kind = modalKind.value;
|
|
if (kind === 'token' || kind === 'header') {
|
|
var value = stringField.value.trim();
|
|
if (!value) {
|
|
throw new Error(tKey('secrets.validation.value_required'));
|
|
}
|
|
return value;
|
|
}
|
|
if (kind === 'username_password') {
|
|
var username = usernameField.value.trim();
|
|
var password = passwordField.value;
|
|
if (!username || !password) {
|
|
throw new Error(tKey('secrets.validation.username_password_required'));
|
|
}
|
|
return {
|
|
username: username,
|
|
password: password,
|
|
};
|
|
}
|
|
try {
|
|
return JSON.parse(jsonField.value);
|
|
} catch (_error) {
|
|
throw new Error(tKey('secrets.validation.json_invalid'));
|
|
}
|
|
}
|
|
|
|
function renderSecrets() {
|
|
var usage = state.derived.usageBySecret;
|
|
var query = state.search.toLowerCase();
|
|
var rows = state.secrets.filter(function (secret) {
|
|
return !query || secret.name.toLowerCase().includes(query) || String(secret.kind || '').toLowerCase().includes(query);
|
|
});
|
|
|
|
summary.textContent = tfKey('secrets.active.subtitle', {
|
|
active: state.derived.activeSecretCount,
|
|
total: state.secrets.length,
|
|
});
|
|
|
|
window.CrankDom.clear(tbody);
|
|
if (cardList) {
|
|
window.CrankDom.clear(cardList);
|
|
}
|
|
|
|
if (state.loading && state.secrets.length === 0) {
|
|
var loadingRow = document.createElement('tr');
|
|
var loadingCell = document.createElement('td');
|
|
loadingCell.colSpan = 7;
|
|
loadingCell.style.cssText = 'text-align:center;padding:36px;color:var(--text-muted);';
|
|
loadingCell.textContent = tKey('secrets.loading');
|
|
loadingRow.appendChild(loadingCell);
|
|
tbody.appendChild(loadingRow);
|
|
renderSecretCards([], tKey('secrets.loading'));
|
|
return;
|
|
}
|
|
|
|
if (state.error) {
|
|
var errorRow = document.createElement('tr');
|
|
var errorCell = document.createElement('td');
|
|
errorCell.colSpan = 7;
|
|
errorCell.style.cssText = 'text-align:center;padding:36px;color:var(--danger,#f85149);';
|
|
errorCell.textContent = state.error;
|
|
errorRow.appendChild(errorCell);
|
|
tbody.appendChild(errorRow);
|
|
renderSecretCards([], state.error);
|
|
return;
|
|
}
|
|
|
|
if (!rows.length) {
|
|
var emptyRow = document.createElement('tr');
|
|
var emptyCell = document.createElement('td');
|
|
emptyCell.colSpan = 7;
|
|
emptyCell.style.cssText = 'text-align:center;padding:36px;color:var(--text-muted);';
|
|
emptyCell.textContent = state.secrets.length ? tKey('secrets.empty.search') : tKey('secrets.empty.none');
|
|
emptyRow.appendChild(emptyCell);
|
|
tbody.appendChild(emptyRow);
|
|
renderSecretCards(rows);
|
|
return;
|
|
}
|
|
|
|
rows.forEach(function (secret) {
|
|
var tr = document.createElement('tr');
|
|
var references = usage[secret.id] || [];
|
|
|
|
var nameCell = document.createElement('td');
|
|
nameCell.className = 'col-name';
|
|
nameCell.textContent = secret.name;
|
|
tr.appendChild(nameCell);
|
|
|
|
var kindCell = document.createElement('td');
|
|
kindCell.textContent = secretKindLabel(secret.kind);
|
|
tr.appendChild(kindCell);
|
|
|
|
var versionCell = document.createElement('td');
|
|
versionCell.className = 'col-mono';
|
|
versionCell.textContent = 'v' + secret.current_version;
|
|
tr.appendChild(versionCell);
|
|
|
|
var lastUsedCell = document.createElement('td');
|
|
lastUsedCell.textContent = secret.last_used_at ? formatDate(secret.last_used_at) : tKey('secrets.last_used.never');
|
|
tr.appendChild(lastUsedCell);
|
|
|
|
var usedByCell = document.createElement('td');
|
|
usedByCell.textContent = window.tPlural('secrets.used_by_count', references.length, { count: references.length });
|
|
tr.appendChild(usedByCell);
|
|
|
|
var statusCell = document.createElement('td');
|
|
var badge = document.createElement('span');
|
|
badge.className = 'badge ' + (secret.status === 'disabled' ? 'badge-revoked' : 'badge-active');
|
|
badge.textContent = tKey('secrets.status.' + secret.status);
|
|
statusCell.appendChild(badge);
|
|
tr.appendChild(statusCell);
|
|
|
|
var actions = document.createElement('td');
|
|
actions.className = 'col-actions';
|
|
var rotateButton = document.createElement('button');
|
|
rotateButton.type = 'button';
|
|
rotateButton.className = 'btn-secondary table-action-btn';
|
|
rotateButton.textContent = tKey('secrets.action.rotate');
|
|
rotateButton.setAttribute('data-testid', 'secret-rotate-action');
|
|
rotateButton.setAttribute('data-secret-id', secret.id);
|
|
rotateButton.addEventListener('click', function () {
|
|
openModal('rotate', secret);
|
|
});
|
|
actions.appendChild(rotateButton);
|
|
|
|
var deleteButton = document.createElement('button');
|
|
deleteButton.type = 'button';
|
|
deleteButton.className = 'btn-danger table-action-btn';
|
|
deleteButton.textContent = tKey('secrets.action.delete');
|
|
deleteButton.setAttribute('data-testid', 'secret-delete-action');
|
|
deleteButton.setAttribute('data-secret-id', secret.id);
|
|
deleteButton.addEventListener('click', async function () {
|
|
await deleteSecret(secret);
|
|
});
|
|
actions.appendChild(deleteButton);
|
|
tr.appendChild(actions);
|
|
|
|
tbody.appendChild(tr);
|
|
});
|
|
|
|
renderSecretCards(rows);
|
|
}
|
|
|
|
function renderSecretCards(rows, statusText) {
|
|
if (!cardList) {
|
|
return;
|
|
}
|
|
|
|
window.CrankDom.clear(cardList);
|
|
|
|
if (statusText && !rows.length) {
|
|
var messageCard = document.createElement('div');
|
|
messageCard.className = 'resource-card';
|
|
var message = document.createElement('div');
|
|
message.className = 'resource-meta-value';
|
|
message.textContent = statusText;
|
|
message.style.color = state.error ? 'var(--danger,#f85149)' : 'var(--text-muted)';
|
|
messageCard.appendChild(message);
|
|
cardList.appendChild(messageCard);
|
|
return;
|
|
}
|
|
|
|
rows.forEach(function(secret) {
|
|
var references = state.derived.usageBySecret[secret.id] || [];
|
|
var card = document.createElement('div');
|
|
card.className = 'resource-card';
|
|
|
|
var header = document.createElement('div');
|
|
header.className = 'resource-card-header';
|
|
var headerBody = document.createElement('div');
|
|
var title = document.createElement('div');
|
|
title.className = 'resource-card-title';
|
|
title.textContent = secret.name;
|
|
var subtitle = document.createElement('div');
|
|
subtitle.className = 'resource-card-subtitle';
|
|
subtitle.textContent = secretKindLabel(secret.kind);
|
|
headerBody.appendChild(title);
|
|
headerBody.appendChild(subtitle);
|
|
|
|
var actions = document.createElement('div');
|
|
actions.className = 'resource-card-actions';
|
|
var badge = document.createElement('span');
|
|
badge.className = 'badge ' + (secret.status === 'disabled' ? 'badge-revoked' : 'badge-active');
|
|
badge.textContent = tKey('secrets.status.' + secret.status);
|
|
actions.appendChild(badge);
|
|
header.appendChild(headerBody);
|
|
header.appendChild(actions);
|
|
card.appendChild(header);
|
|
|
|
var metaGrid = document.createElement('div');
|
|
metaGrid.className = 'resource-meta-grid';
|
|
metaGrid.appendChild(buildSecretMetaItem('secrets.th.version', 'v' + secret.current_version));
|
|
metaGrid.appendChild(buildSecretMetaItem(
|
|
'secrets.th.last_used',
|
|
secret.last_used_at ? formatDate(secret.last_used_at) : tKey('secrets.last_used.never')
|
|
));
|
|
metaGrid.appendChild(buildSecretMetaItem(
|
|
'secrets.th.used_by',
|
|
window.tPlural('secrets.used_by_count', references.length, { count: references.length })
|
|
));
|
|
card.appendChild(metaGrid);
|
|
|
|
var actionRow = document.createElement('div');
|
|
actionRow.className = 'resource-card-actions';
|
|
var rotateButton = document.createElement('button');
|
|
rotateButton.className = 'btn-secondary';
|
|
rotateButton.type = 'button';
|
|
rotateButton.textContent = tKey('secrets.action.rotate');
|
|
rotateButton.addEventListener('click', function() {
|
|
openModal('rotate', secret);
|
|
});
|
|
actionRow.appendChild(rotateButton);
|
|
|
|
var deleteButton = document.createElement('button');
|
|
deleteButton.className = 'btn-secondary';
|
|
deleteButton.type = 'button';
|
|
deleteButton.textContent = tKey('secrets.action.delete');
|
|
deleteButton.addEventListener('click', async function() {
|
|
await deleteSecret(secret);
|
|
});
|
|
actionRow.appendChild(deleteButton);
|
|
card.appendChild(actionRow);
|
|
|
|
cardList.appendChild(card);
|
|
});
|
|
}
|
|
|
|
function buildSecretMetaItem(labelKey, valueText) {
|
|
var item = document.createElement('div');
|
|
item.className = 'resource-meta-item';
|
|
var label = document.createElement('div');
|
|
label.className = 'resource-meta-label';
|
|
label.textContent = tKey(labelKey);
|
|
var value = document.createElement('div');
|
|
value.className = 'resource-meta-value';
|
|
value.textContent = valueText;
|
|
item.appendChild(label);
|
|
item.appendChild(value);
|
|
return item;
|
|
}
|
|
|
|
function renderProfiles() {
|
|
var usage = state.derived.usageBySecret;
|
|
var secretsById = state.derived.secretsById;
|
|
|
|
profilesSummary.textContent = window.tPlural(
|
|
'secrets.profiles.subtitle_count',
|
|
state.profiles.length,
|
|
{ count: state.profiles.length }
|
|
);
|
|
|
|
window.CrankDom.clear(profilesList);
|
|
|
|
if (state.error) {
|
|
profilesList.appendChild(window.CrankDom.createEmptyState(
|
|
tKey('secrets.profiles.error_title'),
|
|
state.error
|
|
));
|
|
return;
|
|
}
|
|
|
|
if (state.loading && state.profiles.length === 0) {
|
|
profilesList.appendChild(window.CrankDom.createEmptyState(
|
|
tKey('secrets.profiles.loading_title'),
|
|
tKey('secrets.profiles.loading_body')
|
|
));
|
|
return;
|
|
}
|
|
|
|
if (!state.profiles.length) {
|
|
profilesList.appendChild(window.CrankDom.createEmptyState(
|
|
tKey('secrets.profiles.empty_title'),
|
|
tKey('secrets.profiles.empty_body')
|
|
));
|
|
return;
|
|
}
|
|
|
|
state.profiles.forEach(function (profile) {
|
|
var secretIds = secretIdsForProfile(profile);
|
|
var card = document.createElement('div');
|
|
card.className = 'resource-card';
|
|
|
|
var header = document.createElement('div');
|
|
header.className = 'resource-card-header';
|
|
var headerBody = document.createElement('div');
|
|
|
|
var title = document.createElement('div');
|
|
title.className = 'resource-card-title';
|
|
title.textContent = profile.name;
|
|
headerBody.appendChild(title);
|
|
|
|
var subtitle = document.createElement('div');
|
|
subtitle.className = 'resource-card-subtitle';
|
|
subtitle.textContent = profileSummary(profile, secretsById);
|
|
headerBody.appendChild(subtitle);
|
|
|
|
var pillRow = document.createElement('div');
|
|
pillRow.className = 'resource-pill-row';
|
|
var statusPill = document.createElement('span');
|
|
statusPill.className = 'resource-status-pill active';
|
|
statusPill.textContent = authKindLabel(profile.kind);
|
|
pillRow.appendChild(statusPill);
|
|
headerBody.appendChild(pillRow);
|
|
header.appendChild(headerBody);
|
|
card.appendChild(header);
|
|
|
|
var metaGrid = document.createElement('div');
|
|
metaGrid.className = 'resource-meta-grid';
|
|
[
|
|
[tKey('secrets.profiles.meta.created'), formatDate(profile.created_at)],
|
|
[tKey('secrets.profiles.meta.updated'), formatDate(profile.updated_at)]
|
|
].forEach(function(entry) {
|
|
var item = document.createElement('div');
|
|
item.className = 'resource-meta-item';
|
|
var label = document.createElement('div');
|
|
label.className = 'resource-meta-label';
|
|
label.textContent = entry[0];
|
|
var value = document.createElement('div');
|
|
value.className = 'resource-meta-value';
|
|
value.textContent = entry[1];
|
|
item.appendChild(label);
|
|
item.appendChild(value);
|
|
metaGrid.appendChild(item);
|
|
});
|
|
card.appendChild(metaGrid);
|
|
|
|
var detail = document.createElement('div');
|
|
detail.className = 'resource-detail-block';
|
|
var detailTitle = document.createElement('div');
|
|
detailTitle.className = 'resource-detail-title';
|
|
detailTitle.textContent = tKey('secrets.profiles.references');
|
|
detail.appendChild(detailTitle);
|
|
|
|
var refList = document.createElement('div');
|
|
refList.className = 'secret-ref-list';
|
|
secretIds.forEach(function(secretId) {
|
|
var users = usage[secretId] || [];
|
|
var pill = document.createElement('span');
|
|
pill.className = 'secret-ref-pill';
|
|
var strong = document.createElement('strong');
|
|
strong.textContent = secretName(secretsById, secretId);
|
|
var count = document.createElement('span');
|
|
count.textContent = tfKey('secrets.profiles.reference_count', { count: users.length });
|
|
pill.appendChild(strong);
|
|
pill.appendChild(count);
|
|
refList.appendChild(pill);
|
|
});
|
|
detail.appendChild(refList);
|
|
card.appendChild(detail);
|
|
|
|
profilesList.appendChild(card);
|
|
});
|
|
}
|
|
|
|
async function load() {
|
|
state.workspaceId = currentWorkspaceId();
|
|
state.loading = true;
|
|
state.error = '';
|
|
renderSecrets();
|
|
renderProfiles();
|
|
|
|
if (!state.workspaceId) {
|
|
state.secrets = [];
|
|
state.profiles = [];
|
|
recomputeDerivedState();
|
|
state.loading = false;
|
|
state.error = tKey('secrets.error.workspace');
|
|
renderSecrets();
|
|
renderProfiles();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var results = await Promise.all([
|
|
window.CrankApi.listSecrets(state.workspaceId),
|
|
window.CrankApi.listAuthProfiles(state.workspaceId),
|
|
]);
|
|
state.secrets = (results[0] && results[0].items) || [];
|
|
state.profiles = (results[1] && results[1].items) || [];
|
|
recomputeDerivedState();
|
|
} catch (error) {
|
|
state.error = error.message || tKey('secrets.error.load');
|
|
state.secrets = [];
|
|
state.profiles = [];
|
|
recomputeDerivedState();
|
|
} finally {
|
|
state.loading = false;
|
|
renderSecrets();
|
|
renderProfiles();
|
|
}
|
|
}
|
|
|
|
async function deleteSecret(secret) {
|
|
if (!confirm(tfKey('secrets.confirm.delete', { name: secret.name }))) {
|
|
return;
|
|
}
|
|
try {
|
|
await window.CrankApi.deleteSecret(state.workspaceId, secret.id);
|
|
await load();
|
|
if (window.CrankUi) {
|
|
window.CrankUi.success(
|
|
tfKey('secrets.toast.delete_message', { name: secret.name }),
|
|
tKey('secrets.toast.delete_title')
|
|
);
|
|
}
|
|
} catch (error) {
|
|
if (window.CrankUi) {
|
|
window.CrankUi.error(
|
|
error.message || tKey('secrets.toast.delete_error_message'),
|
|
tKey('secrets.toast.delete_error_title')
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function submitModal() {
|
|
var kind = modalKind.value;
|
|
var value = buildSecretValue();
|
|
modalSubmit.disabled = true;
|
|
modalSubmit.textContent = state.modalMode === 'rotate'
|
|
? tKey('secrets.modal.rotating')
|
|
: tKey('secrets.modal.creating');
|
|
|
|
try {
|
|
if (state.modalMode === 'rotate') {
|
|
await window.CrankApi.rotateSecret(state.workspaceId, state.modalSecretId, { value: value });
|
|
if (window.CrankUi) {
|
|
window.CrankUi.success(
|
|
tKey('secrets.toast.rotate_message'),
|
|
tKey('secrets.toast.rotate_title')
|
|
);
|
|
}
|
|
} else {
|
|
var name = modalName.value.trim();
|
|
if (!name) {
|
|
throw new Error(tKey('secrets.validation.name_required'));
|
|
}
|
|
await window.CrankApi.createSecret(state.workspaceId, {
|
|
name: name,
|
|
kind: kind,
|
|
value: value,
|
|
});
|
|
if (window.CrankUi) {
|
|
window.CrankUi.success(
|
|
tKey('secrets.toast.create_message'),
|
|
tKey('secrets.toast.create_title')
|
|
);
|
|
}
|
|
}
|
|
closeModal();
|
|
await load();
|
|
} catch (error) {
|
|
if (window.CrankUi) {
|
|
window.CrankUi.error(
|
|
error.message || tKey(state.modalMode === 'rotate' ? 'secrets.toast.rotate_error_message' : 'secrets.toast.create_error_message'),
|
|
tKey(state.modalMode === 'rotate' ? 'secrets.toast.rotate_error_title' : 'secrets.toast.create_error_title')
|
|
);
|
|
}
|
|
} finally {
|
|
modalSubmit.disabled = false;
|
|
modalSubmit.textContent = state.modalMode === 'rotate'
|
|
? tKey('secrets.modal.rotate_action')
|
|
: tKey('secrets.modal.create_action');
|
|
}
|
|
}
|
|
|
|
document.getElementById('btn-create-secret').addEventListener('click', function () {
|
|
openModal('create');
|
|
});
|
|
document.getElementById('secret-modal-close-btn').addEventListener('click', closeModal);
|
|
document.getElementById('secret-modal-cancel-btn').addEventListener('click', closeModal);
|
|
modal.addEventListener('click', function (event) {
|
|
if (event.target === modal) {
|
|
closeModal();
|
|
}
|
|
});
|
|
modalKind.addEventListener('change', updateKindFields);
|
|
modalSubmit.addEventListener('click', function () {
|
|
void submitModal();
|
|
});
|
|
searchInput.addEventListener('input', function (event) {
|
|
state.search = event.target.value || '';
|
|
renderSecrets();
|
|
});
|
|
document.addEventListener('workspace:changed', function () {
|
|
void load();
|
|
});
|
|
|
|
updateKindFields();
|
|
void load();
|
|
}
|
|
|
|
if (window.CrankDiagnostics && typeof window.CrankDiagnostics.bootstrap === 'function') {
|
|
window.CrankDiagnostics.bootstrap('secrets', initSecretsPage);
|
|
} else {
|
|
document.addEventListener('DOMContentLoaded', initSecretsPage);
|
|
}
|