fix: EnsureNamespace in doCreate + sanitize API errors in UI

- doCreate() calls EnsureNamespace before creating device (idempotent)
  Fixes namespace-not-found when session was cached before v0.1.62
- Added sanitizeApiError() — hides internal details (namespace names,
  k8s paths) from user, shows friendly Russian messages instead
- Patterns handled: namespace not found, already exists, HTTP 5xx

Image: v0.1.63
This commit is contained in:
Naeel
2026-04-05 11:11:50 +03:00
parent 11bc86d2c9
commit 20846297af
+21 -1
View File
@@ -333,6 +333,21 @@ async function namespaceFromToken(token) {
// API
// =============================================================
// sanitizeApiError — убирает служебные детали из сообщений об ошибках API.
// Пользователь не должен видеть namespace, k8s-пути, internal имена объектов.
function sanitizeApiError(msg) {
if (!msg) return 'Внутренняя ошибка. Попробуйте ещё раз.';
// Скрываем k8s-сообщения с namespace/not found
if (/namespaces?.*not found/i.test(msg)) return 'Ошибка инициализации. Выйдите и войдите снова.';
if (/not found/i.test(msg) && /sless-/i.test(msg)) return 'Ресурс не найден.';
if (/already exists/i.test(msg)) return 'Устройство с таким именем уже существует.';
if (/device.*id.*already/i.test(msg) || /device_id.*exists/i.test(msg)) return 'Device ID уже занят.';
if (/cannot decode jwt/i.test(msg) || /not a jwt/i.test(msg)) return 'Неверный токен.';
// HTTP 5xx — общая фраза
if (/HTTP 5/i.test(msg)) return 'Ошибка сервера. Попробуйте ещё раз.';
return msg;
}
// apiCall — универсальная обёртка для REST запросов к оператору
async function apiCall(method, path, body) {
const opts = {
@@ -738,12 +753,17 @@ async function doCreate() {
btn.disabled = true; btn.textContent = 'Создание...';
try {
// EnsureNamespace — идемпотентный вызов.
// Страховка на случай если сессия была сохранена до автосоздания namespace при логине.
await apiCall('POST', `/v1/namespaces/${S.ns}/ensure`, {});
await apiCreateDevice(name, did);
closeModal();
await renderDevices();
} catch(e) {
btn.disabled = false; btn.textContent = 'Создать';
document.getElementById('m-err').innerHTML = `<div class="alert alert-error">${h(e.message)}</div>`;
// Скрываем служебные детали (namespace, k8s-пути) от пользователя
const msg = sanitizeApiError(e.message);
document.getElementById('m-err').innerHTML = `<div class="alert alert-error">${h(msg)}</div>`;
}
}