Settings page of pluggin inproved

This commit is contained in:
Olaf
2026-08-25 08:03:44 +02:00
parent 315a89380f
commit f6077f0063
6 changed files with 86 additions and 1 deletions
+2
View File
@@ -128,6 +128,8 @@ The manifest includes stable Firefox extension metadata and references the packa
6. Save the settings and login. 6. Save the settings and login.
7. Open a webpage, select the LinkLog toolbar button, review the title and URL, add a comment, and submit it. 7. Open a webpage, select the LinkLog toolbar button, review the title and URL, add a comment, and submit it.
When the extension settings page has a valid session, it shows `<username> logged in at <backend URL>` and a **Sign out** button instead of the login form. Signing out revokes the token and returns the form.
Temporary extensions are removed when Firefox restarts. Reload the extension from `about:debugging` after changing its files. Temporary extensions are removed when Firefox restarts. Reload the extension from `about:debugging` after changing its files.
## Docker ## Docker
+6
View File
@@ -433,3 +433,9 @@ How come when te container starts after docker compose down -v the database is s
### Assistant outcome ### Assistant outcome
Inspected the active container and confirmed it uses `/app/backend/data/linklog.db` from the Compose volume `linklog_linklog_data`, containing 230 links. The current Compose project maps logical `linklog_data` to that prefixed volume. `docker compose down` preserves it; only `docker compose down -v` from the same project removes it. A different directory/project, an external volume, or stopping without `-v` can therefore preserve the data. Inspected the active container and confirmed it uses `/app/backend/data/linklog.db` from the Compose volume `linklog_linklog_data`, containing 230 links. The current Compose project maps logical `linklog_data` to that prefixed volume. `docker compose down` preserves it; only `docker compose down -v` from the same project removes it. A different directory/project, an external volume, or stopping without `-v` can therefore preserve the data.
### User
Refine the Plugins Settings page. When the user is logged in it shows all "<user> logged in at <url>" and a sign-out button. If the user is not logged in it shows the form as is now.
### Assistant outcome
Updated the Firefox extension settings page to restore valid stored sessions, show `<username> logged in at <backend URL>` with a sign-out button, and show the original login form when signed out or when token validation fails. Sign-out revokes the token when possible, clears stored credentials, and returns to the form. Extension and backend validation passed.
+1
View File
@@ -75,6 +75,7 @@
71. Create a make file that will regenerate the logo and icon files from LinkLog.svg 71. Create a make file that will regenerate the logo and icon files from LinkLog.svg
72. Make sure the addon conforms to Firefox guidelines 72. Make sure the addon conforms to Firefox guidelines
73. How come when te container starts after docker compose down -v the database is still populated with old links 73. How come when te container starts after docker compose down -v the database is still populated with old links
74. Refine the Plugins Settings page. When the user is logged in it shows all "<user> logged in at <url>" and a sign-out button. If the user is not logged in it shows the form as is now.
## Future entries ## Future entries
+19
View File
@@ -75,6 +75,25 @@ button {
color: #991b1b; color: #991b1b;
} }
.logged-in {
margin-bottom: 16px;
padding: 14px;
border: 1px solid #45475a;
border-radius: 8px;
background: #313244;
color: #cdd6f4;
}
.logged-in p {
margin: 0;
overflow-wrap: anywhere;
}
.logged-in button {
background: #f38ba8;
color: #11111b;
}
.hidden { .hidden {
display: none; display: none;
} }
+5
View File
@@ -12,6 +12,11 @@
<div id="status" class="status hidden" aria-live="polite"></div> <div id="status" class="status hidden" aria-live="polite"></div>
<section id="logged-in" class="logged-in hidden" aria-live="polite">
<p id="session-summary"></p>
<button type="button" id="sign-out">Sign out</button>
</section>
<form id="settings-form"> <form id="settings-form">
<label> <label>
Backend URL Backend URL
+53 -1
View File
@@ -5,6 +5,9 @@ const form = document.getElementById('settings-form');
const backendUrlInput = document.getElementById('backend-url'); const backendUrlInput = document.getElementById('backend-url');
const usernameInput = document.getElementById('username'); const usernameInput = document.getElementById('username');
const passwordInput = document.getElementById('password'); const passwordInput = document.getElementById('password');
const session = document.getElementById('logged-in');
const sessionSummary = document.getElementById('session-summary');
const signOutButton = document.getElementById('sign-out');
function setStatus(message, isError = false) { function setStatus(message, isError = false) {
statusEl.textContent = message; statusEl.textContent = message;
@@ -14,9 +17,41 @@ function setStatus(message, isError = false) {
} }
async function loadSettings() { async function loadSettings() {
const settings = await browser.storage.local.get(['backendUrl', 'username']); const settings = await browser.storage.local.get(['backendUrl', 'username', 'accessToken']);
backendUrlInput.value = settings.backendUrl || DEFAULT_BACKEND; backendUrlInput.value = settings.backendUrl || DEFAULT_BACKEND;
usernameInput.value = settings.username || ''; usernameInput.value = settings.username || '';
if (settings.accessToken && settings.backendUrl) {
try {
const response = await fetch(
`${settings.backendUrl}/api/auth/me?token=${encodeURIComponent(settings.accessToken)}`
);
if (response.ok) {
const user = await response.json();
showLoggedIn(user.username || settings.username, settings.backendUrl);
return;
}
} catch (error) {
// Show the login form when the backend cannot validate the stored token.
}
await clearSession();
}
showLoggedOut();
}
function showLoggedIn(username, backendUrl) {
sessionSummary.textContent = `${username} logged in at ${backendUrl}`;
session.classList.remove('hidden');
form.classList.add('hidden');
}
function showLoggedOut() {
session.classList.add('hidden');
form.classList.remove('hidden');
}
async function clearSession() {
await browser.storage.local.remove(['accessToken', 'tokenType', 'tokenExpiresAt', 'refreshToken']);
} }
async function saveSettingsAndLogin(event) { async function saveSettingsAndLogin(event) {
@@ -51,11 +86,28 @@ async function saveSettingsAndLogin(event) {
refreshToken: data.refresh_token, refreshToken: data.refresh_token,
}); });
showLoggedIn(data.user?.username || username, backendUrl);
passwordInput.value = '';
setStatus('Logged in successfully'); setStatus('Logged in successfully');
} catch (error) { } catch (error) {
setStatus('Unable to log in. Check backend URL and credentials.', true); setStatus('Unable to log in. Check backend URL and credentials.', true);
} }
} }
async function signOut() {
const settings = await browser.storage.local.get(['accessToken']);
if (settings.accessToken) {
await fetch(`${backendUrlInput.value.trim()}/api/auth/logout`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: settings.accessToken }),
}).catch(() => undefined);
}
await clearSession();
showLoggedOut();
setStatus('Signed out');
}
form.addEventListener('submit', saveSettingsAndLogin); form.addEventListener('submit', saveSettingsAndLogin);
signOutButton.addEventListener('click', signOut);
loadSettings(); loadSettings();