updated: app/data-service.js, app/styles.css, app/ui-settings.js, index.html

This commit is contained in:
2026-05-28 23:52:29 -07:00
parent 1433ec1495
commit ed1107a0c0
4 changed files with 174 additions and 2 deletions
+8 -2
View File
@@ -210,7 +210,7 @@
}
function toApiAssetUrl(assetPath) {
const apiBaseUrl = getApiBaseUrl();
const { apiBaseUrl, apiKey } = resolveConnectionSettings();
const normalizedAssetPath = String(assetPath || "")
.trim()
.replace(/^\/+/, "")
@@ -220,7 +220,12 @@
return "";
}
return new URL(`/api/v1/assets/${encodePathSegments(normalizedAssetPath)}`, `${apiBaseUrl}/`).toString();
const url = new URL(`/api/v1/assets/${encodePathSegments(normalizedAssetPath)}`, `${apiBaseUrl}/`);
if (apiKey) {
url.searchParams.set("apiKey", apiKey);
}
return url.toString();
}
function resetCaches() {
@@ -635,6 +640,7 @@
reason: "connected",
message: "Connected.",
health,
auth: health?.auth || null,
deckCount: Array.isArray(decksPayload?.decks) ? decksPayload.decks.length : null
};
} catch (_error) {
+40
View File
@@ -567,6 +567,46 @@
font-size: 12px;
line-height: 1.4;
}
.settings-connection-summary {
margin-top: 16px;
padding: 14px 16px;
border-radius: 18px;
border: 1px solid rgba(63, 63, 70, 0.9);
background: rgba(9, 9, 11, 0.68);
display: grid;
gap: 10px;
}
.settings-connection-summary[data-tone="success"] {
border-color: rgba(34, 197, 94, 0.45);
background: rgba(20, 83, 45, 0.16);
}
.settings-connection-summary[data-tone="warning"] {
border-color: rgba(245, 158, 11, 0.45);
background: rgba(120, 53, 15, 0.16);
}
.settings-connection-summary[data-tone="error"] {
border-color: rgba(239, 68, 68, 0.45);
background: rgba(127, 29, 29, 0.18);
}
.settings-connection-summary-row {
display: grid;
grid-template-columns: 84px minmax(0, 1fr);
gap: 12px;
align-items: start;
}
.settings-connection-summary-label {
color: #94a3b8;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.settings-connection-summary-value {
color: #f8fafc;
font-size: 13px;
line-height: 1.5;
word-break: break-word;
}
.settings-cache-progress-wrap {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
+108
View File
@@ -24,6 +24,8 @@
onRenderWeek: null
};
let lastConnectionProbeResult = null;
let lastNonSettingsSection = "home";
function getElements() {
@@ -42,6 +44,11 @@
stellariumBackgroundHintEl: document.getElementById("stellarium-background-hint"),
apiBaseUrlEl: document.getElementById("api-base-url"),
apiKeyEl: document.getElementById("api-key"),
apiConnectionSummaryEl: document.getElementById("api-connection-summary"),
apiConnectionSummaryStateEl: document.getElementById("api-connection-summary-state"),
apiConnectionSummaryClientEl: document.getElementById("api-connection-summary-client"),
apiConnectionSummaryAccessEl: document.getElementById("api-connection-summary-access"),
apiConnectionSummaryPermissionsEl: document.getElementById("api-connection-summary-permissions"),
settingsPageStatusEl: document.getElementById("settings-page-status"),
settingsPageStatusTextEl: document.getElementById("settings-page-status-text"),
settingsPageStatusTimeEl: document.getElementById("settings-page-status-time"),
@@ -192,6 +199,98 @@
|| String(previous?.apiKey || "").trim() !== String(next?.apiKey || "").trim();
}
function normalizeConnectionValues(values) {
return Array.isArray(values)
? values.map((entry) => String(entry || "").trim()).filter(Boolean)
: [];
}
function hasAdminCapability(auth) {
const roles = normalizeConnectionValues(auth?.roles);
const scopes = normalizeConnectionValues(auth?.scopes);
return roles.includes("admin") || scopes.includes("api:admin");
}
function formatConnectionValues(values, fallback = "none") {
const normalized = normalizeConnectionValues(values);
return normalized.length ? normalized.join(", ") : fallback;
}
function setConnectionSummary(result = null) {
const {
apiConnectionSummaryEl,
apiConnectionSummaryStateEl,
apiConnectionSummaryClientEl,
apiConnectionSummaryAccessEl,
apiConnectionSummaryPermissionsEl
} = getElements();
if (!apiConnectionSummaryEl) {
return;
}
if (!result) {
apiConnectionSummaryEl.dataset.tone = "neutral";
if (apiConnectionSummaryStateEl) {
apiConnectionSummaryStateEl.textContent = "Not checked yet.";
}
if (apiConnectionSummaryClientEl) {
apiConnectionSummaryClientEl.textContent = "No authenticated API identity.";
}
if (apiConnectionSummaryAccessEl) {
apiConnectionSummaryAccessEl.textContent = "Unknown";
}
if (apiConnectionSummaryPermissionsEl) {
apiConnectionSummaryPermissionsEl.textContent = "Save settings to validate this API key.";
}
return;
}
const tone = result.ok ? "success" : (result.reason === "auth-required" ? "warning" : "error");
const auth = result.auth || result.health?.auth || {};
const roles = normalizeConnectionValues(auth.roles);
const scopes = normalizeConnectionValues(auth.scopes);
const authenticated = auth.authenticated === true;
const accessValue = authenticated
? `${String(auth.accessLevel || "premium").trim() || "premium"}${hasAdminCapability(auth) ? " - admin capable" : ""}`
: (result.health?.apiKeyRequired ? "API key required" : "public");
const permissionsValue = authenticated
? `roles: ${formatConnectionValues(roles)} | scopes: ${formatConnectionValues(scopes)}`
: (result.message || "Unable to validate the API connection.");
apiConnectionSummaryEl.dataset.tone = tone;
if (apiConnectionSummaryStateEl) {
apiConnectionSummaryStateEl.textContent = result.ok
? `Connected${Number.isInteger(result.deckCount) ? `${result.deckCount} deck${result.deckCount === 1 ? "" : "s"}` : ""}`
: String(result.message || "Unable to validate the API connection.");
}
if (apiConnectionSummaryClientEl) {
apiConnectionSummaryClientEl.textContent = authenticated
? [String(auth.clientId || "").trim(), String(auth.accountId || "").trim()].filter(Boolean).join(" / ") || "Authenticated client"
: (result.health?.apiKeyRequired ? "No valid API identity returned." : "Public access");
}
if (apiConnectionSummaryAccessEl) {
apiConnectionSummaryAccessEl.textContent = accessValue;
}
if (apiConnectionSummaryPermissionsEl) {
apiConnectionSummaryPermissionsEl.textContent = permissionsValue;
}
}
async function refreshConnectionSummary(connectionSettings = getConnectionSettings(), { probeResult = null } = {}) {
const apiBaseUrl = String(connectionSettings?.apiBaseUrl || "").trim();
if (!apiBaseUrl) {
lastConnectionProbeResult = null;
setConnectionSummary(null);
return null;
}
const result = probeResult || await window.TarotDataService?.probeConnection?.(connectionSettings) || null;
lastConnectionProbeResult = result;
setConnectionSummary(result);
return result;
}
function setStatus(text) {
if (typeof config.onStatus === "function") {
@@ -571,6 +670,9 @@
}
applySettingsToInputs(loadSavedSettings());
syncSavedSettingsStatus();
void refreshConnectionSummary(getConnectionSettings(), {
probeResult: lastConnectionProbeResult
});
config.setActiveSection?.("settings");
}
@@ -595,8 +697,12 @@
const probeResult = await window.TarotDataService?.probeConnection?.(connectionSettings);
if (!probeResult?.ok) {
setConnectionSummary(probeResult);
throw new Error(probeResult?.message || "Unable to validate the API connection.");
}
lastConnectionProbeResult = probeResult;
setConnectionSummary(probeResult);
}
const connectionResult = window.TarotAppConfig?.updateConnectionSettings?.(connectionSettings) || { didPersist: true };
@@ -738,6 +844,7 @@
syncConnectionInputs();
syncTarotDeckInputOptions();
syncDeckCacheStatus(window.TarotCardImages?.getDeckPreloadStatus?.());
void refreshConnectionSummary(getConnectionSettings());
});
document.addEventListener("tarot:deck-cache-status", (event) => {
@@ -756,6 +863,7 @@
};
syncSavedSettingsStatus();
setConnectionSummary(lastConnectionProbeResult);
bindInteractions();
}
+18
View File
@@ -179,6 +179,24 @@
<input id="api-key" type="password" autocomplete="off" placeholder="Optional unless the API requires one">
</label>
</div>
<div id="api-connection-summary" class="settings-connection-summary" data-tone="neutral" aria-live="polite">
<div class="settings-connection-summary-row">
<span class="settings-connection-summary-label">Status</span>
<span id="api-connection-summary-state" class="settings-connection-summary-value">Not checked yet.</span>
</div>
<div class="settings-connection-summary-row">
<span class="settings-connection-summary-label">Client</span>
<span id="api-connection-summary-client" class="settings-connection-summary-value">No authenticated API identity.</span>
</div>
<div class="settings-connection-summary-row">
<span class="settings-connection-summary-label">Access</span>
<span id="api-connection-summary-access" class="settings-connection-summary-value">Unknown</span>
</div>
<div class="settings-connection-summary-row">
<span class="settings-connection-summary-label">Permissions</span>
<span id="api-connection-summary-permissions" class="settings-connection-summary-value">Save settings to validate this API key.</span>
</div>
</div>
</section>
</div>
<div class="settings-actions settings-page-actions">