diff --git a/extensions/dist/gnexus-creds-extension-chrome-0.2.1.zip b/extensions/dist/gnexus-creds-extension-chrome-0.2.1.zip
deleted file mode 100644
index 5977341..0000000
--- a/extensions/dist/gnexus-creds-extension-chrome-0.2.1.zip
+++ /dev/null
Binary files differ
diff --git a/extensions/dist/gnexus-creds-extension-chrome-0.2.2.zip b/extensions/dist/gnexus-creds-extension-chrome-0.2.2.zip
new file mode 100644
index 0000000..3cf04ee
--- /dev/null
+++ b/extensions/dist/gnexus-creds-extension-chrome-0.2.2.zip
Binary files differ
diff --git a/extensions/dist/gnexus-creds-extension-firefox-0.2.1.zip b/extensions/dist/gnexus-creds-extension-firefox-0.2.1.zip
deleted file mode 100644
index 014e607..0000000
--- a/extensions/dist/gnexus-creds-extension-firefox-0.2.1.zip
+++ /dev/null
Binary files differ
diff --git a/extensions/dist/gnexus-creds-extension-firefox-0.2.2.zip b/extensions/dist/gnexus-creds-extension-firefox-0.2.2.zip
new file mode 100644
index 0000000..0a49e47
--- /dev/null
+++ b/extensions/dist/gnexus-creds-extension-firefox-0.2.2.zip
Binary files differ
diff --git a/extensions/extension/Makefile b/extensions/extension/Makefile
index 1494343..4525beb 100644
--- a/extensions/extension/Makefile
+++ b/extensions/extension/Makefile
@@ -1,5 +1,5 @@
NAME := gnexus-creds-extension
-VERSION := 0.2.1
+VERSION := 0.2.2
# Per-file prerequisites: a directory prerequisite (plain "src") never goes
# stale when only a file inside it is edited.
SRC := manifest.json $(shell find src icons lib -type f)
diff --git a/extensions/extension/manifest.json b/extensions/extension/manifest.json
index cac27a8..df5f1e8 100644
--- a/extensions/extension/manifest.json
+++ b/extensions/extension/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "gnexus-creds",
- "version": "0.2.1",
+ "version": "0.2.2",
"description": "Browser extension for gnexus-creds secret manager.",
"permissions": ["storage", "activeTab", "alarms"],
"host_permissions": ["https://*/*", "http://*/*"],
diff --git a/extensions/extension/pack-src/config.json b/extensions/extension/pack-src/config.json
index 0a8d3e0..11a751b 100644
--- a/extensions/extension/pack-src/config.json
+++ b/extensions/extension/pack-src/config.json
@@ -30,7 +30,9 @@
"enterToken": "Enter API token",
"settingsSaved": "Settings saved",
"error": "Error",
- "openOnSite": "Open on gnexus-creds"
+ "openOnSite": "Open on gnexus-creds",
+ "tokenInvalid": "The saved API token no longer works — open Settings and paste a new one.",
+ "tokenId": "Token ID"
},
"uk": {
"use": "Використати",
@@ -53,7 +55,9 @@
"enterToken": "Вкажіть API-токен",
"settingsSaved": "Налаштування збережено",
"error": "Помилка",
- "openOnSite": "Відкрити на gnexus-creds"
+ "openOnSite": "Відкрити на gnexus-creds",
+ "tokenInvalid": "Збережений API-токен більше не працює — відкрийте налаштування та вставте новий.",
+ "tokenId": "ID токена"
},
"ru": {
"use": "Использовать",
@@ -76,7 +80,9 @@
"enterToken": "Введите API-токен",
"settingsSaved": "Настройки сохранены",
"error": "Ошибка",
- "openOnSite": "Открыть на gnexus-creds"
+ "openOnSite": "Открыть на gnexus-creds",
+ "tokenInvalid": "Сохранённый API-токен больше не работает — откройте настройки и вставьте новый.",
+ "tokenId": "ID токена"
}
}
}
\ No newline at end of file
diff --git a/extensions/extension/src/api.js b/extensions/extension/src/api.js
index 48a8779..ae6cf5f 100644
--- a/extensions/extension/src/api.js
+++ b/extensions/extension/src/api.js
@@ -15,7 +15,9 @@
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
- throw new Error(payload?.error?.message || `HTTP ${response.status}`);
+ const err = new Error(payload?.error?.message || `HTTP ${response.status}`);
+ err.status = response.status;
+ throw err;
}
return payload;
}
diff --git a/extensions/extension/src/background.js b/extensions/extension/src/background.js
index 99d4472..81c61b1 100644
--- a/extensions/extension/src/background.js
+++ b/extensions/extension/src/background.js
@@ -185,6 +185,17 @@
await syncUiLocale("alarm");
});
+// Revoked token visibility: a 401 from any server call must not stay
+// silent — the popup reads the flag and shows "token invalid" instead of
+// looking like a working (but stale) extension
+async function setTokenInvalid(value) {
+ if (value) {
+ await chrome.storage.local.set({ tokenInvalid: true });
+ } else {
+ await chrome.storage.local.remove("tokenInvalid");
+ }
+}
+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
(async () => {
try {
@@ -205,6 +216,7 @@
case "VERIFY_TOKEN":
{
const me = await getMe(message.payload.token, message.payload.baseUrl);
+ await setTokenInvalid(false);
// cache the per-user UI language while we hold a fresh /me
try {
const locale = String(me?.locale_effective || me?.locale || "en").trim().toLowerCase();
@@ -218,6 +230,7 @@
case "LIST_SECRETS":
{
const data = await getSecretsCached(message.payload?.force);
+ await setTokenInvalid(false);
sendResponse({ ok: true, data });
}
break;
@@ -236,6 +249,7 @@
case "CREATE_SECRET":
{
const data = await createSecret(token, baseUrl, message.payload);
+ await setTokenInvalid(false);
invalidateCache();
try {
await chrome.storage.session.remove("pendingSave");
@@ -317,7 +331,12 @@
sendResponse({ ok: false, error: "Unknown message type" });
}
} catch (err) {
- sendResponse({ ok: false, error: err.message });
+ // 401 from any API call means the stored token no longer works —
+ // remember it for the popup; a successful VERIFY_TOKEN clears it
+ if (err?.status === 401) {
+ await setTokenInvalid(true).catch(() => {});
+ }
+ sendResponse({ ok: false, error: err.message, status: err.status });
}
})();
return true;
diff --git a/extensions/extension/src/pack-shared.js b/extensions/extension/src/pack-shared.js
index cac9410..2e67a13 100644
--- a/extensions/extension/src/pack-shared.js
+++ b/extensions/extension/src/pack-shared.js
@@ -171,6 +171,7 @@
"openAppTitle", "refresh", "settingsTitle", "searchPlaceholder",
"noSecrets", "saveCredentials", "account", "credentialsSaved",
"enterToken", "settingsSaved", "error", "openOnSite",
+ "tokenInvalid", "tokenId",
];
const LOCALIZED_LOCALES = ["en", "uk", "ru"];
@@ -198,6 +199,8 @@
settingsSaved: "Settings saved",
error: "Error",
openOnSite: "Open on gnexus-creds",
+ tokenInvalid: "The saved API token no longer works — open Settings and paste a new one.",
+ tokenId: "Token ID",
};
const DEFAULT_STRINGS_BY_LOCALE = {
diff --git a/extensions/extension/src/popup/popup.js b/extensions/extension/src/popup/popup.js
index f665a37..3abd770 100644
--- a/extensions/extension/src/popup/popup.js
+++ b/extensions/extension/src/popup/popup.js
@@ -13,7 +13,9 @@
return;
}
if (!response || !response.ok) {
- reject(new Error(response?.error || "Unknown error"));
+ const err = new Error(response?.error || "Unknown error");
+ err.status = response?.status;
+ reject(err);
return;
}
resolve(response.data);
@@ -83,6 +85,7 @@
${loc.tokenHint}
+
@@ -106,6 +109,17 @@
const settingsErrorWrap = settingsDrawer.querySelector("#settings-error-wrap");
const settingsSuccessWrap = settingsDrawer.querySelector("#settings-success-wrap");
+// The stored token carries its public id: gcr_{public_id16hex}_{secret}.
+// Showing the id lets the user match the popup against the tokens table on
+// the site (which token is this extension actually using).
+async function updateTokenId() {
+ const el = document.getElementById("token-id");
+ if (!el) return;
+ const token = String((await chrome.storage.local.get("token")).token || "");
+ const publicId = token.match(/^gcr_([0-9a-f]{16})_/)?.[1] || "";
+ el.textContent = publicId ? `${loc.tokenId}: ${publicId}` : "";
+}
+
function escapeHtml(text) {
if (text == null) return "";
const div = document.createElement("div");
@@ -228,6 +242,15 @@
try {
const settings = await sendMessage("GET_SETTINGS");
baseUrlInput.value = settings.baseUrl || "https://creds.gnexus.space";
+ updateTokenId();
+
+ // The background sets a 401-side flag; show the invalid-token plate
+ // instead of a silently stale secrets list.
+ const flagged = (await chrome.storage.local.get("tokenInvalid")).tokenInvalid;
+ if (flagged && settings.token) {
+ mainErrorWrap.classList.remove("d-none");
+ showWrapAlert(mainErrorWrap, "error", loc.tokenInvalid);
+ }
const pending = await sendMessage("GET_PENDING_SAVE");
if (pending) {
@@ -270,6 +293,12 @@
secretsList.innerHTML = "";
emptyState.classList.remove("d-none");
emptyState.querySelector(".empty-state-text").textContent = `${loc.error}: ${err.message}`;
+ // the 401 that flips the background flag is discovered right here —
+ // surface the dedicated plate on this same load, not only after a reload
+ if (err?.status === 401) {
+ mainErrorWrap.classList.remove("d-none");
+ showWrapAlert(mainErrorWrap, "error", loc.tokenInvalid);
+ }
} finally {
loaderWrap.classList.add("d-none");
}
@@ -342,6 +371,7 @@
settingsDrawer.show();
// body() rebuilds on show() — set the status line after that
updatePackStatus(await applyPackStyles());
+ updateTokenId();
});
openSiteBtn.addEventListener("click", () => {
diff --git a/extensions/manifest.json b/extensions/manifest.json
index 556a228..5f1fdf8 100644
--- a/extensions/manifest.json
+++ b/extensions/manifest.json
@@ -1,19 +1,19 @@
{
- "version": "0.2.1",
+ "version": "0.2.2",
"released_at": "2026-10-02T00:00:00Z",
- "notes": "Design-resource pack: CSS, detection tables and UI config refresh from the server without re-releasing the extension.",
+ "notes": "Revoked-token visibility: the popup flags a dead token and shows its public ID; the tokens table adds Public ID, Created and Last used columns.",
"builds": [
{
"browser": "chrome",
- "filename": "gnexus-creds-extension-chrome-0.2.1.zip",
- "sha256": "a516b258b7f6ee4221355ddf976fffeefb76506b61b318a49fbf48a273ef7d2a",
- "size": 5061252
+ "filename": "gnexus-creds-extension-chrome-0.2.2.zip",
+ "sha256": "9db133fcb0b7dcccfa19a0243dc56b6e30b684d4f7e64ed0c757dcae11a71251",
+ "size": 5062020
},
{
"browser": "firefox",
- "filename": "gnexus-creds-extension-firefox-0.2.1.zip",
- "sha256": "30ee6dfff83693324f770196d990af76c781f1d9da8cfcbb8ee88ae82aa9384c",
- "size": 5060362
+ "filename": "gnexus-creds-extension-firefox-0.2.2.zip",
+ "sha256": "898c7b4b71358d18a7b080aeb0652d0164a876789726752c405de56dc92e9f84",
+ "size": 5061105
}
]
}
diff --git a/extensions/pack/pack.json b/extensions/pack/pack.json
index 1235afb..c503652 100644
--- a/extensions/pack/pack.json
+++ b/extensions/pack/pack.json
@@ -1,8 +1,8 @@
{
- "packVersion": 18,
- "minExtensionVersion": "0.2.0",
+ "packVersion": 24,
+ "minExtensionVersion": "0.2.2",
"maxExtensionVersion": null,
- "publishedAt": "2026-10-02T08:43:57.810Z",
+ "publishedAt": "2026-10-02T20:20:01.090Z",
"files": {
"content.css": {
"sha256": "e17800f82cbde350b6fe3c60746136260844ede20a5f1d4fbfd2d4930fcd6005",
@@ -17,8 +17,8 @@
"size": 2211
},
"config.json": {
- "sha256": "19c7b3cfc7b42da9f87dcb6d5d9a55af8be976b8a1b1ab3ad4cd75c62a3b1ba9",
- "size": 2973
+ "sha256": "5b11b004a51f2be2f05b2c6e4652b10a1795d14ff7691b047182ca304cad81cf",
+ "size": 3498
}
}
}
diff --git a/extensions/pack/v24/config.json b/extensions/pack/v24/config.json
new file mode 100644
index 0000000..11a751b
--- /dev/null
+++ b/extensions/pack/v24/config.json
@@ -0,0 +1,88 @@
+{
+ "timings": {
+ "scanDebounceMs": 300,
+ "cardExitMs": 280
+ },
+ "strings": {
+ "use": "Use",
+ "dismiss": "Dismiss",
+ "selectAccount": "Select account"
+ },
+ "stringsByLocale": {
+ "en": {
+ "use": "Use",
+ "dismiss": "Dismiss",
+ "selectAccount": "Select account",
+ "drawerTitle": "Settings",
+ "serverUrl": "Server URL",
+ "apiToken": "API token",
+ "tokenHint": "Create a token with read, reveal, write scopes on the website.",
+ "save": "Save",
+ "openApp": "Open gnexus-creds",
+ "openAppTitle": "Open gnexus-creds",
+ "refresh": "Refresh",
+ "settingsTitle": "Settings",
+ "searchPlaceholder": "Search secrets...",
+ "noSecrets": "No secrets",
+ "saveCredentials": "Save credentials for {source}?",
+ "account": "Account:",
+ "credentialsSaved": "Credentials saved",
+ "enterToken": "Enter API token",
+ "settingsSaved": "Settings saved",
+ "error": "Error",
+ "openOnSite": "Open on gnexus-creds",
+ "tokenInvalid": "The saved API token no longer works — open Settings and paste a new one.",
+ "tokenId": "Token ID"
+ },
+ "uk": {
+ "use": "Використати",
+ "dismiss": "Приховати",
+ "selectAccount": "Виберіть акаунт",
+ "drawerTitle": "Налаштування",
+ "serverUrl": "Адреса сервера",
+ "apiToken": "API-токен",
+ "tokenHint": "Створіть токен зі scopes read, reveal, write на сайті.",
+ "save": "Зберегти",
+ "openApp": "Відкрити gnexus-creds",
+ "openAppTitle": "Відкрити gnexus-creds",
+ "refresh": "Оновити",
+ "settingsTitle": "Налаштування",
+ "searchPlaceholder": "Пошук секретів...",
+ "noSecrets": "Немає секретів",
+ "saveCredentials": "Зберегти дані для {source}?",
+ "account": "Акаунт:",
+ "credentialsSaved": "Дані збережено",
+ "enterToken": "Вкажіть API-токен",
+ "settingsSaved": "Налаштування збережено",
+ "error": "Помилка",
+ "openOnSite": "Відкрити на gnexus-creds",
+ "tokenInvalid": "Збережений API-токен більше не працює — відкрийте налаштування та вставте новий.",
+ "tokenId": "ID токена"
+ },
+ "ru": {
+ "use": "Использовать",
+ "dismiss": "Скрыть",
+ "selectAccount": "Выберите аккаунт",
+ "drawerTitle": "Настройки",
+ "serverUrl": "URL сервера",
+ "apiToken": "API-токен",
+ "tokenHint": "Создайте токен со scopes read, reveal, write на сайте.",
+ "save": "Сохранить",
+ "openApp": "Открыть gnexus-creds",
+ "openAppTitle": "Открыть gnexus-creds",
+ "refresh": "Обновить",
+ "settingsTitle": "Настройки",
+ "searchPlaceholder": "Поиск секретов...",
+ "noSecrets": "Нет секретов",
+ "saveCredentials": "Сохранить данные для {source}?",
+ "account": "Аккаунт:",
+ "credentialsSaved": "Данные сохранены",
+ "enterToken": "Введите API-токен",
+ "settingsSaved": "Настройки сохранены",
+ "error": "Ошибка",
+ "openOnSite": "Открыть на gnexus-creds",
+ "tokenInvalid": "Сохранённый API-токен больше не работает — откройте настройки и вставьте новый.",
+ "tokenId": "ID токена"
+ }
+ }
+}
\ No newline at end of file
diff --git a/extensions/pack/v24/content.css b/extensions/pack/v24/content.css
new file mode 100644
index 0000000..e508290
--- /dev/null
+++ b/extensions/pack/v24/content.css
@@ -0,0 +1,116 @@
+/* Card styles for the closed Shadow DOM root (content.js injects this file's
+ text into the shadow root; it is never added to the page stylesheet, so the
+ host page can neither restyle the card nor see our class names).
+ Palette and geometry mirror gnexus-ui-kit 1.0 — keep in sync with lib/. */
+
+.gnexus-creds-autofill-container {
+ position: fixed;
+ top: 16px;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 2147483647;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ width: max-content;
+ max-width: calc(100% - 32px);
+ pointer-events: none;
+}
+
+.gnexus-creds-autofill-card {
+ pointer-events: auto;
+ background: #16161e;
+ border: 2px solid rgba(192, 202, 245, 0.24);
+ border-left: 6px solid #7aa2f7;
+ color: #c0caf5;
+ font-family: 'IBM Plex Mono', ui-monospace, monospace;
+ font-size: 14px;
+ border-radius: 6px;
+ box-shadow: 0 14px 36px rgba(22, 22, 30, 0.36);
+ padding: 15px 18px;
+ min-width: 300px;
+ max-width: 460px;
+ width: 100%;
+ opacity: 0;
+ transform: translateY(-12px);
+ transition: opacity 0.28s ease, transform 0.28s ease;
+}
+
+.gnexus-creds-autofill-card-visible {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+.gnexus-creds-autofill-card-exit {
+ opacity: 0;
+ transform: translateY(-12px);
+ transition: opacity 0.25s ease-in, transform 0.25s ease-in;
+}
+
+.gnexus-creds-autofill-title {
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ color: #7aa2f7;
+ line-height: 1.25;
+ word-break: break-word;
+ margin: 0 0 12px;
+ font-size: 14px;
+}
+
+.gnexus-creds-autofill-list {
+ max-height: 200px;
+ overflow-y: auto;
+ margin-bottom: 12px;
+}
+
+.gnexus-creds-autofill-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ padding: 10px 0;
+ border-bottom: 2px solid rgba(192, 202, 245, 0.08);
+}
+
+.gnexus-creds-autofill-row:last-child {
+ border-bottom: none;
+}
+
+.gnexus-creds-autofill-row-title {
+ font-size: 13px;
+ color: #c0caf5;
+ word-break: break-word;
+}
+
+.gnexus-creds-autofill-actions {
+ display: flex;
+ gap: 10px;
+ justify-content: flex-end;
+}
+
+.gnexus-creds-autofill-btn {
+ background: #16161e;
+ border: 2px solid rgba(192, 202, 245, 0.24);
+ border-left: 6px solid #7aa2f7;
+ color: #7aa2f7;
+ padding: 8px 16px;
+ border-radius: 6px;
+ font-family: inherit;
+ font-size: 13px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ cursor: pointer;
+ line-height: 1;
+ flex-shrink: 0;
+}
+
+.gnexus-creds-autofill-btn-secondary {
+ border-left-width: 2px;
+ color: #a9b1d6;
+}
+
+.gnexus-creds-autofill-btn:hover {
+ opacity: 0.85;
+}
\ No newline at end of file
diff --git a/extensions/pack/v24/popup.css b/extensions/pack/v24/popup.css
new file mode 100644
index 0000000..ecb886d
--- /dev/null
+++ b/extensions/pack/v24/popup.css
@@ -0,0 +1,22 @@
+body {
+ width: 420px;
+ min-height: 500px;
+}
+
+.page-header .page-header-content {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
+
+.page-header-logo {
+ width: 28px;
+ height: 28px;
+ margin-right: 8px;
+}
+
+.list-subtitle {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+}
diff --git a/extensions/pack/v24/tables.json b/extensions/pack/v24/tables.json
new file mode 100644
index 0000000..c22d391
--- /dev/null
+++ b/extensions/pack/v24/tables.json
@@ -0,0 +1,255 @@
+{
+ "usernameKeywords": [
+ [
+ "username",
+ 6
+ ],
+ [
+ "user_name",
+ 6
+ ],
+ [
+ "user_id",
+ 4
+ ],
+ [
+ "userid",
+ 4
+ ],
+ [
+ "login",
+ 5
+ ],
+ [
+ "log_in",
+ 3
+ ],
+ [
+ "logon",
+ 3
+ ],
+ [
+ "signin",
+ 2
+ ],
+ [
+ "email",
+ 5
+ ],
+ [
+ "e_mail",
+ 5
+ ],
+ [
+ "mail",
+ 3
+ ],
+ [
+ "correo",
+ 4
+ ],
+ [
+ "courriel",
+ 4
+ ],
+ [
+ "phone",
+ 4
+ ],
+ [
+ "tel",
+ 2
+ ],
+ [
+ "telefon",
+ 4
+ ],
+ [
+ "mobile",
+ 2
+ ],
+ [
+ "account",
+ 2
+ ],
+ [
+ "nick",
+ 3
+ ],
+ [
+ "handle",
+ 2
+ ],
+ [
+ "member",
+ 2
+ ],
+ [
+ "benutzer",
+ 5
+ ],
+ [
+ "benutzername",
+ 6
+ ],
+ [
+ "kennung",
+ 3
+ ],
+ [
+ "логин",
+ 6
+ ],
+ [
+ "пользователь",
+ 5
+ ],
+ [
+ "юзер",
+ 4
+ ],
+ [
+ "почта",
+ 5
+ ],
+ [
+ "телефон",
+ 4
+ ],
+ [
+ "usuario",
+ 5
+ ],
+ [
+ "utilisateur",
+ 4
+ ]
+ ],
+ "passwordKeywords": [
+ [
+ "password",
+ 8
+ ],
+ [
+ "passwd",
+ 8
+ ],
+ [
+ "pwd",
+ 8
+ ],
+ [
+ "pass",
+ 7
+ ],
+ [
+ "pw",
+ 5
+ ],
+ [
+ "passwort",
+ 8
+ ],
+ [
+ "motdepasse",
+ 8
+ ],
+ [
+ "mot_de_passe",
+ 8
+ ],
+ [
+ "contrasena",
+ 8
+ ],
+ [
+ "contrasenia",
+ 8
+ ],
+ [
+ "senha",
+ 8
+ ],
+ [
+ "haslo",
+ 6
+ ],
+ [
+ "geslo",
+ 6
+ ],
+ [
+ "пароль",
+ 8
+ ],
+ [
+ "пассворд",
+ 6
+ ]
+ ],
+ "antiKeywords": [
+ "cc",
+ "card",
+ "cvc",
+ "cvv",
+ "security_code",
+ "search",
+ "query",
+ "filter",
+ "coupon",
+ "promo",
+ "newsletter",
+ "subscribe",
+ "subject",
+ "message",
+ "comment",
+ "first_name",
+ "lastname",
+ "last_name",
+ "fname",
+ "lname",
+ "birth",
+ "dob",
+ "zip",
+ "postal",
+ "amount",
+ "price",
+ "total",
+ "qty",
+ "country",
+ "city",
+ "street",
+ "address",
+ "otp",
+ "one_time",
+ "onetime",
+ "one_time_code",
+ "verification",
+ "confirm",
+ "repeat",
+ "retype",
+ "again"
+ ],
+ "autocompleteTokens": {
+ "username": {
+ "username": 9,
+ "password": 0
+ },
+ "current_password": {
+ "username": 0,
+ "password": 9
+ },
+ "new_password": {
+ "username": 0,
+ "password": 7
+ },
+ "email": {
+ "username": 7,
+ "password": 0
+ },
+ "tel": {
+ "username": 3,
+ "password": 0
+ }
+ }
+}
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index 544daa1..d60ff0f 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -379,13 +379,19 @@
.map((token) => ({
key: token.public_id,
name: token.name,
+ public_id: token.public_id,
scopes: token.scopes,
+ created_at: formatTimestamp(token.created_at),
+ last_used_at: token.last_used_at ? formatTimestamp(token.last_used_at) : "—",
raw: token
}))
);
const tokenColumns = computed(() => [
{ key: "name", label: t("col.name") },
+ { key: "public_id", label: t("col.publicId") },
{ key: "scopes", label: t("col.scopes") },
+ { key: "created_at", label: t("col.created") },
+ { key: "last_used_at", label: t("col.lastUsed") },
{ key: "actions", label: "" }
]);
const diagnosticsItems = computed(() => [
@@ -1471,6 +1477,9 @@
{{ row.name }}
+
+ {{ row.public_id }}
+
{{ row.scopes.join(", ") }}
diff --git a/frontend/src/i18n/messages/en.js b/frontend/src/i18n/messages/en.js
index 47d7019..194ff19 100644
--- a/frontend/src/i18n/messages/en.js
+++ b/frontend/src/i18n/messages/en.js
@@ -70,6 +70,8 @@
"col.file": "File",
"col.size": "Size",
"col.created": "Created",
+ "col.publicId": "Public ID",
+ "col.lastUsed": "Last used",
// detail panel (SecretDetailPanel.vue)
"detail.purpose": "Purpose",
diff --git a/frontend/src/i18n/messages/ru.js b/frontend/src/i18n/messages/ru.js
index 1c284b3..60de70a 100644
--- a/frontend/src/i18n/messages/ru.js
+++ b/frontend/src/i18n/messages/ru.js
@@ -63,6 +63,8 @@
"col.file": "Файл",
"col.size": "Размер",
"col.created": "Создан",
+ "col.publicId": "Публичный ID",
+ "col.lastUsed": "Последнее использование",
"detail.purpose": "Назначение",
"detail.category": "Категория",
diff --git a/frontend/src/i18n/messages/uk.js b/frontend/src/i18n/messages/uk.js
index 5200464..dc3e365 100644
--- a/frontend/src/i18n/messages/uk.js
+++ b/frontend/src/i18n/messages/uk.js
@@ -63,6 +63,8 @@
"col.file": "Файл",
"col.size": "Розмір",
"col.created": "Створено",
+ "col.publicId": "Публічний ID",
+ "col.lastUsed": "Останнє використання",
"detail.purpose": "Призначення",
"detail.category": "Категорія",