### Fixed

#### JWT Authentication

- Now we can use JWT Authentication ES512 correctly (#742).
- Several misdirections in the Setting dialogues have been fixed (i.e., seconds and minutes confusion...).
- The key area in the Setting dialogue has been enlarged and accepts newlines correctly.
- Caching of JWT tokens now works correctly
    - Tokens are now cached and reused until they expire.
    - They will be kept until 10% of the expiration duration is remaining or 10 seconds, whichever is longer (but at a maximum of 1 minute).
- JWT settings are now correctly displayed on the Setting dialogue.

#### Other fixes

- Receiving non-latest revisions no longer causes unexpected overwrites.
    - On receiving revisions that made conflicting changes, we are still able to handle them.

### Improved

- No longer duplicated message notifications are shown when a connection to the remote server fails.
    - Instead, a single notification is shown, and it will be kept on the notification area inside the editor until the situation is resolved.
- The notification area is no longer imposing, distracting, and overwhelming.
    - With a pale background, but bordered and with icons.
This commit is contained in:
vorotamoroz
2025-11-06 09:24:16 +00:00
parent a5b88a8d47
commit c4f2baef5e
8 changed files with 156 additions and 26 deletions

View File

@@ -21,6 +21,7 @@ export const EVENT_REQUEST_CLOSE_P2P = "request-close-p2p";
export const EVENT_REQUEST_RUN_DOCTOR = "request-run-doctor"; export const EVENT_REQUEST_RUN_DOCTOR = "request-run-doctor";
export const EVENT_REQUEST_RUN_FIX_INCOMPLETE = "request-run-fix-incomplete"; export const EVENT_REQUEST_RUN_FIX_INCOMPLETE = "request-run-fix-incomplete";
export const EVENT_ON_UNRESOLVED_ERROR = "on-unresolved-error";
// export const EVENT_FILE_CHANGED = "file-changed"; // export const EVENT_FILE_CHANGED = "file-changed";
@@ -40,6 +41,7 @@ declare global {
[EVENT_REQUEST_SHOW_SETUP_QR]: undefined; [EVENT_REQUEST_SHOW_SETUP_QR]: undefined;
[EVENT_REQUEST_RUN_DOCTOR]: string; [EVENT_REQUEST_RUN_DOCTOR]: string;
[EVENT_REQUEST_RUN_FIX_INCOMPLETE]: undefined; [EVENT_REQUEST_RUN_FIX_INCOMPLETE]: undefined;
[EVENT_ON_UNRESOLVED_ERROR]: undefined;
} }
} }

Submodule src/lib updated: 08b43da7fb...e7197a38d8

View File

@@ -1,7 +1,15 @@
import { fireAndForget, yieldMicrotask } from "octagonal-wheels/promises"; import { fireAndForget, yieldMicrotask } from "octagonal-wheels/promises";
import type { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB"; import type { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB";
import { AbstractModule } from "../AbstractModule"; import { AbstractModule } from "../AbstractModule";
import { Logger, LOG_LEVEL_NOTICE, LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger"; import {
Logger,
LOG_LEVEL_NOTICE,
LOG_LEVEL_INFO,
LOG_LEVEL_VERBOSE,
LEVEL_NOTICE,
LEVEL_INFO,
type LOG_LEVEL,
} from "octagonal-wheels/common/logger";
import { isLockAcquired, shareRunningResult, skipIfDuplicated } from "octagonal-wheels/concurrency/lock"; import { isLockAcquired, shareRunningResult, skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
import { balanceChunkPurgedDBs } from "@/lib/src/pouchdb/chunks"; import { balanceChunkPurgedDBs } from "@/lib/src/pouchdb/chunks";
import { purgeUnreferencedChunks } from "@/lib/src/pouchdb/chunks"; import { purgeUnreferencedChunks } from "@/lib/src/pouchdb/chunks";
@@ -28,7 +36,7 @@ import {
updatePreviousExecutionTime, updatePreviousExecutionTime,
} from "../../common/utils"; } from "../../common/utils";
import { isAnyNote } from "../../lib/src/common/utils"; import { isAnyNote } from "../../lib/src/common/utils";
import { EVENT_FILE_SAVED, EVENT_SETTING_SAVED, eventHub } from "../../common/events"; import { EVENT_FILE_SAVED, EVENT_ON_UNRESOLVED_ERROR, EVENT_SETTING_SAVED, eventHub } from "../../common/events";
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator"; import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
import { $msg } from "../../lib/src/common/i18n"; import { $msg } from "../../lib/src/common/i18n";
@@ -40,6 +48,20 @@ const REPLICATION_ON_EVENT_FORECASTED_TIME = 5000;
export class ModuleReplicator extends AbstractModule { export class ModuleReplicator extends AbstractModule {
_replicatorType?: RemoteType; _replicatorType?: RemoteType;
_previousErrors = new Set<string>();
showError(msg: string, max_log_level: LOG_LEVEL = LEVEL_NOTICE) {
const level = this._previousErrors.has(msg) ? LEVEL_INFO : max_log_level;
this._log(msg, level);
if (!this._previousErrors.has(msg)) {
this._previousErrors.add(msg);
eventHub.emitEvent(EVENT_ON_UNRESOLVED_ERROR);
}
}
clearErrors() {
this._previousErrors.clear();
eventHub.emitEvent(EVENT_ON_UNRESOLVED_ERROR);
}
private _everyOnloadAfterLoadSettings(): Promise<boolean> { private _everyOnloadAfterLoadSettings(): Promise<boolean> {
eventHub.onEvent(EVENT_FILE_SAVED, () => { eventHub.onEvent(EVENT_FILE_SAVED, () => {
@@ -59,7 +81,7 @@ export class ModuleReplicator extends AbstractModule {
async setReplicator() { async setReplicator() {
const replicator = await this.services.replicator.getNewReplicator(); const replicator = await this.services.replicator.getNewReplicator();
if (!replicator) { if (!replicator) {
this._log($msg("Replicator.Message.InitialiseFatalError"), LOG_LEVEL_NOTICE); this.showError($msg("Replicator.Message.InitialiseFatalError"), LOG_LEVEL_NOTICE);
return false; return false;
} }
if (this.core.replicator) { if (this.core.replicator) {
@@ -89,7 +111,7 @@ export class ModuleReplicator extends AbstractModule {
// Checking salt // Checking salt
const replicator = this.services.replicator.getActiveReplicator(); const replicator = this.services.replicator.getActiveReplicator();
if (!replicator) { if (!replicator) {
this._log($msg("Replicator.Message.InitialiseFatalError"), LOG_LEVEL_NOTICE); this.showError($msg("Replicator.Message.InitialiseFatalError"), LOG_LEVEL_NOTICE);
return false; return false;
} }
return await replicator.ensurePBKDF2Salt(this.settings, showMessage, true); return await replicator.ensurePBKDF2Salt(this.settings, showMessage, true);
@@ -98,15 +120,16 @@ export class ModuleReplicator extends AbstractModule {
async _everyBeforeReplicate(showMessage: boolean): Promise<boolean> { async _everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
// Checking salt // Checking salt
if (!this.core.managers.networkManager.isOnline) { if (!this.core.managers.networkManager.isOnline) {
this._log("Network is offline", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO); this.showError("Network is offline", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
return false; return false;
} }
// Showing message is false: that because be shown here. (And it is a fatal error, no way to hide it). // Showing message is false: that because be shown here. (And it is a fatal error, no way to hide it).
if (!(await this.ensureReplicatorPBKDF2Salt(false))) { if (!(await this.ensureReplicatorPBKDF2Salt(false))) {
Logger("Failed to initialise the encryption key, preventing replication.", LOG_LEVEL_NOTICE); this.showError("Failed to initialise the encryption key, preventing replication.");
return false; return false;
} }
await this.loadQueuedFiles(); await this.loadQueuedFiles();
this.clearErrors();
return true; return true;
} }
@@ -195,18 +218,19 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
} }
if (!(await this.services.fileProcessing.commitPendingFileEvents())) { if (!(await this.services.fileProcessing.commitPendingFileEvents())) {
Logger($msg("Replicator.Message.Pending"), LOG_LEVEL_NOTICE); this.showError($msg("Replicator.Message.Pending"), LOG_LEVEL_NOTICE);
return false; return false;
} }
if (!this.core.managers.networkManager.isOnline) { if (!this.core.managers.networkManager.isOnline) {
this._log("Network is offline", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO); this.showError("Network is offline", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
return false; return false;
} }
if (!(await this.services.replication.onBeforeReplicate(showMessage))) { if (!(await this.services.replication.onBeforeReplicate(showMessage))) {
Logger($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE); this.showError($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE);
return false; return false;
} }
this.clearErrors();
return true; return true;
} }
@@ -401,11 +425,56 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
this.saveQueuedFiles(); this.saveQueuedFiles();
}); });
async checkIsChangeRequiredForDatabaseProcessing(dbDoc: LoadedEntry): Promise<boolean> {
const path = getPath(dbDoc);
try {
const savedDoc = await this.localDatabase.getRaw<LoadedEntry>(dbDoc._id, {
conflicts: true,
revs_info: true,
});
const newRev = dbDoc._rev ?? "";
const latestRev = savedDoc._rev ?? "";
const revisions = savedDoc._revs_info?.map((e) => e.rev) ?? [];
if (savedDoc._conflicts && savedDoc._conflicts.length > 0) {
// There are conflicts, so we have to process it.
return true;
}
if (newRev == latestRev) {
// The latest revision. We need to process it.
return true;
}
const index = revisions.indexOf(newRev);
if (index >= 0) {
// the revision has been inserted before.
return false; // Already processed.
}
return true; // This mostly should not happen, but we have to process it just in case.
} catch (e: any) {
if ("status" in e && e.status == 404) {
return true;
// Not existing, so we have to process it.
} else {
Logger(
`Failed to get existing document for ${path} (${dbDoc._id.substring(0, 8)}, ${dbDoc._rev?.substring(0, 10)}) `,
LOG_LEVEL_NOTICE
);
Logger(e, LOG_LEVEL_VERBOSE);
return true;
}
}
return true;
}
databaseQueuedProcessor = new QueueProcessor( databaseQueuedProcessor = new QueueProcessor(
async (docs: EntryBody[]) => { async (docs: EntryBody[]) => {
const dbDoc = docs[0] as LoadedEntry; // It has no `data` const dbDoc = docs[0] as LoadedEntry; // It has no `data`
const path = getPath(dbDoc); const path = getPath(dbDoc);
// If the document is existing with any revision, confirm that we have to process it.
const isRequired = await this.checkIsChangeRequiredForDatabaseProcessing(dbDoc);
if (!isRequired) {
Logger(`Skipped (Not latest): ${path} (${dbDoc._id.substring(0, 8)})`, LOG_LEVEL_VERBOSE);
return;
}
// If `Read chunks online` is disabled, chunks should be transferred before here. // If `Read chunks online` is disabled, chunks should be transferred before here.
// However, in some cases, chunks are after that. So, if missing chunks exist, we have to wait for them. // However, in some cases, chunks are after that. So, if missing chunks exist, we have to wait for them.
const doc = await this.localDatabase.getDBEntryFromMeta({ ...dbDoc }, false, true); const doc = await this.localDatabase.getDBEntryFromMeta({ ...dbDoc }, false, true);
@@ -503,6 +572,10 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
return !checkResult; return !checkResult;
} }
private _reportUnresolvedMessages(): Promise<string[]> {
return Promise.resolve([...this._previousErrors]);
}
onBindFunction(core: LiveSyncCore, services: typeof core.services): void { onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
services.replicator.handleGetActiveReplicator(this._getReplicator.bind(this)); services.replicator.handleGetActiveReplicator(this._getReplicator.bind(this));
services.databaseEvents.handleOnDatabaseInitialisation(this._everyOnInitializeDatabase.bind(this)); services.databaseEvents.handleOnDatabaseInitialisation(this._everyOnInitializeDatabase.bind(this));
@@ -516,5 +589,6 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
services.replication.handleReplicateByEvent(this._replicateByEvent.bind(this)); services.replication.handleReplicateByEvent(this._replicateByEvent.bind(this));
services.remote.handleReplicateAllToRemote(this._replicateAllToServer.bind(this)); services.remote.handleReplicateAllToRemote(this._replicateAllToServer.bind(this));
services.remote.handleReplicateAllFromRemote(this._replicateAllFromServer.bind(this)); services.remote.handleReplicateAllFromRemote(this._replicateAllFromServer.bind(this));
services.appLifecycle.reportUnresolvedMessages(this._reportUnresolvedMessages.bind(this));
} }
} }

View File

@@ -1,5 +1,12 @@
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts"; import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { LOG_LEVEL_DEBUG, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger"; import {
LEVEL_INFO,
LEVEL_NOTICE,
LOG_LEVEL_DEBUG,
LOG_LEVEL_NOTICE,
LOG_LEVEL_VERBOSE,
type LOG_LEVEL,
} from "octagonal-wheels/common/logger";
import { Notice, requestUrl, type RequestUrlParam, type RequestUrlResponse } from "../../deps.ts"; import { Notice, requestUrl, type RequestUrlParam, type RequestUrlResponse } from "../../deps.ts";
import { type CouchDBCredentials, type EntryDoc, type FilePath } from "../../lib/src/common/types.ts"; import { type CouchDBCredentials, type EntryDoc, type FilePath } from "../../lib/src/common/types.ts";
import { getPathFromTFile } from "../../common/utils.ts"; import { getPathFromTFile } from "../../common/utils.ts";
@@ -12,6 +19,7 @@ import { ObsHttpHandler } from "./APILib/ObsHttpHandler.ts";
import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.ts"; import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.ts";
import { AuthorizationHeaderGenerator } from "../../lib/src/replication/httplib.ts"; import { AuthorizationHeaderGenerator } from "../../lib/src/replication/httplib.ts";
import type { LiveSyncCore } from "../../main.ts"; import type { LiveSyncCore } from "../../main.ts";
import { EVENT_ON_UNRESOLVED_ERROR, eventHub } from "../../common/events.ts";
setNoticeClass(Notice); setNoticeClass(Notice);
@@ -24,7 +32,20 @@ export class ModuleObsidianAPI extends AbstractObsidianModule {
_customHandler!: ObsHttpHandler; _customHandler!: ObsHttpHandler;
_authHeader = new AuthorizationHeaderGenerator(); _authHeader = new AuthorizationHeaderGenerator();
_previousErrors = new Set<string>();
showError(msg: string, max_log_level: LOG_LEVEL = LEVEL_NOTICE) {
const level = this._previousErrors.has(msg) ? LEVEL_INFO : max_log_level;
this._log(msg, level);
if (!this._previousErrors.has(msg)) {
this._previousErrors.add(msg);
eventHub.emitEvent(EVENT_ON_UNRESOLVED_ERROR);
}
}
clearErrors() {
this._previousErrors.clear();
eventHub.emitEvent(EVENT_ON_UNRESOLVED_ERROR);
}
last_successful_post = false; last_successful_post = false;
_customFetchHandler(): ObsHttpHandler { _customFetchHandler(): ObsHttpHandler {
if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined); if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined);
@@ -180,6 +201,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule {
} }
} }
} }
this.clearErrors();
return response; return response;
} catch (ex) { } catch (ex) {
if (ex instanceof TypeError) { if (ex instanceof TypeError) {
@@ -195,7 +217,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule {
headers, headers,
}); });
if (resp2.status / 100 == 2) { if (resp2.status / 100 == 2) {
this._log( this.showError(
"The request was successful by API. But the native fetch API failed! Please check CORS settings on the remote database!. While this condition, you cannot enable LiveSync", "The request was successful by API. But the native fetch API failed! Please check CORS settings on the remote database!. While this condition, you cannot enable LiveSync",
LOG_LEVEL_NOTICE LOG_LEVEL_NOTICE
); );
@@ -203,7 +225,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule {
} }
const r2 = resp2.clone(); const r2 = resp2.clone();
const msg = await r2.text(); const msg = await r2.text();
this._log(`Failed to fetch by API. ${resp2.status}: ${msg}`, LOG_LEVEL_NOTICE); this.showError(`Failed to fetch by API. ${resp2.status}: ${msg}`, LOG_LEVEL_NOTICE);
return resp2; return resp2;
} }
throw ex; throw ex;
@@ -211,7 +233,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule {
} catch (ex: any) { } catch (ex: any) {
this._log(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL_VERBOSE); this._log(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL_VERBOSE);
const msg = ex instanceof Error ? `${ex?.name}:${ex?.message}` : ex?.toString(); const msg = ex instanceof Error ? `${ex?.name}:${ex?.message}` : ex?.toString();
this._log(`Failed to fetch: ${msg}`, LOG_LEVEL_NOTICE); this.showError(`Failed to fetch: ${msg}`); // Do not show notice, due to throwing below
this._log(ex, LOG_LEVEL_VERBOSE); this._log(ex, LOG_LEVEL_VERBOSE);
// limit only in bulk_docs. // limit only in bulk_docs.
if (url.toString().indexOf("_bulk_docs") !== -1) { if (url.toString().indexOf("_bulk_docs") !== -1) {
@@ -279,6 +301,10 @@ export class ModuleObsidianAPI extends AbstractObsidianModule {
return `${"appId" in this.app ? this.app.appId : ""}`; return `${"appId" in this.app ? this.app.appId : ""}`;
} }
private _reportUnresolvedMessages(): Promise<string[]> {
return Promise.resolve([...this._previousErrors]);
}
onBindFunction(core: LiveSyncCore, services: typeof core.services) { onBindFunction(core: LiveSyncCore, services: typeof core.services) {
services.API.handleGetCustomFetchHandler(this._customFetchHandler.bind(this)); services.API.handleGetCustomFetchHandler(this._customFetchHandler.bind(this));
services.API.handleIsLastPostFailedDueToPayloadSize(this._getLastPostFailedBySize.bind(this)); services.API.handleIsLastPostFailedDueToPayloadSize(this._getLastPostFailedBySize.bind(this));
@@ -288,5 +314,6 @@ export class ModuleObsidianAPI extends AbstractObsidianModule {
services.vault.handleVaultName(this._vaultName.bind(this)); services.vault.handleVaultName(this._vaultName.bind(this));
services.vault.handleGetActiveFilePath(this._getActiveFilePath.bind(this)); services.vault.handleGetActiveFilePath(this._getActiveFilePath.bind(this));
services.API.handleGetAppID(this._anyGetAppId.bind(this)); services.API.handleGetAppID(this._anyGetAppId.bind(this));
services.appLifecycle.reportUnresolvedMessages(this._reportUnresolvedMessages.bind(this));
} }
} }

View File

@@ -19,7 +19,12 @@ import {
logMessages, logMessages,
} from "../../lib/src/mock_and_interop/stores.ts"; } from "../../lib/src/mock_and_interop/stores.ts";
import { eventHub } from "../../lib/src/hub/hub.ts"; import { eventHub } from "../../lib/src/hub/hub.ts";
import { EVENT_FILE_RENAMED, EVENT_LAYOUT_READY, EVENT_LEAF_ACTIVE_CHANGED } from "../../common/events.ts"; import {
EVENT_FILE_RENAMED,
EVENT_LAYOUT_READY,
EVENT_LEAF_ACTIVE_CHANGED,
EVENT_ON_UNRESOLVED_ERROR,
} from "../../common/events.ts";
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts"; import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
import { addIcon, normalizePath, Notice } from "../../deps.ts"; import { addIcon, normalizePath, Notice } from "../../deps.ts";
import { LOG_LEVEL_NOTICE, setGlobalLogFunction } from "octagonal-wheels/common/logger"; import { LOG_LEVEL_NOTICE, setGlobalLogFunction } from "octagonal-wheels/common/logger";
@@ -198,11 +203,13 @@ export class ModuleLog extends AbstractObsidianModule {
this.applyStatusBarText(); this.applyStatusBarText();
}, 20); }, 20);
statusBarLabels.onChanged((label) => applyToDisplay(label.value)); statusBarLabels.onChanged((label) => applyToDisplay(label.value));
this.activeFileStatus.onChanged(() => this.updateMessageArea());
} }
private _everyOnload(): Promise<boolean> { private _everyOnload(): Promise<boolean> {
eventHub.onEvent(EVENT_LEAF_ACTIVE_CHANGED, () => this.onActiveLeafChange()); eventHub.onEvent(EVENT_LEAF_ACTIVE_CHANGED, () => this.onActiveLeafChange());
eventHub.onceEvent(EVENT_LAYOUT_READY, () => this.onActiveLeafChange()); eventHub.onceEvent(EVENT_LAYOUT_READY, () => this.onActiveLeafChange());
eventHub.onEvent(EVENT_ON_UNRESOLVED_ERROR, () => this.updateMessageArea());
return Promise.resolve(true); return Promise.resolve(true);
} }
@@ -234,8 +241,19 @@ export class ModuleLog extends AbstractObsidianModule {
async setFileStatus() { async setFileStatus() {
const fileStatus = await this.getActiveFileStatus(); const fileStatus = await this.getActiveFileStatus();
this.activeFileStatus.value = fileStatus; this.activeFileStatus.value = fileStatus;
this.messageArea!.innerText = this.settings.hideFileWarningNotice ? "" : fileStatus;
} }
async updateMessageArea() {
if (this.messageArea) {
const messageLines = [];
const fileStatus = this.activeFileStatus.value;
if (fileStatus && !this.settings.hideFileWarningNotice) messageLines.push(fileStatus);
const messages = (await this.services.appLifecycle.getUnresolvedMessages()).flat().filter((e) => e);
messageLines.push(...messages);
this.messageArea.innerText = messageLines.map((e) => `⚠️ ${e}`).join("\n");
}
}
onActiveLeafChange() { onActiveLeafChange() {
fireAndForget(async () => { fireAndForget(async () => {
this.adjustStatusDivPosition(); this.adjustStatusDivPosition();

View File

@@ -41,7 +41,7 @@ export function getBucketConfigSummary(setting: ObsidianLiveSyncSettings, showAd
*/ */
export function getCouchDBConfigSummary(setting: ObsidianLiveSyncSettings, showAdvanced = false) { export function getCouchDBConfigSummary(setting: ObsidianLiveSyncSettings, showAdvanced = false) {
const settingTable: Partial<ObsidianLiveSyncSettings> = pickCouchDBSyncSettings(setting); const settingTable: Partial<ObsidianLiveSyncSettings> = pickCouchDBSyncSettings(setting);
return getSummaryFromPartialSettings(settingTable, showAdvanced); return getSummaryFromPartialSettings(settingTable, showAdvanced || setting.useJWT);
} }
/** /**

View File

@@ -223,7 +223,7 @@
<option value="ES512">ES512</option> <option value="ES512">ES512</option>
</select> </select>
</InputRow> </InputRow>
<InputRow label="JWT Expiration Duration (seconds)"> <InputRow label="JWT Expiration Duration (minutes)">
<input <input
type="text" type="text"
name="couchdb-jwt-exp-duration" name="couchdb-jwt-exp-duration"
@@ -233,19 +233,21 @@
/> />
</InputRow> </InputRow>
<InputRow label="JWT Key"> <InputRow label="JWT Key">
<input <textarea
type="text"
name="couchdb-jwt-key" name="couchdb-jwt-key"
rows="5"
autocapitalize="off"
spellcheck="false"
placeholder="Enter your JWT secret or private key" placeholder="Enter your JWT secret or private key"
bind:value={syncSetting.jwtKey} bind:value={syncSetting.jwtKey}
disabled={!isUseJWT} disabled={!isUseJWT}
/> ></textarea>
</InputRow> </InputRow>
<InputRow label="JWT Key ID (kid)"> <InputRow label="JWT Key ID (kid)">
<input <input
type="text" type="text"
name="couchdb-jwt-kid" name="couchdb-jwt-kid"
placeholder="Enter your JWT Key ID (optional)" placeholder="Enter your JWT Key ID"
bind:value={syncSetting.jwtKid} bind:value={syncSetting.jwtKid}
disabled={!isUseJWT} disabled={!isUseJWT}
/> />
@@ -254,7 +256,7 @@
<input <input
type="text" type="text"
name="couchdb-jwt-sub" name="couchdb-jwt-sub"
placeholder="Enter your JWT Subject (optional)" placeholder="Enter your JWT Subject (CouchDB Username)"
bind:value={syncSetting.jwtSub} bind:value={syncSetting.jwtSub}
disabled={!isUseJWT} disabled={!isUseJWT}
/> />

View File

@@ -414,12 +414,19 @@ div.workspace-leaf-content[data-type=bases] .livesync-status {
} }
.livesync-status div.livesync-status-messagearea { .livesync-status div.livesync-status-messagearea:empty {
display: none;
}
.livesync-status div.livesync-status-messagearea:not(:empty) {
opacity: 0.6; opacity: 0.6;
color: var(--text-on-accent); color: var(--text-on-accent);
background: var(--background-modifier-error); border: 1px solid var(--background-modifier-error);
background-color: rgba(var(--background-modifier-error-rgb), 0.2);
-webkit-filter: unset; -webkit-filter: unset;
filter: unset; filter: unset;
width: fit-content;
margin-left: auto;
} }