Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions e2e/wallpaper.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

const { test, expect } = require('@playwright/test');
const { seedConfig, readConfig, dismissSetupPrompt, setInlineRow } = require('./helpers');
const { BASE_URL } = require('./base-url');

const WALLPAPER = { type: 'url', url: '/icons/wallpaper/none.png', brightness: 1, fit: 'fill' };

Expand Down Expand Up @@ -101,3 +102,50 @@ test('a failed wallpaper link goes back to the last saved wallpaper, not the one
await expect(page.locator('#toast')).toHaveClass(/\berr\b/);
await expect(page.locator('#ie-bgurl-v')).toContainText('saved-later.png');
});

test.describe('Unsplash key', () => {
const UNSPLASH = { type: 'unsplash', brightness: 0.62, collection: '' };

test.beforeEach(async ({ page, request }) => {
await seedConfig(request, { items: [], settings: { background: { ...UNSPLASH } } });
const res = await request.post('/api/settings/unsplash-key', {
data: { apiKey: 'stored-key' },
headers: { Origin: BASE_URL },
});
expect(res.ok()).toBeTruthy();
await page.route('**/api/wallpaper', route => route.fulfill({ json: { url: null } }));
await page.goto('/admin/');
await page.locator('.nl[data-sec="appearance"]').click();
await expect(page.locator('#ie-apikey-v')).toHaveText('Configured');
});

test('opening the field and saving does not send a key', async ({ page }) => {
const sent = [];
page.on('request', r => {
if (r.url().includes('/api/settings/unsplash-key') && r.method() === 'POST') sent.push(r.postData());
});
await page.locator('#ie-apikey .rv').click();
const input = page.locator('#bg-apikey-inp');
await expect(input).toHaveValue('');
await expect(input).toHaveAttribute('type', 'password');
await page.locator('#ie-apikey .rl').click();
await expect(page.locator('#ie-apikey-v')).toHaveText('Configured');

await setInlineRow(page, 'ie-bgcol', 'bg-col-inp', 'AGVpqBZnzUE');
const saved = page.waitForResponse(r => r.url().includes('/api/config') && r.request().method() === 'POST');
await page.locator('#bg-save').click();
expect((await saved).ok()).toBeTruthy();
await expect(page.locator('#toast')).not.toHaveClass(/\berr\b/);
expect(sent).toEqual([]);
});

test('a typed key is sent once and the field is emptied', async ({ page }) => {
await setInlineRow(page, 'ie-apikey', 'bg-apikey-inp', 'new-key');
await expect(page.locator('#ie-apikey-v')).toHaveText('New value set');
const posted = page.waitForRequest(r => r.url().includes('/api/settings/unsplash-key') && r.method() === 'POST');
await page.locator('#bg-save').click();
expect(JSON.parse((await posted).postData() || '{}')).toEqual({ apiKey: 'new-key' });
await expect(page.locator('#ie-apikey-v')).toHaveText('Configured');
await expect(page.locator('#bg-apikey-inp')).toHaveValue('');
});
});
3 changes: 1 addition & 2 deletions ui/admin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,6 @@ <h1 class="sec-title" id="ev-title">General</h1>
<input id="srv-socket" type="text" class="d-none" autocomplete="off">
<input id="srv-hosts" type="text" class="d-none" autocomplete="off">
<input id="sec-pw" type="password" class="d-none" autocomplete="new-password">
<input id="bg-apikey" type="text" class="d-none" autocomplete="off">
<input id="bg-col" type="text" class="d-none" autocomplete="off">
<input id="bg-url" type="url" class="d-none" autocomplete="off">
<div id="toast" role="status" aria-live="polite"></div>
Expand Down Expand Up @@ -301,6 +300,6 @@ <h1 class="sec-title" id="ev-title">General</h1>
</div></div>
</div>

<script type="module" src="/js/admin.js?v=6a405722"></script>
<script type="module" src="/js/admin.js?v=bb2719cd"></script>
</body>
</html>
59 changes: 39 additions & 20 deletions ui/js/admin-settings.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { toast, apiGet, apiPost, errorText, reveal, swapContent } from '/js/admin-shared.js?v=36dc5cc1';
import { toast, apiGet, apiPost, errorText, reveal, swapContent, wireInlineEdit } from '/js/admin-shared.js?v=36dc5cc1';
import { socketProbeAdvice } from '/js/admin-error.js?v=b0881957';
import { pwStrength } from '/js/password-strength.js?v=389e0ed0';
import { t } from '/js/i18n.js?v=ed7c5565';
Expand Down Expand Up @@ -33,6 +33,7 @@ let _srvTrack = null;
/** @type {{ dirty: () => boolean, reset: (force?: boolean) => void } | null} */
let _bgTrack = null;
let _savedWallpaperUrl = '';
let _unsplashKeySet = false;

/** The wallpaper link the server holds, for undoing a link that failed. */
export const savedWallpaperUrl = () => _savedWallpaperUrl;
Expand Down Expand Up @@ -71,7 +72,7 @@ const readWallpaperForm = () =>
_val('bg-url-inp', 'bg-url'),
_val('bg-fit'),
_val('bg-color-val'),
_val('bg-apikey-inp', 'bg-apikey'),
_val('bg-apikey-inp'),
]);

/** Disables `buttonId` while its section matches what was last saved. */
Expand Down Expand Up @@ -170,22 +171,9 @@ export function loadSettings(c) {
if (llEl) llEl.value = s.logLevel || 'info';
const langEl = inp('lang-sel');
if (langEl) langEl.value = s.language || 'en';
/* The key itself is never included in /api/config. */
const apiEl = inp('bg-apikey-inp') || inp('bg-apikey');
if (apiEl) {
apiEl.placeholder = `●●●●●●●●●● (${t('common.configured')})`;
apiGet('/api/settings/unsplash-key')
.then(d => {
const vEl = el('ie-apikey-v');
if (!d.configured) {
apiEl.placeholder = t('appearance.unsplashKeyPh');
if (vEl) vEl.textContent = t('common.notSet');
} else {
if (vEl) vEl.textContent = t('common.configured');
}
})
.catch(() => {});
}
apiGet('/api/settings/unsplash-key')
.then(d => showUnsplashKeyState(!!d.configured))
.catch(() => {});
const colEl = inp('bg-col');
if (colEl) colEl.value = bg.collection || '';
const urlEl = inp('bg-url');
Expand Down Expand Up @@ -333,6 +321,32 @@ export function showWallpaperFile(url) {
}
}

const unsplashKeyLabel = () => t(_unsplashKeySet ? 'common.configured' : 'common.notSet');

function showUnsplashKeyState(set) {
_unsplashKeySet = set;
const vEl = el('ie-apikey-v');
if (vEl) vEl.textContent = unsplashKeyLabel();
}

/** The field opens empty. A blank field keeps the stored key. */
export function wireUnsplashKey() {
const keyInp = document.createElement('input');
keyInp.id = 'bg-apikey-inp';
keyInp.autocomplete = 'new-password';
document.body.appendChild(keyInp);
const row = el('ie-apikey');
if (!row) return;
wireInlineEdit(row, keyInp, {
type: 'password',
placeholder: () => t(_unsplashKeySet ? 'widgetCfg.replaceSecret' : 'appearance.unsplashKeyPh'),
fill: false,
render: (valEl, v) => {
valEl.textContent = v ? t('widgetCfg.newValueSet') : unsplashKeyLabel();
},
});
}

export function showBgFields(type) {
const host = el('bg-unsplash-fields')?.parentElement;
swapContent(host, () => {
Expand Down Expand Up @@ -408,8 +422,13 @@ async function saveWallpaper() {
/* After the main config. GET /api/config strips the key, so a config write
that follows would overwrite it with nothing. */
if (type === 'unsplash') {
const keyVal = (inp('bg-apikey-inp') || inp('bg-apikey'))?.value?.trim() || '';
if (keyVal) await apiPost('/api/settings/unsplash-key', { apiKey: keyVal });
const keyEl = inp('bg-apikey-inp');
const keyVal = keyEl?.value?.trim() || '';
if (keyEl && keyVal) {
await apiPost('/api/settings/unsplash-key', { apiKey: keyVal });
keyEl.value = '';
showUnsplashKeyState(true);
}
}
_bgTrack?.reset();
toast(t('toast.saved'));
Expand Down
8 changes: 3 additions & 5 deletions ui/js/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import {
settingsDirty,
showBgFields,
showWallpaperFile,
} from '/js/admin-settings.js?v=65546081';
wireUnsplashKey,
} from '/js/admin-settings.js?v=5d039019';
import {
apiGet,
apiPost,
Expand Down Expand Up @@ -789,10 +790,7 @@ function initAllInlineEdits() {
);
}

const apiInp = document.createElement('input');
apiInp.id = 'bg-apikey-inp';
document.body.appendChild(apiInp);
initInlineEdit('ie-apikey', 'bg-apikey-inp', { placeholder: () => t('appearance.unsplashKeyPh') });
wireUnsplashKey();

const colInp = document.createElement('input');
colInp.id = 'bg-col-inp';
Expand Down
Loading