summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/group-settings.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/group-settings.js106
1 files changed, 106 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js
index 88ab68e..03162d9 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js
@@ -34,6 +34,7 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef,
enabledApps, onEnabledApps,
scanSettings, onScanSettings,
tmdbConfig, onTmdbConfig, onTmdbEnabled,
+ musicbrainzConfig, onMusicbrainzConfig, onMusicbrainzEnabled,
entries, nodeDirs, videoRoot, onVideoRoot,
onPaired, onLeft }) {
const [members, setMembers] = useState([]);
@@ -358,6 +359,74 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef,
saveTmdbConfig();
}, [isNodeAdmin, connected, tmdbConfig, saveTmdbConfig]);
+ const [mbBusy, setMbBusy] = useState(false);
+ const [mbMsg, setMbMsg] = useState('');
+ const [mbContactDraft, setMbContactDraft] = useState('');
+ const [mbEnabledBusy, setMbEnabledBusy] = useState(false);
+ const mbEnabled = musicbrainzConfig ? musicbrainzConfig.enabled : true;
+
+ /**
+ * Whether MusicBrainz is used at all — per-group from the start
+ * (docs/musicbay.md §3.2/§6). Same shape as saveTmdbEnabled.
+ */
+ const saveMusicbrainzEnabled = useCallback(async (nextEnabled) => {
+ const transport = transportRef && transportRef.current;
+ setMbMsg('');
+ setMbEnabledBusy(true);
+ try {
+ if (!transport || !transport.connected) {
+ throw new Error('Not connected to the node');
+ }
+ const sk = transport.sessionKeys && transport.sessionKeys.skEdB64;
+ const signFn = (sk && window.MeshBayKeys)
+ ? (transcript) => window.MeshBayKeys.signBytes(sk, transcript)
+ : null;
+ await transport.setMusicbrainzEnabled(nextEnabled, signFn);
+ if (onMusicbrainzEnabled) onMusicbrainzEnabled(nextEnabled);
+ } catch (err) {
+ setMbMsg(err.message);
+ } finally {
+ setMbEnabledBusy(false);
+ }
+ }, [transportRef, onMusicbrainzEnabled]);
+
+ /**
+ * The node-wide MusicBrainz contact string (docs/musicbay.md §3.2) — not
+ * a secret, unlike TMDB's token, but still cleared from the draft field
+ * after a save: the node never echoes it back
+ * (musicbrainz_config_ack carries only whether one is set), so there is
+ * nothing to keep showing.
+ */
+ const saveMusicbrainzConfig = useCallback(async () => {
+ const transport = transportRef && transportRef.current;
+ setMbMsg('');
+ setMbBusy(true);
+ try {
+ if (!transport || !transport.connected) {
+ throw new Error('Not connected to the node');
+ }
+ const sk = transport.sessionKeys && transport.sessionKeys.skEdB64;
+ const signFn = (sk && window.MeshBayKeys)
+ ? (transcript) => window.MeshBayKeys.signBytes(sk, transcript)
+ : null;
+ const contact = mbContactDraft.trim();
+ await transport.setMusicbrainzConfig(contact || undefined, signFn);
+ setMbContactDraft('');
+ if (onMusicbrainzConfig) {
+ onMusicbrainzConfig({
+ contactConfigured: contact
+ ? true
+ : (musicbrainzConfig ? musicbrainzConfig.contactConfigured : false),
+ });
+ }
+ setMbMsg(t('settings_node.scan_saved'));
+ } catch (err) {
+ setMbMsg(err.message);
+ } finally {
+ setMbBusy(false);
+ }
+ }, [transportRef, onMusicbrainzConfig, mbContactDraft, musicbrainzConfig]);
+
// Every folder anywhere in the group's shared index, deepest included —
// `entries[].path` is each file's containing directory (files-app.js's own
// convention), so every ancestor prefix of it is a real folder, and
@@ -688,6 +757,43 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef,
</div>
`}
+ ${/* Same two-part shape as TMDB above: the on/off switch is per-group,
+ the contact string stays node-wide (docs/musicbay.md §3.2) —
+ one operator identity, not a per-group concern. Unlike TMDB
+ there is no token field: MusicBrainz's read endpoints need no
+ credential, just a descriptive User-Agent contact. */
+ isNodeAdmin && connected && html`
+ <div class="settings-section">
+ <h3 class="settings-heading">${t('settings_node.musicbrainz_title')}</h3>
+ <p class="settings-hint">${t('settings_node.musicbrainz_hint')}</p>
+ <div class="settings-row">
+ <label class="settings-label">
+ <input type="checkbox" checked=${mbEnabled} disabled=${mbEnabledBusy}
+ onChange=${(e) => saveMusicbrainzEnabled(e.target.checked)} />
+ ${' '}${mbEnabled ? t('settings_node.musicbrainz_enabled') : t('settings_node.musicbrainz_disabled')}
+ </label>
+ </div>
+ <div class="settings-row">
+ <label class="settings-label">
+ ${t('settings_node.musicbrainz_contact_label')}
+ <input type="text" placeholder=${t('settings_node.musicbrainz_contact_placeholder')}
+ value=${mbContactDraft} disabled=${mbBusy}
+ onInput=${e => setMbContactDraft(e.target.value)} />
+ </label>
+ <p class="settings-hint">
+ ${musicbrainzConfig && musicbrainzConfig.contactConfigured
+ ? t('settings_node.musicbrainz_contact_set')
+ : t('settings_node.musicbrainz_contact_unset')}
+ </p>
+ </div>
+ <button class="btn btn-small btn-secondary" style="margin-top:8px"
+ disabled=${mbBusy} onClick=${() => saveMusicbrainzConfig()}>
+ ${mbBusy ? t('settings_node.scan_saving') : t('settings_node.musicbrainz_save')}
+ </button>
+ ${mbMsg && html`<p class="settings-hint">${mbMsg}</p>`}
+ </div>
+ `}
+
${/* Which folder is the Videos app's entry point for this group —
per-group like uploads, not node-wide like TMDB (mediacenter.md
§5.6). Until one is chosen, the Videos tab says so instead of