ui: add frontend diagnostics and stable test hooks

This commit is contained in:
a.tolmachev
2026-04-12 11:23:10 +03:00
parent d85f7ddf05
commit 1cb4cbc960
13 changed files with 229 additions and 51 deletions
+3
View File
@@ -139,6 +139,9 @@
document.querySelectorAll('.user-dropdown-role').forEach(function(element) {
element.textContent = role + ' · ' + workspace;
});
if (window.CrankDiagnostics && typeof window.CrankDiagnostics.ensureShellTestIds === 'function') {
window.CrankDiagnostics.ensureShellTestIds();
}
}
function replaceSession(session) {
+111
View File
@@ -0,0 +1,111 @@
(function() {
var currentPage = '';
function messageFor(error) {
if (!error) {
return 'unknown error';
}
if (error.message) {
return error.message;
}
return String(error);
}
function setPage(pageName) {
currentPage = pageName || '';
document.documentElement.setAttribute('data-crank-page', currentPage || 'unknown');
}
function markBootstrapState(pageName, status, details) {
document.documentElement.setAttribute('data-crank-bootstrap-page', pageName || currentPage || 'unknown');
document.documentElement.setAttribute('data-crank-bootstrap-state', status);
if (status === 'error' && details) {
document.documentElement.setAttribute('data-crank-bootstrap-error', details);
return;
}
document.documentElement.removeAttribute('data-crank-bootstrap-error');
}
function report(stage, error, pageName) {
var resolvedPage = pageName || currentPage || 'unknown';
console.error('[CrankUI][' + resolvedPage + '][' + stage + ']', error);
window.dispatchEvent(new CustomEvent('crank:ui-error', {
detail: {
page: resolvedPage,
stage: stage,
message: messageFor(error),
},
}));
}
function bootstrap(pageName, init) {
function run() {
setPage(pageName);
markBootstrapState(pageName, 'pending');
try {
var result = init();
if (result && typeof result.then === 'function') {
result.then(function() {
markBootstrapState(pageName, 'ready');
}).catch(function(error) {
markBootstrapState(pageName, 'error', messageFor(error));
report('bootstrap', error, pageName);
});
return;
}
markBootstrapState(pageName, 'ready');
} catch (error) {
markBootstrapState(pageName, 'error', messageFor(error));
report('bootstrap', error, pageName);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', run, { once: true });
return;
}
run();
}
function ensureTestId(element, value) {
if (!element || !value) {
return;
}
element.setAttribute('data-testid', value);
}
function ensureShellTestIds() {
document.querySelectorAll('.nav-avatar').forEach(function(element) {
ensureTestId(element, 'shell-avatar');
});
document.querySelectorAll('.user-dropdown-name').forEach(function(element) {
ensureTestId(element, 'shell-user-name');
});
document.querySelectorAll('.user-dropdown-role').forEach(function(element) {
ensureTestId(element, 'shell-user-role');
});
ensureTestId(document.getElementById('ws-current-name'), 'shell-workspace-name');
}
window.addEventListener('error', function(event) {
if (!currentPage) {
return;
}
report('runtime', event.error || new Error(event.message || 'window error'));
});
window.addEventListener('unhandledrejection', function(event) {
if (!currentPage) {
return;
}
report('unhandledrejection', event.reason || new Error('unhandled rejection'));
});
window.CrankDiagnostics = {
bootstrap: bootstrap,
ensureTestId: ensureTestId,
ensureShellTestIds: ensureShellTestIds,
report: report,
setPage: setPage,
};
}());
+11 -3
View File
@@ -14,9 +14,11 @@
errorElement.style.display = 'none';
}
document.addEventListener('DOMContentLoaded', function() {
function initLoginPage() {
window.CrankAuth.guardLoginPage().catch(function(error) {
console.error(error);
if (window.CrankDiagnostics && typeof window.CrankDiagnostics.report === 'function') {
window.CrankDiagnostics.report('guard-login-page', error, 'login');
}
});
document.getElementById('login-form').addEventListener('submit', async function(event) {
@@ -42,5 +44,11 @@
showError(tKey('login.error.generic'));
}
});
});
}
if (window.CrankDiagnostics && typeof window.CrankDiagnostics.bootstrap === 'function') {
window.CrankDiagnostics.bootstrap('login', initLoginPage);
return;
}
document.addEventListener('DOMContentLoaded', initLoginPage);
}());
+15 -2
View File
@@ -1,4 +1,4 @@
document.addEventListener('DOMContentLoaded', function () {
function initSecretsPage() {
var state = {
workspaceId: null,
secrets: [],
@@ -145,6 +145,9 @@ document.addEventListener('DOMContentLoaded', function () {
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();
@@ -295,6 +298,8 @@ document.addEventListener('DOMContentLoaded', function () {
var rotateButton = document.createElement('button');
rotateButton.className = 'icon-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);
});
@@ -303,6 +308,8 @@ document.addEventListener('DOMContentLoaded', function () {
var deleteButton = document.createElement('button');
deleteButton.className = 'icon-btn danger';
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);
});
@@ -560,4 +567,10 @@ document.addEventListener('DOMContentLoaded', function () {
updateKindFields();
void load();
});
}
if (window.CrankDiagnostics && typeof window.CrankDiagnostics.bootstrap === 'function') {
window.CrankDiagnostics.bootstrap('secrets', initSecretsPage);
} else {
document.addEventListener('DOMContentLoaded', initSecretsPage);
}
+10 -2
View File
@@ -35,7 +35,7 @@ var editingUpstreamId = window.editingUpstreamId;
var protoParsed = window.protoParsed;
var selectedRpcMethod = window.selectedRpcMethod;
document.addEventListener('DOMContentLoaded', async function() {
async function initWizardPage() {
document.querySelector('.btn-continue').addEventListener('click', function() {
if (currentStep < TOTAL_STEPS) {
goToStep(currentStep + 1);
@@ -128,7 +128,15 @@ document.addEventListener('DOMContentLoaded', async function() {
updateWizardProtocolVisibility();
_doGoToStep(1);
});
}
if (window.CrankDiagnostics && typeof window.CrankDiagnostics.bootstrap === 'function') {
window.CrankDiagnostics.bootstrap('wizard', initWizardPage);
} else {
document.addEventListener('DOMContentLoaded', function() {
void initWizardPage();
});
}
/* ── HTTP method picker (Step 4 REST) ── */