updated: app/data-service.js, app/styles.css, app/ui-settings.js, index.html
This commit is contained in:
+8
-2
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user