mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-17 06:42:31 +00:00
Merge pull request #915 from vrtmrz/improve_log_feature
Improve log features
This commit is contained in:
@@ -0,0 +1,142 @@
|
|||||||
|
import { REMOTE_COUCHDB, REMOTE_MINIO } from "@lib/common/models/setting.const";
|
||||||
|
import type { ObsidianLiveSyncSettings } from "@lib/common/models/setting.type";
|
||||||
|
import { generateCredentialObject } from "@lib/replication/httplib";
|
||||||
|
import { parseHeaderValues } from "@lib/common/utils";
|
||||||
|
import { requestToCouchDBWithCredentials } from "./utils";
|
||||||
|
import { LOG_LEVEL_VERBOSE, Logger } from "@lib/common/logger";
|
||||||
|
import { DEFAULT_SETTINGS } from "@lib/common/models/setting.const.defaults";
|
||||||
|
import { isCloudantURI } from "@lib/pouchdb/utils_couchdb";
|
||||||
|
import { compatGlobal } from "@lib/common/coreEnvFunctions";
|
||||||
|
import { manifestVersion, packageVersion } from "@lib/common/coreEnvVars";
|
||||||
|
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
||||||
|
function redactObject(obj: Record<string, any>, dotted: string, redactedValue = "REDACTED") {
|
||||||
|
const keys = dotted.split(".");
|
||||||
|
let current = obj;
|
||||||
|
for (let i = 0; i < keys.length - 1; i++) {
|
||||||
|
const key = keys[i];
|
||||||
|
if (!(key in current)) {
|
||||||
|
current[key] = {} as Record<string, any>;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
current = current[key];
|
||||||
|
}
|
||||||
|
const lastKey = keys[keys.length - 1];
|
||||||
|
if (lastKey in current) {
|
||||||
|
current[lastKey] = redactedValue;
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
export async function generateReport(settings: ObsidianLiveSyncSettings, core: LiveSyncBaseCore) {
|
||||||
|
let responseConfig: Record<string, any> = {};
|
||||||
|
const REDACTED = "𝑅𝐸𝐷𝐴𝐶𝑇𝐸𝐷";
|
||||||
|
if (settings.remoteType == REMOTE_COUCHDB) {
|
||||||
|
try {
|
||||||
|
const credential = generateCredentialObject(settings);
|
||||||
|
const customHeaders = parseHeaderValues(settings.couchDB_CustomHeaders);
|
||||||
|
const r = await requestToCouchDBWithCredentials(
|
||||||
|
settings.couchDB_URI,
|
||||||
|
credential,
|
||||||
|
window.origin,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
customHeaders
|
||||||
|
);
|
||||||
|
responseConfig = r.json as Record<string, any>;
|
||||||
|
redactObject(responseConfig, "couch_httpd_auth.secret");
|
||||||
|
redactObject(responseConfig, "couch_httpd_auth.authentication_db");
|
||||||
|
redactObject(responseConfig, "couch_httpd_auth.authentication_redirect");
|
||||||
|
redactObject(responseConfig, "couchdb.uuid");
|
||||||
|
redactObject(responseConfig, "admins");
|
||||||
|
redactObject(responseConfig, "users");
|
||||||
|
redactObject(responseConfig, "chttpd_auth.secret");
|
||||||
|
delete responseConfig["jwt_keys"];
|
||||||
|
} catch (ex) {
|
||||||
|
Logger(ex, LOG_LEVEL_VERBOSE);
|
||||||
|
responseConfig = {
|
||||||
|
error: "Requesting information from the remote CouchDB has failed. If you are using IBM Cloudant, this is normal behaviour.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (settings.remoteType == REMOTE_MINIO) {
|
||||||
|
responseConfig = { error: "Object Storage Synchronisation" };
|
||||||
|
//
|
||||||
|
}
|
||||||
|
const defaultKeys = Object.keys(DEFAULT_SETTINGS) as (keyof ObsidianLiveSyncSettings)[];
|
||||||
|
const pluginConfig = JSON.parse(JSON.stringify(settings)) as ObsidianLiveSyncSettings;
|
||||||
|
const pluginKeys = Object.keys(pluginConfig);
|
||||||
|
for (const key of pluginKeys) {
|
||||||
|
if (defaultKeys.includes(key as keyof ObsidianLiveSyncSettings)) continue;
|
||||||
|
delete pluginConfig[key as keyof ObsidianLiveSyncSettings];
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginConfig.couchDB_DBNAME = REDACTED;
|
||||||
|
pluginConfig.couchDB_PASSWORD = REDACTED;
|
||||||
|
const scheme = pluginConfig.couchDB_URI.startsWith("http:")
|
||||||
|
? "(HTTP)"
|
||||||
|
: pluginConfig.couchDB_URI.startsWith("https:")
|
||||||
|
? "(HTTPS)"
|
||||||
|
: "";
|
||||||
|
pluginConfig.couchDB_URI = isCloudantURI(pluginConfig.couchDB_URI) ? "cloudant" : `self-hosted${scheme}`;
|
||||||
|
pluginConfig.couchDB_USER = REDACTED;
|
||||||
|
pluginConfig.passphrase = REDACTED;
|
||||||
|
pluginConfig.encryptedPassphrase = REDACTED;
|
||||||
|
pluginConfig.encryptedCouchDBConnection = REDACTED;
|
||||||
|
pluginConfig.accessKey = REDACTED;
|
||||||
|
pluginConfig.secretKey = REDACTED;
|
||||||
|
const redact = (source: string) => `${REDACTED}(${source.length} letters)`;
|
||||||
|
const toSchemeOnly = (uri: string) => {
|
||||||
|
try {
|
||||||
|
return `${new URL(uri).protocol}//`;
|
||||||
|
} catch {
|
||||||
|
const matched = uri.match(/^[A-Za-z][A-Za-z0-9+.-]*:\/\//);
|
||||||
|
return matched?.[0] ?? REDACTED;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pluginConfig.remoteConfigurations = Object.fromEntries(
|
||||||
|
Object.entries(pluginConfig.remoteConfigurations || {}).map(([id, config]) => [
|
||||||
|
id,
|
||||||
|
{
|
||||||
|
...config,
|
||||||
|
uri: toSchemeOnly(config.uri),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
);
|
||||||
|
pluginConfig.region = redact(pluginConfig.region);
|
||||||
|
pluginConfig.bucket = redact(pluginConfig.bucket);
|
||||||
|
pluginConfig.pluginSyncExtendedSetting = {};
|
||||||
|
pluginConfig.P2P_AppID = redact(pluginConfig.P2P_AppID);
|
||||||
|
pluginConfig.P2P_passphrase = redact(pluginConfig.P2P_passphrase);
|
||||||
|
pluginConfig.P2P_roomID = redact(pluginConfig.P2P_roomID);
|
||||||
|
pluginConfig.P2P_relays = redact(pluginConfig.P2P_relays);
|
||||||
|
pluginConfig.jwtKey = redact(pluginConfig.jwtKey);
|
||||||
|
pluginConfig.jwtSub = redact(pluginConfig.jwtSub);
|
||||||
|
pluginConfig.jwtKid = redact(pluginConfig.jwtKid);
|
||||||
|
pluginConfig.bucketCustomHeaders = redact(pluginConfig.bucketCustomHeaders);
|
||||||
|
pluginConfig.couchDB_CustomHeaders = redact(pluginConfig.couchDB_CustomHeaders);
|
||||||
|
pluginConfig.P2P_turnCredential = redact(pluginConfig.P2P_turnCredential);
|
||||||
|
pluginConfig.P2P_turnUsername = redact(pluginConfig.P2P_turnUsername);
|
||||||
|
pluginConfig.P2P_turnServers = `(${pluginConfig.P2P_turnServers.split(",").length} servers configured)`;
|
||||||
|
const endpoint = pluginConfig.endpoint;
|
||||||
|
if (endpoint == "") {
|
||||||
|
pluginConfig.endpoint = "Not configured or AWS";
|
||||||
|
} else {
|
||||||
|
const endpointScheme = pluginConfig.endpoint.startsWith("http:")
|
||||||
|
? "(HTTP)"
|
||||||
|
: pluginConfig.endpoint.startsWith("https:")
|
||||||
|
? "(HTTPS)"
|
||||||
|
: "";
|
||||||
|
pluginConfig.endpoint = `${endpoint.indexOf(".r2.cloudflarestorage.") !== -1 ? "R2" : "self-hosted?"}(${endpointScheme})`;
|
||||||
|
}
|
||||||
|
const obsidianInfo = {
|
||||||
|
navigator: compatGlobal.navigator.userAgent,
|
||||||
|
fileSystem: core.services.vault.isStorageInsensitive() ? "insensitive" : "sensitive",
|
||||||
|
};
|
||||||
|
const result = {
|
||||||
|
obsidianInfo,
|
||||||
|
responseConfig,
|
||||||
|
pluginConfig,
|
||||||
|
manifestVersion,
|
||||||
|
packageVersion,
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
+1
-1
Submodule src/lib updated: 36b99354f6...6abcea69eb
@@ -25,7 +25,7 @@ import {
|
|||||||
EVENT_ON_UNRESOLVED_ERROR,
|
EVENT_ON_UNRESOLVED_ERROR,
|
||||||
} from "../../common/events.ts";
|
} from "../../common/events.ts";
|
||||||
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { addIcon, normalizePath, Notice } from "../../deps.ts";
|
import { addIcon, debounce, normalizePath, Notice, stringifyYaml, type WorkspaceLeaf } from "../../deps.ts";
|
||||||
import { LOG_LEVEL_NOTICE, setGlobalLogFunction } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_NOTICE, setGlobalLogFunction } from "octagonal-wheels/common/logger";
|
||||||
import { LogPaneView, VIEW_TYPE_LOG } from "./Log/LogPaneView.ts";
|
import { LogPaneView, VIEW_TYPE_LOG } from "./Log/LogPaneView.ts";
|
||||||
import { serialized } from "octagonal-wheels/concurrency/lock";
|
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||||
@@ -41,6 +41,8 @@ import {
|
|||||||
} from "@lib/string_and_binary/path.ts";
|
} from "@lib/string_and_binary/path.ts";
|
||||||
import { MARK_LOG_NETWORK_ERROR, MARK_LOG_SEPARATOR } from "@lib/services/lib/logUtils.ts";
|
import { MARK_LOG_NETWORK_ERROR, MARK_LOG_SEPARATOR } from "@lib/services/lib/logUtils.ts";
|
||||||
import { NetworkWarningStyles } from "@lib/common/models/setting.const.ts";
|
import { NetworkWarningStyles } from "@lib/common/models/setting.const.ts";
|
||||||
|
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||||
|
import { generateReport } from "@/common/reportTool.ts";
|
||||||
|
|
||||||
// This module cannot be a core module because it depends on the Obsidian UI.
|
// This module cannot be a core module because it depends on the Obsidian UI.
|
||||||
|
|
||||||
@@ -50,18 +52,51 @@ const globalLogFunction = (message: any, level?: number, key?: string) => {
|
|||||||
const messageX =
|
const messageX =
|
||||||
message instanceof Error
|
message instanceof Error
|
||||||
? new LiveSyncError("[Error Logged]: " + message.message, { cause: message })
|
? new LiveSyncError("[Error Logged]: " + message.message, { cause: message })
|
||||||
: message;
|
: typeof message === "string"
|
||||||
|
? message
|
||||||
|
: JSON.stringify(message);
|
||||||
const entry = { message: messageX, level, key } as LogEntry;
|
const entry = { message: messageX, level, key } as LogEntry;
|
||||||
recentLogEntries.value = [...recentLogEntries.value, entry];
|
recentLogEntries.value = [...recentLogEntries.value, entry];
|
||||||
};
|
};
|
||||||
|
|
||||||
setGlobalLogFunction(globalLogFunction);
|
setGlobalLogFunction(globalLogFunction);
|
||||||
let recentLogs = [] as string[];
|
// Keep the recent logs in memory for display, but also keep a longer history in logForDump for when the user wants to see more logs.
|
||||||
|
// logForDump is not reactive and is only used for dumping logs when requested, while recentLogs is reactive and is used for displaying logs in the UI.
|
||||||
|
const logForDump = [] as string[];
|
||||||
|
|
||||||
function addLog(log: string) {
|
function addLog(log: string) {
|
||||||
recentLogs = [...recentLogs, log].splice(-200);
|
logForDump.push(log);
|
||||||
logMessages.value = recentLogs;
|
while (logForDump.length > 1000) {
|
||||||
|
logForDump.shift();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display log is kept separate from the full log history to optimize performance and memory usage.
|
||||||
|
// And debounce the updates to the display log to avoid excessive UI updates when there are many log entries in a short time.
|
||||||
|
const logForDisplay = [] as string[];
|
||||||
|
|
||||||
|
const updateLogMessage = debounce(() => {
|
||||||
|
logMessages.value = [...logForDisplay];
|
||||||
|
}, 25);
|
||||||
|
function addDisplayLog(log: string) {
|
||||||
|
logForDisplay.push(log);
|
||||||
|
while (logForDisplay.length > 200) {
|
||||||
|
logForDisplay.shift();
|
||||||
|
}
|
||||||
|
updateLogMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
const redactPatterns = [/PBKDF2 salt \(Security Seed\):.*$/];
|
||||||
|
function redactLog(log: string) {
|
||||||
|
let redactedLog = log;
|
||||||
|
for (const pattern of redactPatterns) {
|
||||||
|
redactedLog = redactedLog.replace(pattern, (match) => {
|
||||||
|
return match.split(":")[0] + ": [REDACTED]";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return redactedLog;
|
||||||
|
}
|
||||||
|
|
||||||
// logStore.intercept(e => e.slice(Math.min(e.length - 200, 0)));
|
// logStore.intercept(e => e.slice(Math.min(e.length - 200, 0)));
|
||||||
|
|
||||||
const showDebugLog = false;
|
const showDebugLog = false;
|
||||||
@@ -86,15 +121,15 @@ export class ModuleLog extends AbstractObsidianModule {
|
|||||||
// const emptyMark = `\u{2003}`;
|
// const emptyMark = `\u{2003}`;
|
||||||
function padLeftSpComputed(numI: ReactiveValue<number>, mark: string) {
|
function padLeftSpComputed(numI: ReactiveValue<number>, mark: string) {
|
||||||
const formatted = reactiveSource("");
|
const formatted = reactiveSource("");
|
||||||
let timer: ReturnType<typeof setTimeout> | undefined = undefined;
|
let timer: number | undefined = undefined;
|
||||||
let maxLen = 1;
|
let maxLen = 1;
|
||||||
numI.onChanged((numX) => {
|
numI.onChanged((numX) => {
|
||||||
const num = numX.value;
|
const num = numX.value;
|
||||||
const numLen = `${Math.abs(num)}`.length + 1;
|
const numLen = `${Math.abs(num)}`.length + 1;
|
||||||
maxLen = maxLen < numLen ? numLen : maxLen;
|
maxLen = maxLen < numLen ? numLen : maxLen;
|
||||||
if (timer) clearTimeout(timer);
|
if (timer) compatGlobal.clearTimeout(timer);
|
||||||
if (num == 0) {
|
if (num == 0) {
|
||||||
timer = setTimeout(() => {
|
timer = compatGlobal.setTimeout(() => {
|
||||||
formatted.value = "";
|
formatted.value = "";
|
||||||
maxLen = 1;
|
maxLen = 1;
|
||||||
}, 3000);
|
}, 3000);
|
||||||
@@ -323,7 +358,7 @@ export class ModuleLog extends AbstractObsidianModule {
|
|||||||
if (this.nextFrameQueue) {
|
if (this.nextFrameQueue) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.nextFrameQueue = requestAnimationFrame(() => {
|
this.nextFrameQueue = compatGlobal.requestAnimationFrame(() => {
|
||||||
this.nextFrameQueue = undefined;
|
this.nextFrameQueue = undefined;
|
||||||
const { message, status } = this.statusBarLabels.value;
|
const { message, status } = this.statusBarLabels.value;
|
||||||
// const recent = logMessages.value;
|
// const recent = logMessages.value;
|
||||||
@@ -346,7 +381,8 @@ export class ModuleLog extends AbstractObsidianModule {
|
|||||||
(a, b) => (a < b.ttl ? a : b.ttl),
|
(a, b) => (a < b.ttl ? a : b.ttl),
|
||||||
Number.MAX_SAFE_INTEGER
|
Number.MAX_SAFE_INTEGER
|
||||||
);
|
);
|
||||||
if (this.logLines.length > 0) setTimeout(() => this.applyStatusBarText(), minimumNext - now);
|
if (this.logLines.length > 0)
|
||||||
|
compatGlobal.setTimeout(() => this.applyStatusBarText(), minimumNext - now);
|
||||||
const recent = this.logLines.map((e) => e.message);
|
const recent = this.logLines.map((e) => e.message);
|
||||||
const recentLogs = recent.reverse().join("\n");
|
const recentLogs = recent.reverse().join("\n");
|
||||||
if (isDirty("recentLogs", recentLogs)) this.logHistory!.innerText = recentLogs;
|
if (isDirty("recentLogs", recentLogs)) this.logHistory!.innerText = recentLogs;
|
||||||
@@ -368,7 +404,7 @@ export class ModuleLog extends AbstractObsidianModule {
|
|||||||
if (this.statusDiv) {
|
if (this.statusDiv) {
|
||||||
this.statusDiv.remove();
|
this.statusDiv.remove();
|
||||||
}
|
}
|
||||||
document.querySelectorAll(`.livesync-status`)?.forEach((e) => e.remove());
|
compatGlobal.document.querySelectorAll(`.livesync-status`)?.forEach((e) => e.remove());
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
_everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
@@ -390,7 +426,28 @@ export class ModuleLog extends AbstractObsidianModule {
|
|||||||
void this.services.API.showWindow(VIEW_TYPE_LOG);
|
void this.services.API.showWindow(VIEW_TYPE_LOG);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.registerView(VIEW_TYPE_LOG, (leaf) => new LogPaneView(leaf, this.plugin));
|
this.addCommand({
|
||||||
|
id: "dump-debug-info",
|
||||||
|
name: "Generate full report for opening the issue with debug info",
|
||||||
|
callback: async () => {
|
||||||
|
const recentLog = [...logForDump];
|
||||||
|
const report = await generateReport(this.services.setting.currentSettings(), this.core);
|
||||||
|
const info = {
|
||||||
|
...report,
|
||||||
|
recentLog: recentLog.map(redactLog),
|
||||||
|
};
|
||||||
|
const yaml = `\`\`\`\`
|
||||||
|
# ---- Debug Info Dump ----
|
||||||
|
${stringifyYaml(info)}
|
||||||
|
\`\`\`\``;
|
||||||
|
if (await this.services.UI.promptCopyToClipboard("Debug info", yaml)) {
|
||||||
|
new Notice(
|
||||||
|
"Debug info copied to clipboard. You can paste it in the issue. Be careful as it may contain sensitive information, review it before sharing."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.registerView(VIEW_TYPE_LOG, (leaf: WorkspaceLeaf) => new LogPaneView(leaf, this.plugin));
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
private _everyOnloadAfterLoadSettings(): Promise<boolean> {
|
private _everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
@@ -404,7 +461,7 @@ export class ModuleLog extends AbstractObsidianModule {
|
|||||||
void this.setFileStatus();
|
void this.setFileStatus();
|
||||||
});
|
});
|
||||||
|
|
||||||
const w = document.querySelectorAll(`.livesync-status`);
|
const w = compatGlobal.document.querySelectorAll(`.livesync-status`);
|
||||||
w.forEach((e) => e.remove());
|
w.forEach((e) => e.remove());
|
||||||
|
|
||||||
this.observeForLogs();
|
this.observeForLogs();
|
||||||
@@ -421,6 +478,8 @@ export class ModuleLog extends AbstractObsidianModule {
|
|||||||
this.statusBar?.addClass("syncstatusbar");
|
this.statusBar?.addClass("syncstatusbar");
|
||||||
}
|
}
|
||||||
this.adjustStatusDivPosition();
|
this.adjustStatusDivPosition();
|
||||||
|
this._log("Log module loaded", LOG_LEVEL_INFO);
|
||||||
|
this._log("Verbose log", LOG_LEVEL_VERBOSE);
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,11 +503,12 @@ export class ModuleLog extends AbstractObsidianModule {
|
|||||||
if (level == LOG_LEVEL_DEBUG && !showDebugLog) {
|
if (level == LOG_LEVEL_DEBUG && !showDebugLog) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let memoOnly = false;
|
||||||
if (level <= LOG_LEVEL_INFO && this.settings && this.settings.lessInformationInLog) {
|
if (level <= LOG_LEVEL_INFO && this.settings && this.settings.lessInformationInLog) {
|
||||||
return;
|
memoOnly = true;
|
||||||
}
|
}
|
||||||
if (this.settings && !this.settings.showVerboseLog && level == LOG_LEVEL_VERBOSE) {
|
if (this.settings && !this.settings.showVerboseLog && level == LOG_LEVEL_VERBOSE) {
|
||||||
return;
|
memoOnly = true;
|
||||||
}
|
}
|
||||||
const vaultName = this.services.vault.getVaultName();
|
const vaultName = this.services.vault.getVaultName();
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -469,6 +529,15 @@ export class ModuleLog extends AbstractObsidianModule {
|
|||||||
? `${errorInfo}`
|
? `${errorInfo}`
|
||||||
: JSON.stringify(message, null, 2);
|
: JSON.stringify(message, null, 2);
|
||||||
const newMessage = timestamp + "->" + messageContent;
|
const newMessage = timestamp + "->" + messageContent;
|
||||||
|
|
||||||
|
if (this.settings?.writeLogToTheFile) {
|
||||||
|
this.writeLogToTheFile(now, vaultName, newMessage);
|
||||||
|
}
|
||||||
|
addLog(newMessage);
|
||||||
|
if (memoOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addDisplayLog(newMessage);
|
||||||
if (message instanceof Error) {
|
if (message instanceof Error) {
|
||||||
console.error(vaultName + ":" + newMessage);
|
console.error(vaultName + ":" + newMessage);
|
||||||
} else if (level >= LOG_LEVEL_INFO) {
|
} else if (level >= LOG_LEVEL_INFO) {
|
||||||
@@ -479,10 +548,6 @@ export class ModuleLog extends AbstractObsidianModule {
|
|||||||
if (!this.settings?.showOnlyIconsOnEditor) {
|
if (!this.settings?.showOnlyIconsOnEditor) {
|
||||||
this.statusLog.value = messageContent;
|
this.statusLog.value = messageContent;
|
||||||
}
|
}
|
||||||
if (this.settings?.writeLogToTheFile) {
|
|
||||||
this.writeLogToTheFile(now, vaultName, newMessage);
|
|
||||||
}
|
|
||||||
addLog(newMessage);
|
|
||||||
this.logLines.push({ ttl: now.getTime() + 3000, message: newMessage });
|
this.logLines.push({ ttl: now.getTime() + 3000, message: newMessage });
|
||||||
|
|
||||||
if (level >= LOG_LEVEL_NOTICE) {
|
if (level >= LOG_LEVEL_NOTICE) {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import { EVENT_REQUEST_SHOW_HISTORY } from "../../../common/obsidianEvents.ts";
|
|||||||
import { generateCredentialObject } from "../../../lib/src/replication/httplib.ts";
|
import { generateCredentialObject } from "../../../lib/src/replication/httplib.ts";
|
||||||
import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts";
|
import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts";
|
||||||
import type { PageFunctions } from "./SettingPane.ts";
|
import type { PageFunctions } from "./SettingPane.ts";
|
||||||
|
import { generateReport } from "@/common/reportTool.ts";
|
||||||
export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement, { addPanel }: PageFunctions): void {
|
export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement, { addPanel }: PageFunctions): void {
|
||||||
// const hatchWarn = this.createEl(paneEl, "div", { text: `To stop the boot up sequence for fixing problems on databases, you can put redflag.md on top of your vault (Rebooting obsidian is required).` });
|
// const hatchWarn = this.createEl(paneEl, "div", { text: `To stop the boot up sequence for fixing problems on databases, you can put redflag.md on top of your vault (Rebooting obsidian is required).` });
|
||||||
// hatchWarn.addClass("op-warn-info");
|
// hatchWarn.addClass("op-warn-info");
|
||||||
@@ -69,140 +70,14 @@ export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement,
|
|||||||
eventHub.emitEvent(EVENT_REQUEST_RUN_FIX_INCOMPLETE);
|
eventHub.emitEvent(EVENT_REQUEST_RUN_FIX_INCOMPLETE);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
new Setting(paneEl).setName($msg("Prepare the 'report' to create an issue")).addButton((button) =>
|
new Setting(paneEl).setName($msg("Prepare the 'report' to create an issue")).addButton((button) =>
|
||||||
button
|
button
|
||||||
.setButtonText($msg("Copy Report to clipboard"))
|
.setButtonText($msg("Copy Report to clipboard"))
|
||||||
.setCta()
|
.setCta()
|
||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
let responseConfig: any = {};
|
await this.app.commands.executeCommandById("obsidian-livesync:dump-debug-info");
|
||||||
const REDACTED = "𝑅𝐸𝐷𝐴𝐶𝑇𝐸𝐷";
|
|
||||||
if (this.editingSettings.remoteType == REMOTE_COUCHDB) {
|
|
||||||
try {
|
|
||||||
const credential = generateCredentialObject(this.editingSettings);
|
|
||||||
const customHeaders = parseHeaderValues(this.editingSettings.couchDB_CustomHeaders);
|
|
||||||
const r = await requestToCouchDBWithCredentials(
|
|
||||||
this.editingSettings.couchDB_URI,
|
|
||||||
credential,
|
|
||||||
window.origin,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
customHeaders
|
|
||||||
);
|
|
||||||
|
|
||||||
Logger(JSON.stringify(r.json, null, 2));
|
|
||||||
|
|
||||||
responseConfig = r.json;
|
|
||||||
responseConfig["couch_httpd_auth"].secret = REDACTED;
|
|
||||||
responseConfig["couch_httpd_auth"].authentication_db = REDACTED;
|
|
||||||
responseConfig["couch_httpd_auth"].authentication_redirect = REDACTED;
|
|
||||||
responseConfig["couchdb"].uuid = REDACTED;
|
|
||||||
responseConfig["admins"] = REDACTED;
|
|
||||||
delete responseConfig["jwt_keys"];
|
|
||||||
if ("secret" in responseConfig["chttpd_auth"])
|
|
||||||
responseConfig["chttpd_auth"].secret = REDACTED;
|
|
||||||
} catch (ex) {
|
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
|
||||||
responseConfig = {
|
|
||||||
error: "Requesting information from the remote CouchDB has failed. If you are using IBM Cloudant, this is normal behaviour.",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else if (this.editingSettings.remoteType == REMOTE_MINIO) {
|
|
||||||
responseConfig = { error: "Object Storage Synchronisation" };
|
|
||||||
//
|
|
||||||
}
|
|
||||||
const defaultKeys = Object.keys(DEFAULT_SETTINGS) as (keyof ObsidianLiveSyncSettings)[];
|
|
||||||
const pluginConfig = JSON.parse(JSON.stringify(this.editingSettings)) as ObsidianLiveSyncSettings;
|
|
||||||
const pluginKeys = Object.keys(pluginConfig);
|
|
||||||
for (const key of pluginKeys) {
|
|
||||||
if (defaultKeys.includes(key as any)) continue;
|
|
||||||
delete pluginConfig[key as keyof ObsidianLiveSyncSettings];
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginConfig.couchDB_DBNAME = REDACTED;
|
|
||||||
pluginConfig.couchDB_PASSWORD = REDACTED;
|
|
||||||
const scheme = pluginConfig.couchDB_URI.startsWith("http:")
|
|
||||||
? "(HTTP)"
|
|
||||||
: pluginConfig.couchDB_URI.startsWith("https:")
|
|
||||||
? "(HTTPS)"
|
|
||||||
: "";
|
|
||||||
pluginConfig.couchDB_URI = isCloudantURI(pluginConfig.couchDB_URI)
|
|
||||||
? "cloudant"
|
|
||||||
: `self-hosted${scheme}`;
|
|
||||||
pluginConfig.couchDB_USER = REDACTED;
|
|
||||||
pluginConfig.passphrase = REDACTED;
|
|
||||||
pluginConfig.encryptedPassphrase = REDACTED;
|
|
||||||
pluginConfig.encryptedCouchDBConnection = REDACTED;
|
|
||||||
pluginConfig.accessKey = REDACTED;
|
|
||||||
pluginConfig.secretKey = REDACTED;
|
|
||||||
const redact = (source: string) => `${REDACTED}(${source.length} letters)`;
|
|
||||||
const toSchemeOnly = (uri: string) => {
|
|
||||||
try {
|
|
||||||
return `${new URL(uri).protocol}//`;
|
|
||||||
} catch {
|
|
||||||
const matched = uri.match(/^[A-Za-z][A-Za-z0-9+.-]*:\/\//);
|
|
||||||
return matched?.[0] ?? REDACTED;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
pluginConfig.remoteConfigurations = Object.fromEntries(
|
|
||||||
Object.entries(pluginConfig.remoteConfigurations || {}).map(([id, config]) => [
|
|
||||||
id,
|
|
||||||
{
|
|
||||||
...config,
|
|
||||||
uri: toSchemeOnly(config.uri),
|
|
||||||
},
|
|
||||||
])
|
|
||||||
);
|
|
||||||
pluginConfig.region = redact(pluginConfig.region);
|
|
||||||
pluginConfig.bucket = redact(pluginConfig.bucket);
|
|
||||||
pluginConfig.pluginSyncExtendedSetting = {};
|
|
||||||
pluginConfig.P2P_AppID = redact(pluginConfig.P2P_AppID);
|
|
||||||
pluginConfig.P2P_passphrase = redact(pluginConfig.P2P_passphrase);
|
|
||||||
pluginConfig.P2P_roomID = redact(pluginConfig.P2P_roomID);
|
|
||||||
pluginConfig.P2P_relays = redact(pluginConfig.P2P_relays);
|
|
||||||
pluginConfig.jwtKey = redact(pluginConfig.jwtKey);
|
|
||||||
pluginConfig.jwtSub = redact(pluginConfig.jwtSub);
|
|
||||||
pluginConfig.jwtKid = redact(pluginConfig.jwtKid);
|
|
||||||
pluginConfig.bucketCustomHeaders = redact(pluginConfig.bucketCustomHeaders);
|
|
||||||
pluginConfig.couchDB_CustomHeaders = redact(pluginConfig.couchDB_CustomHeaders);
|
|
||||||
pluginConfig.P2P_turnCredential = redact(pluginConfig.P2P_turnCredential);
|
|
||||||
pluginConfig.P2P_turnUsername = redact(pluginConfig.P2P_turnUsername);
|
|
||||||
pluginConfig.P2P_turnServers = `(${pluginConfig.P2P_turnServers.split(",").length} servers configured)`;
|
|
||||||
const endpoint = pluginConfig.endpoint;
|
|
||||||
if (endpoint == "") {
|
|
||||||
pluginConfig.endpoint = "Not configured or AWS";
|
|
||||||
} else {
|
|
||||||
const endpointScheme = pluginConfig.endpoint.startsWith("http:")
|
|
||||||
? "(HTTP)"
|
|
||||||
: pluginConfig.endpoint.startsWith("https:")
|
|
||||||
? "(HTTPS)"
|
|
||||||
: "";
|
|
||||||
pluginConfig.endpoint = `${endpoint.indexOf(".r2.cloudflarestorage.") !== -1 ? "R2" : "self-hosted?"}(${endpointScheme})`;
|
|
||||||
}
|
|
||||||
const obsidianInfo = {
|
|
||||||
navigator: navigator.userAgent,
|
|
||||||
fileSystem: this.core.services.vault.isStorageInsensitive() ? "insensitive" : "sensitive",
|
|
||||||
};
|
|
||||||
const msgConfig = `# ---- Obsidian info ----
|
|
||||||
${stringifyYaml(obsidianInfo)}
|
|
||||||
---
|
|
||||||
# ---- remote config ----
|
|
||||||
${stringifyYaml(responseConfig)}
|
|
||||||
---
|
|
||||||
# ---- Plug-in config ----
|
|
||||||
${stringifyYaml({
|
|
||||||
version: this.manifestVersion,
|
|
||||||
...pluginConfig,
|
|
||||||
})}`;
|
|
||||||
console.log(msgConfig);
|
|
||||||
if ((await this.services.UI.promptCopyToClipboard("Generated report", msgConfig)) == true) {
|
|
||||||
// await navigator.clipboard.writeText(msgConfig);
|
|
||||||
// Logger(
|
|
||||||
// `Generated report has been copied to clipboard. Please report the issue with this! Thank you for your cooperation!`,
|
|
||||||
// LOG_LEVEL_NOTICE
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
new Setting(paneEl)
|
new Setting(paneEl)
|
||||||
|
|||||||
@@ -10,11 +10,15 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid
|
|||||||
|
|
||||||
### Improved
|
### Improved
|
||||||
- Improved the error verbosity on concurrent processing during the start-up process.
|
- Improved the error verbosity on concurrent processing during the start-up process.
|
||||||
|
- Now the `report` includes recent logs (of verbosity `verbose` even settings is not set to `verbose`).
|
||||||
|
- Updating logs is now debounced to avoid excessive updates during rapid log generation.
|
||||||
|
- Added a `Generate full report for opening the issue with debug info` command to the command palette, which generates a report without opening the settings dialogue.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix an issue about resuming from background on iOS (#888).
|
- Fix an issue about resuming from background on iOS (#888).
|
||||||
|
|
||||||
|
|
||||||
## 0.25.64
|
## 0.25.64
|
||||||
|
|
||||||
17th May, 2026
|
17th May, 2026
|
||||||
|
|||||||
Reference in New Issue
Block a user