\r\n\t\t\t\t{{ t('dbdoctor', 'This will issue SET GLOBAL / ALTER SYSTEM against the live server. The change is reversible by re-applying the previous value, but please verify your assumptions before proceeding.') }}\r\n\t\t\t
\r\n\t\t\t\t\t{{ t('dbdoctor', 'Optional. By default DB Doctor uses Nextcloud\\'s database connection. If your Nextcloud database user lacks read privileges on certain status views, you can supply a separate read-only account here. The password is encrypted at rest using Nextcloud\\'s credentials manager.') }}\r\n\t\t\t\t
\r\n\t\t\t\t\t{{ t('dbdoctor', 'No changes have been applied yet. When you click \"Apply now\" on the dashboard, every change is recorded here.') }}\r\n\t\t\t\t
\r\n\t\t\t\t
\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t
\r\n\t\t\t\t\t\t\t
{{ t('dbdoctor', 'When') }}
\r\n\t\t\t\t\t\t\t
{{ t('dbdoctor', 'Who') }}
\r\n\t\t\t\t\t\t\t
{{ t('dbdoctor', 'Variable') }}
\r\n\t\t\t\t\t\t\t
{{ t('dbdoctor', 'Old value') }}
\r\n\t\t\t\t\t\t\t
{{ t('dbdoctor', 'New value') }}
\r\n\t\t\t\t\t\t\t
{{ t('dbdoctor', 'Result') }}
\r\n\t\t\t\t\t\t
\r\n\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t
\r\n\t\t\t\t\t\t\t
{{ new Date(a.appliedAt * 1000).toLocaleString() }}
\r\n\t\t\t\t{{ t('dbdoctor', 'Add the following line to your {file}, then restart or reload the server.', { file: rule.apply?.configFile ?? '' }) }}\r\n\t\t\t
\r\n\r\n\t\t\r\n\r\n\t\t\r\n\t\r\n\r\n\r\n\r\n\r\n\r\n","/**\r\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\r\n * SPDX-License-Identifier: AGPL-3.0-or-later\r\n */\r\n\r\nexport type Grade = 'A' | 'B' | 'C' | 'D' | 'F'\r\n\r\nexport type MascotState = 'happy' | 'idle' | 'concerned' | 'checking'\r\n\r\n/**\r\n * Derive a grade letter from a numeric 0-100 score. Mirrors the\r\n * server-side {@see \\OCA\\DBDoctor\\Service\\Score::gradeFor} so\r\n * client-side fallbacks and server responses agree.\r\n *\r\n * @param score\r\n */\r\nexport function gradeFor(score: number): Grade {\r\n\tif (score >= 90) { return 'A' }\r\n\tif (score >= 80) { return 'B' }\r\n\tif (score >= 70) { return 'C' }\r\n\tif (score >= 60) { return 'D' }\r\n\treturn 'F'\r\n}\r\n\r\n/**\r\n * The CSS custom-property used to colour the score / heartbeat for\r\n * a given grade. Indirection through CSS variables keeps the\r\n * grade colours editable in tokens.scss.\r\n *\r\n * @param grade\r\n */\r\nexport function colorVarFor(grade: Grade): string {\r\n\treturn {\r\n\t\tA: 'var(--dbd-grade-a)',\r\n\t\tB: 'var(--dbd-grade-b)',\r\n\t\tC: 'var(--dbd-grade-c)',\r\n\t\tD: 'var(--dbd-grade-d)',\r\n\t\tF: 'var(--dbd-grade-f)',\r\n\t}[grade]\r\n}\r\n\r\n/**\r\n * Readable variant of the grade colour. The raw palette is tuned for\r\n * accents (tints, strokes, dots); as actual text on card surfaces it\r\n * can fall below AA — most visibly the dark-red F on a dark theme.\r\n * These variants are blended with the theme's main text colour in\r\n * tokens.scss so they stay legible on both light and dark surfaces.\r\n *\r\n * @param grade\r\n */\r\nexport function readableColorVarFor(grade: Grade): string {\r\n\treturn {\r\n\t\tA: 'var(--dbd-grade-a-readable)',\r\n\t\tB: 'var(--dbd-grade-b-readable)',\r\n\t\tC: 'var(--dbd-grade-c-readable)',\r\n\t\tD: 'var(--dbd-grade-d-readable)',\r\n\t\tF: 'var(--dbd-grade-f-readable)',\r\n\t}[grade]\r\n}\r\n\r\n/**\r\n * Beats per minute used by the Heartbeat component. The worse the\r\n * grade, the faster (and more alarming) the heartbeat — a soft visual\r\n * metaphor that doesn't fight the data.\r\n *\r\n * @param grade\r\n */\r\nexport function bpmFor(grade: Grade): number {\r\n\treturn { A: 60, B: 75, C: 90, D: 110, F: 130 }[grade]\r\n}\r\n\r\n/**\r\n * Beats per minute as a function of *live database latency*. Used\r\n * by the Heartbeat when a smoothed latency reading is available — a\r\n * snappy database makes the mascot's heart slow + calm, a struggling\r\n * one makes it race.\r\n *\r\n * Curve:\r\n * - 0 ms → 50 BPM (resting calm)\r\n * - 1 ms → 60 BPM\r\n * - 10 ms → 80 BPM (alert)\r\n * - 100 ms → 110 BPM (stressed)\r\n * - ms → 140 BPM (alarmed)\r\n *\r\n * The shape is `50 + 30 * log10(ms + 1)` clamped to [50, 160]. log10\r\n * keeps the response sane across the multi-decade range we care about\r\n * (sub-millisecond local DBs through hundreds-of-ms remote).\r\n *\r\n * @param latencyMs\r\n */\r\nexport function bpmForLatency(latencyMs: number): number {\r\n\tif (!Number.isFinite(latencyMs) || latencyMs < 0) { return 60 }\r\n\tconst bpm = 50 + 30 * Math.log10(latencyMs + 1)\r\n\treturn Math.max(50, Math.min(160, bpm))\r\n}\r\n\r\n/**\r\n * Mascot expression for a given grade + running state. When a check\r\n * is running we always show the \"checking\" mascot so the user gets\r\n * immediate feedback that something's happening.\r\n *\r\n * @param grade\r\n * @param running\r\n */\r\nexport function mascotFor(grade: Grade | null, running: boolean): MascotState {\r\n\tif (running) { return 'checking' }\r\n\tif (grade === null) { return 'idle' }\r\n\tif (grade === 'A' || grade === 'B') { return 'happy' }\r\n\tif (grade === 'F') { return 'concerned' }\r\n\treturn 'idle'\r\n}\r\n","\r\n\r\n\t\r\n\t\t\r\n\r\n\t\t
\r\n\t\t\t\r\n\t\t\t
\r\n\t\t\t\t\r\n\r\n\t\t\t\t\r\n\t\t\t\t
0\"\r\n\t\t\t\t\t:key=\"`ripple-${rippleKey}`\"\r\n\t\t\t\t\tclass=\"score-card__ripple\"\r\n\t\t\t\t\t:style=\"{ '--ripple-color': gradeColor } as CSSPropertiesWithVars\" />\r\n\r\n\t\t\t\t
\r\n\t\r\n\r\n\r\n\r\n\r\n\r\n","/**\r\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\r\n * SPDX-License-Identifier: AGPL-3.0-or-later\r\n */\r\n\r\nimport type { RunResult, SeriesPoint } from '../api/types'\r\n\r\nimport { defineStore } from 'pinia'\r\nimport { computed, ref } from 'vue'\r\nimport * as api from '../api/client'\r\nimport logger from '../utils/logger'\r\n\r\n/**\r\n * Store for the dashboard's main check data.\r\n *\r\n * Holds the most-recent RunResult, a per-rule history series cache\r\n * (lazily filled when a card expands), and a \"celebrate\" flag the\r\n * confetti overlay listens to. We deliberately keep this store\r\n * narrow — settings live in their own store.\r\n */\r\nexport const useChecksStore = defineStore('dbdoctor/checks', () => {\r\n\tconst latest = ref(null)\r\n\tconst running = ref(false)\r\n\tconst error = ref(null)\r\n\tconst ruleSeries = ref>({})\r\n\t// Toggled true for one frame after a fresh A grade is achieved\r\n\t// so ConfettiOverlay can mount and immediately consume it.\r\n\tconst celebrate = ref(false)\r\n\r\n\tconst grade = computed(() => latest.value?.grade ?? null)\r\n\r\n\tasync function fetchLatest(): Promise {\r\n\t\ttry {\r\n\t\t\tlatest.value = await api.getLatest()\r\n\t\t\terror.value = null\r\n\t\t} catch (e) {\r\n\t\t\terror.value = (e as Error).message ?? 'Could not load latest run.'\r\n\t\t\tlogger.error('fetchLatest failed', e)\r\n\t\t}\r\n\t}\r\n\r\n\tasync function runNow(): Promise {\r\n\t\tif (running.value) { return }\r\n\t\trunning.value = true\r\n\t\terror.value = null\r\n\t\tconst previousGrade = latest.value?.grade ?? null\r\n\t\ttry {\r\n\t\t\tconst next = await api.runCheck()\r\n\t\t\tlatest.value = next\r\n\t\t\t// Celebrate when the user just earned an A — but not\r\n\t\t\t// every load on a server that's already at A.\r\n\t\t\tif (next.grade === 'A' && previousGrade !== 'A') {\r\n\t\t\t\tcelebrate.value = true\r\n\t\t\t}\r\n\t\t} catch (e) {\r\n\t\t\terror.value = (e as Error).message ?? 'Check run failed.'\r\n\t\t\tlogger.error('runNow failed', e)\r\n\t\t} finally {\r\n\t\t\trunning.value = false\r\n\t\t}\r\n\t}\r\n\r\n\tfunction consumeCelebrate(): void {\r\n\t\tcelebrate.value = false\r\n\t}\r\n\r\n\tasync function loadSeries(ruleId: string, days: number = 30): Promise {\r\n\t\ttry {\r\n\t\t\truleSeries.value = {\r\n\t\t\t\t...ruleSeries.value,\r\n\t\t\t\t[ruleId]: await api.getHistory(ruleId, days),\r\n\t\t\t}\r\n\t\t} catch (e) {\r\n\t\t\tlogger.warn(`history load failed for ${ruleId}`, e)\r\n\t\t}\r\n\t}\r\n\r\n\tasync function applyAndRefresh(\r\n\t\truleId: string,\r\n\t\tvariable: string,\r\n\t\tvalue: string,\r\n\t): Promise<{ success: boolean, oldValue: string | null, newValue: string | null, error?: string }> {\r\n\t\tconst result = await api.applyChange(ruleId, variable, value)\r\n\t\tif (result.success) {\r\n\t\t\t// Re-run so the score reflects the change. We don't await\r\n\t\t\t// this aggressively — the dialog has already shown the\r\n\t\t\t// outcome by the time this resolves.\r\n\t\t\tvoid runNow()\r\n\t\t}\r\n\t\treturn result\r\n\t}\r\n\r\n\treturn {\r\n\t\t// state\r\n\t\tlatest,\r\n\t\trunning,\r\n\t\terror,\r\n\t\truleSeries,\r\n\t\tcelebrate,\r\n\t\t// derived\r\n\t\tgrade,\r\n\t\t// actions\r\n\t\tfetchLatest,\r\n\t\trunNow,\r\n\t\tloadSeries,\r\n\t\tapplyAndRefresh,\r\n\t\tconsumeCelebrate,\r\n\t}\r\n})\r\n","\r\n\r\n\t\r\n\t= 2\" class=\"score-trend\">\r\n\t\t\r\n\t\t\t{{ t('dbdoctor', 'Score trend') }}\r\n\t\t\t\r\n\t\t\t\t{{ deltaText }}\r\n\t\t\t\t{{ t('dbdoctor', 'last {days} days', { days: DAYS }) }}\r\n\t\t\t\r\n\t\t\r\n\t\t
\r\n\t\t\t\t\t{{ n('dbdoctor',\r\n\t\t\t\t\t\t'%n applied fix has reverted to a different value on the live server.',\r\n\t\t\t\t\t\t'%n applied fixes have reverted to different values on the live server.',\r\n\t\t\t\t\t\trevertedFixes.length) }}\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t{{ t('dbdoctor', 'This usually means the database restarted. Make these permanent in your config file:') }}\r\n\t\t\t\t\t\r\n\t\t\t\t\t
\r\n\r\n\r\n\r\n\r\n\r\n","import { createApp } from \"vue\";\nfunction spawnDialog(dialog, props = {}, options = {}) {\n let { container } = options;\n if (\"container\" in props && typeof props.container === \"string\") {\n container ??= props.container;\n }\n const resolvedContainer = typeof container === \"string\" && document.querySelector(container) || document.body;\n const element = resolvedContainer.appendChild(document.createElement(\"div\"));\n return new Promise((resolve, reject) => {\n const app = createApp(dialog, {\n ...props,\n // If dialog has no `container` prop passing a falsy value does nothing\n // Otherwise it is expected that `null` disables teleport and mounts dialog in place like NcDialog/NcModal\n container: null,\n onClose(...rest) {\n const payload = rest.length > 1 ? rest : rest[0];\n app.unmount();\n element.remove();\n resolve(payload);\n },\n \"onVue:unmounted\": () => {\n app.unmount();\n element.remove();\n reject(new Error(\"Dialog was unmounted without close event\"));\n }\n });\n app.mount(element);\n });\n}\nexport {\n spawnDialog\n};\n//# sourceMappingURL=index.mjs.map\n","import { a as getLanguage, e as getPlural, t as translate, d as translatePlural } from \"./chunks/translation-DoG5ZELJ.mjs\";\n/*!\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: GPL-3.0-or-later\n */\nclass GettextWrapper {\n bundle;\n constructor(pluralFunction) {\n this.bundle = {\n pluralFunction,\n translations: {}\n };\n }\n /**\n * Append new translations to the wrapper.\n *\n * This is useful if translations should be added on demand,\n * e.g. depending on component usage.\n *\n * @param bundle - The new translation bundle to append\n */\n addTranslations(bundle) {\n const dict = Object.values(bundle.translations[\"\"] ?? {}).map(({ msgid, msgid_plural: msgidPlural, msgstr }) => {\n if (msgidPlural !== void 0) {\n return [`_${msgid}_::_${msgidPlural}_`, msgstr];\n }\n return [msgid, msgstr[0]];\n });\n this.bundle.translations = {\n ...this.bundle.translations,\n ...Object.fromEntries(dict)\n };\n }\n /**\n * Get translated string (singular form), optionally with placeholders\n *\n * @param original original string to translate\n * @param placeholders map of placeholder key to value\n */\n gettext(original, placeholders = {}) {\n return translate(\"\", original, placeholders, void 0, { bundle: this.bundle });\n }\n /**\n * Get translated string with plural forms\n *\n * @param singular Singular text form\n * @param plural Plural text form to be used if `count` requires it\n * @param count The number to insert into the text\n * @param placeholders optional map of placeholder key to value\n */\n ngettext(singular, plural, count, placeholders = {}) {\n return translatePlural(\"\", singular, plural, count, placeholders, { bundle: this.bundle });\n }\n}\nclass GettextBuilder {\n debug = false;\n language = \"en\";\n translations = {};\n setLanguage(language) {\n this.language = language;\n return this;\n }\n /**\n * Try to detect locale from context with `en` as fallback value\n * This only works within a Nextcloud page context.\n *\n * @deprecated use `detectLanguage` instead.\n */\n detectLocale() {\n return this.detectLanguage();\n }\n /**\n * Try to detect locale from context with `en` as fallback value.\n * This only works within a Nextcloud page context.\n */\n detectLanguage() {\n return this.setLanguage(getLanguage().replace(\"-\", \"_\"));\n }\n /**\n * Register a new translation bundle for a specified language.\n *\n * Please note that existing translations for that language will be overwritten.\n *\n * @param language - Language this is the translation for\n * @param data - The translation bundle\n */\n addTranslation(language, data) {\n this.translations[language] = data;\n return this;\n }\n enableDebugMode() {\n this.debug = true;\n return this;\n }\n build() {\n if (this.debug) {\n console.debug(`Creating gettext instance for language ${this.language}`);\n }\n const wrapper = new GettextWrapper((n) => getPlural(n, this.language));\n if (this.language in this.translations) {\n wrapper.addTranslations(this.translations[this.language]);\n }\n return wrapper;\n }\n}\nfunction getGettextBuilder() {\n return new GettextBuilder();\n}\nexport {\n getGettextBuilder\n};\n//# sourceMappingURL=gettext.mjs.map\n","function getDefaultExportFromCjs (x) {\n\treturn x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;\n}\n\nvar browser = {exports: {}};\n\n// shim for using process in browser\nvar process = browser.exports = {};\n\n// cached from whatever global is present so that test runners that stub it\n// don't break things. But we need to wrap it in a try catch in case it is\n// wrapped in strict mode code which doesn't define any globals. It's inside a\n// function because try/catches deoptimize in certain engines.\n\nvar cachedSetTimeout;\nvar cachedClearTimeout;\n\nfunction defaultSetTimout() {\n throw new Error('setTimeout has not been defined');\n}\nfunction defaultClearTimeout () {\n throw new Error('clearTimeout has not been defined');\n}\n(function () {\n try {\n if (typeof setTimeout === 'function') {\n cachedSetTimeout = setTimeout;\n } else {\n cachedSetTimeout = defaultSetTimout;\n }\n } catch (e) {\n cachedSetTimeout = defaultSetTimout;\n }\n try {\n if (typeof clearTimeout === 'function') {\n cachedClearTimeout = clearTimeout;\n } else {\n cachedClearTimeout = defaultClearTimeout;\n }\n } catch (e) {\n cachedClearTimeout = defaultClearTimeout;\n }\n} ());\nfunction runTimeout(fun) {\n if (cachedSetTimeout === setTimeout) {\n //normal enviroments in sane situations\n return setTimeout(fun, 0);\n }\n // if setTimeout wasn't available but was latter defined\n if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {\n cachedSetTimeout = setTimeout;\n return setTimeout(fun, 0);\n }\n try {\n // when when somebody has screwed with setTimeout but no I.E. maddness\n return cachedSetTimeout(fun, 0);\n } catch(e){\n try {\n // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally\n return cachedSetTimeout.call(null, fun, 0);\n } catch(e){\n // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error\n return cachedSetTimeout.call(this, fun, 0);\n }\n }\n\n\n}\nfunction runClearTimeout(marker) {\n if (cachedClearTimeout === clearTimeout) {\n //normal enviroments in sane situations\n return clearTimeout(marker);\n }\n // if clearTimeout wasn't available but was latter defined\n if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {\n cachedClearTimeout = clearTimeout;\n return clearTimeout(marker);\n }\n try {\n // when when somebody has screwed with setTimeout but no I.E. maddness\n return cachedClearTimeout(marker);\n } catch (e){\n try {\n // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally\n return cachedClearTimeout.call(null, marker);\n } catch (e){\n // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.\n // Some versions of I.E. have different rules for clearTimeout vs setTimeout\n return cachedClearTimeout.call(this, marker);\n }\n }\n\n\n\n}\nvar queue = [];\nvar draining = false;\nvar currentQueue;\nvar queueIndex = -1;\n\nfunction cleanUpNextTick() {\n if (!draining || !currentQueue) {\n return;\n }\n draining = false;\n if (currentQueue.length) {\n queue = currentQueue.concat(queue);\n } else {\n queueIndex = -1;\n }\n if (queue.length) {\n drainQueue();\n }\n}\n\nfunction drainQueue() {\n if (draining) {\n return;\n }\n var timeout = runTimeout(cleanUpNextTick);\n draining = true;\n\n var len = queue.length;\n while(len) {\n currentQueue = queue;\n queue = [];\n while (++queueIndex < len) {\n if (currentQueue) {\n currentQueue[queueIndex].run();\n }\n }\n queueIndex = -1;\n len = queue.length;\n }\n currentQueue = null;\n draining = false;\n runClearTimeout(timeout);\n}\n\nprocess.nextTick = function (fun) {\n var args = new Array(arguments.length - 1);\n if (arguments.length > 1) {\n for (var i = 1; i < arguments.length; i++) {\n args[i - 1] = arguments[i];\n }\n }\n queue.push(new Item(fun, args));\n if (queue.length === 1 && !draining) {\n runTimeout(drainQueue);\n }\n};\n\n// v8 likes predictible objects\nfunction Item(fun, array) {\n this.fun = fun;\n this.array = array;\n}\nItem.prototype.run = function () {\n this.fun.apply(null, this.array);\n};\nprocess.title = 'browser';\nprocess.browser = true;\nprocess.env = {};\nprocess.argv = [];\nprocess.version = ''; // empty string to avoid regexp issues\nprocess.versions = {};\n\nfunction noop() {}\n\nprocess.on = noop;\nprocess.addListener = noop;\nprocess.once = noop;\nprocess.off = noop;\nprocess.removeListener = noop;\nprocess.removeAllListeners = noop;\nprocess.emit = noop;\nprocess.prependListener = noop;\nprocess.prependOnceListener = noop;\n\nprocess.listeners = function (name) { return [] };\n\nprocess.binding = function (name) {\n throw new Error('process.binding is not supported');\n};\n\nprocess.cwd = function () { return '/' };\nprocess.chdir = function (dir) {\n throw new Error('process.chdir is not supported');\n};\nprocess.umask = function() { return 0; };\n\nvar browserExports = browser.exports;\nconst process$1 = /*@__PURE__*/getDefaultExportFromCjs(browserExports);\n\nexport { process$1 as default, process$1 as process };\n//# sourceMappingURL=index.js.map\n","'use strict'\n\nconst debug = (\n typeof process === 'object' &&\n process.env &&\n process.env.NODE_DEBUG &&\n /\\bsemver\\b/i.test(process.env.NODE_DEBUG)\n) ? (...args) => console.error('SEMVER', ...args)\n : () => {}\n\nmodule.exports = debug\n","'use strict'\n\n// Note: this is the semver.org version of the spec that it implements\n// Not necessarily the package version of this code.\nconst SEMVER_SPEC_VERSION = '2.0.0'\n\nconst MAX_LENGTH = 256\nconst MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER ||\n/* istanbul ignore next */ 9007199254740991\n\n// Max safe segment length for coercion.\nconst MAX_SAFE_COMPONENT_LENGTH = 16\n\n// Max safe length for a build identifier. The max length minus 6 characters for\n// the shortest version with a build 0.0.0+BUILD.\nconst MAX_SAFE_BUILD_LENGTH = MAX_LENGTH - 6\n\nconst RELEASE_TYPES = [\n 'major',\n 'premajor',\n 'minor',\n 'preminor',\n 'patch',\n 'prepatch',\n 'prerelease',\n]\n\nmodule.exports = {\n MAX_LENGTH,\n MAX_SAFE_COMPONENT_LENGTH,\n MAX_SAFE_BUILD_LENGTH,\n MAX_SAFE_INTEGER,\n RELEASE_TYPES,\n SEMVER_SPEC_VERSION,\n FLAG_INCLUDE_PRERELEASE: 0b001,\n FLAG_LOOSE: 0b010,\n}\n","'use strict'\n\nconst {\n MAX_SAFE_COMPONENT_LENGTH,\n MAX_SAFE_BUILD_LENGTH,\n MAX_LENGTH,\n} = require('./constants')\nconst debug = require('./debug')\nexports = module.exports = {}\n\n// The actual regexps go on exports.re\nconst re = exports.re = []\nconst safeRe = exports.safeRe = []\nconst src = exports.src = []\nconst safeSrc = exports.safeSrc = []\nconst t = exports.t = {}\nlet R = 0\n\nconst LETTERDASHNUMBER = '[a-zA-Z0-9-]'\n\n// Replace some greedy regex tokens to prevent regex dos issues. These regex are\n// used internally via the safeRe object since all inputs in this library get\n// normalized first to trim and collapse all extra whitespace. The original\n// regexes are exported for userland consumption and lower level usage. A\n// future breaking change could export the safer regex only with a note that\n// all input should have extra whitespace removed.\nconst safeRegexReplacements = [\n ['\\\\s', 1],\n ['\\\\d', MAX_LENGTH],\n [LETTERDASHNUMBER, MAX_SAFE_BUILD_LENGTH],\n]\n\nconst makeSafeRegex = (value) => {\n for (const [token, max] of safeRegexReplacements) {\n value = value\n .split(`${token}*`).join(`${token}{0,${max}}`)\n .split(`${token}+`).join(`${token}{1,${max}}`)\n }\n return value\n}\n\nconst createToken = (name, value, isGlobal) => {\n const safe = makeSafeRegex(value)\n const index = R++\n debug(name, index, value)\n t[name] = index\n src[index] = value\n safeSrc[index] = safe\n re[index] = new RegExp(value, isGlobal ? 'g' : undefined)\n safeRe[index] = new RegExp(safe, isGlobal ? 'g' : undefined)\n}\n\n// The following Regular Expressions can be used for tokenizing,\n// validating, and parsing SemVer version strings.\n\n// ## Numeric Identifier\n// A single `0`, or a non-zero digit followed by zero or more digits.\n\ncreateToken('NUMERICIDENTIFIER', '0|[1-9]\\\\d*')\ncreateToken('NUMERICIDENTIFIERLOOSE', '\\\\d+')\n\n// ## Non-numeric Identifier\n// Zero or more digits, followed by a letter or hyphen, and then zero or\n// more letters, digits, or hyphens.\n\ncreateToken('NONNUMERICIDENTIFIER', `\\\\d*[a-zA-Z-]${LETTERDASHNUMBER}*`)\n\n// ## Main Version\n// Three dot-separated numeric identifiers.\n\ncreateToken('MAINVERSION', `(${src[t.NUMERICIDENTIFIER]})\\\\.` +\n `(${src[t.NUMERICIDENTIFIER]})\\\\.` +\n `(${src[t.NUMERICIDENTIFIER]})`)\n\ncreateToken('MAINVERSIONLOOSE', `(${src[t.NUMERICIDENTIFIERLOOSE]})\\\\.` +\n `(${src[t.NUMERICIDENTIFIERLOOSE]})\\\\.` +\n `(${src[t.NUMERICIDENTIFIERLOOSE]})`)\n\n// ## Pre-release Version Identifier\n// A numeric identifier, or a non-numeric identifier.\n// Non-numeric identifiers include numeric identifiers but can be longer.\n// Therefore non-numeric identifiers must go first.\n\ncreateToken('PRERELEASEIDENTIFIER', `(?:${src[t.NONNUMERICIDENTIFIER]\n}|${src[t.NUMERICIDENTIFIER]})`)\n\ncreateToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NONNUMERICIDENTIFIER]\n}|${src[t.NUMERICIDENTIFIERLOOSE]})`)\n\n// ## Pre-release Version\n// Hyphen, followed by one or more dot-separated pre-release version\n// identifiers.\n\ncreateToken('PRERELEASE', `(?:-(${src[t.PRERELEASEIDENTIFIER]\n}(?:\\\\.${src[t.PRERELEASEIDENTIFIER]})*))`)\n\ncreateToken('PRERELEASELOOSE', `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE]\n}(?:\\\\.${src[t.PRERELEASEIDENTIFIERLOOSE]})*))`)\n\n// ## Build Metadata Identifier\n// Any combination of digits, letters, or hyphens.\n\ncreateToken('BUILDIDENTIFIER', `${LETTERDASHNUMBER}+`)\n\n// ## Build Metadata\n// Plus sign, followed by one or more period-separated build metadata\n// identifiers.\n\ncreateToken('BUILD', `(?:\\\\+(${src[t.BUILDIDENTIFIER]\n}(?:\\\\.${src[t.BUILDIDENTIFIER]})*))`)\n\n// ## Full Version String\n// A main version, followed optionally by a pre-release version and\n// build metadata.\n\n// Note that the only major, minor, patch, and pre-release sections of\n// the version string are capturing groups. The build metadata is not a\n// capturing group, because it should not ever be used in version\n// comparison.\n\ncreateToken('FULLPLAIN', `v?${src[t.MAINVERSION]\n}${src[t.PRERELEASE]}?${\n src[t.BUILD]}?`)\n\ncreateToken('FULL', `^${src[t.FULLPLAIN]}$`)\n\n// like full, but allows v1.2.3 and =1.2.3, which people do sometimes.\n// also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty\n// common in the npm registry.\ncreateToken('LOOSEPLAIN', `[v=\\\\s]*${src[t.MAINVERSIONLOOSE]\n}${src[t.PRERELEASELOOSE]}?${\n src[t.BUILD]}?`)\n\ncreateToken('LOOSE', `^${src[t.LOOSEPLAIN]}$`)\n\ncreateToken('GTLT', '((?:<|>)?=?)')\n\n// Something like \"2.*\" or \"1.2.x\".\n// Note that \"x.x\" is a valid xRange identifier, meaning \"any version\"\n// Only the first item is strictly required.\ncreateToken('XRANGEIDENTIFIERLOOSE', `${src[t.NUMERICIDENTIFIERLOOSE]}|x|X|\\\\*`)\ncreateToken('XRANGEIDENTIFIER', `${src[t.NUMERICIDENTIFIER]}|x|X|\\\\*`)\n\ncreateToken('XRANGEPLAIN', `[v=\\\\s]*(${src[t.XRANGEIDENTIFIER]})` +\n `(?:\\\\.(${src[t.XRANGEIDENTIFIER]})` +\n `(?:\\\\.(${src[t.XRANGEIDENTIFIER]})` +\n `(?:${src[t.PRERELEASE]})?${\n src[t.BUILD]}?` +\n `)?)?`)\n\ncreateToken('XRANGEPLAINLOOSE', `[v=\\\\s]*(${src[t.XRANGEIDENTIFIERLOOSE]})` +\n `(?:\\\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` +\n `(?:\\\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` +\n `(?:${src[t.PRERELEASELOOSE]})?${\n src[t.BUILD]}?` +\n `)?)?`)\n\ncreateToken('XRANGE', `^${src[t.GTLT]}\\\\s*${src[t.XRANGEPLAIN]}$`)\ncreateToken('XRANGELOOSE', `^${src[t.GTLT]}\\\\s*${src[t.XRANGEPLAINLOOSE]}$`)\n\n// Coercion.\n// Extract anything that could conceivably be a part of a valid semver\ncreateToken('COERCEPLAIN', `${'(^|[^\\\\d])' +\n '(\\\\d{1,'}${MAX_SAFE_COMPONENT_LENGTH}})` +\n `(?:\\\\.(\\\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` +\n `(?:\\\\.(\\\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?`)\ncreateToken('COERCE', `${src[t.COERCEPLAIN]}(?:$|[^\\\\d])`)\ncreateToken('COERCEFULL', src[t.COERCEPLAIN] +\n `(?:${src[t.PRERELEASE]})?` +\n `(?:${src[t.BUILD]})?` +\n `(?:$|[^\\\\d])`)\ncreateToken('COERCERTL', src[t.COERCE], true)\ncreateToken('COERCERTLFULL', src[t.COERCEFULL], true)\n\n// Tilde ranges.\n// Meaning is \"reasonably at or greater than\"\ncreateToken('LONETILDE', '(?:~>?)')\n\ncreateToken('TILDETRIM', `(\\\\s*)${src[t.LONETILDE]}\\\\s+`, true)\nexports.tildeTrimReplace = '$1~'\n\ncreateToken('TILDE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAIN]}$`)\ncreateToken('TILDELOOSE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAINLOOSE]}$`)\n\n// Caret ranges.\n// Meaning is \"at least and backwards compatible with\"\ncreateToken('LONECARET', '(?:\\\\^)')\n\ncreateToken('CARETTRIM', `(\\\\s*)${src[t.LONECARET]}\\\\s+`, true)\nexports.caretTrimReplace = '$1^'\n\ncreateToken('CARET', `^${src[t.LONECARET]}${src[t.XRANGEPLAIN]}$`)\ncreateToken('CARETLOOSE', `^${src[t.LONECARET]}${src[t.XRANGEPLAINLOOSE]}$`)\n\n// A simple gt/lt/eq thing, or just \"\" to indicate \"any version\"\ncreateToken('COMPARATORLOOSE', `^${src[t.GTLT]}\\\\s*(${src[t.LOOSEPLAIN]})$|^$`)\ncreateToken('COMPARATOR', `^${src[t.GTLT]}\\\\s*(${src[t.FULLPLAIN]})$|^$`)\n\n// An expression to strip any whitespace between the gtlt and the thing\n// it modifies, so that `> 1.2.3` ==> `>1.2.3`\ncreateToken('COMPARATORTRIM', `(\\\\s*)${src[t.GTLT]\n}\\\\s*(${src[t.LOOSEPLAIN]}|${src[t.XRANGEPLAIN]})`, true)\nexports.comparatorTrimReplace = '$1$2$3'\n\n// Something like `1.2.3 - 1.2.4`\n// Note that these all use the loose form, because they'll be\n// checked against either the strict or loose comparator form\n// later.\ncreateToken('HYPHENRANGE', `^\\\\s*(${src[t.XRANGEPLAIN]})` +\n `\\\\s+-\\\\s+` +\n `(${src[t.XRANGEPLAIN]})` +\n `\\\\s*$`)\n\ncreateToken('HYPHENRANGELOOSE', `^\\\\s*(${src[t.XRANGEPLAINLOOSE]})` +\n `\\\\s+-\\\\s+` +\n `(${src[t.XRANGEPLAINLOOSE]})` +\n `\\\\s*$`)\n\n// Star ranges basically just allow anything at all.\ncreateToken('STAR', '(<|>)?=?\\\\s*\\\\*')\n// >=0.0.0 is like a star\ncreateToken('GTE0', '^\\\\s*>=\\\\s*0\\\\.0\\\\.0\\\\s*$')\ncreateToken('GTE0PRE', '^\\\\s*>=\\\\s*0\\\\.0\\\\.0-0\\\\s*$')\n","'use strict'\n\n// parse out just the options we care about\nconst looseOption = Object.freeze({ loose: true })\nconst emptyOpts = Object.freeze({ })\nconst parseOptions = options => {\n if (!options) {\n return emptyOpts\n }\n\n if (typeof options !== 'object') {\n return looseOption\n }\n\n return options\n}\nmodule.exports = parseOptions\n","'use strict'\n\nconst numeric = /^[0-9]+$/\nconst compareIdentifiers = (a, b) => {\n if (typeof a === 'number' && typeof b === 'number') {\n return a === b ? 0 : a < b ? -1 : 1\n }\n\n const anum = numeric.test(a)\n const bnum = numeric.test(b)\n\n if (anum && bnum) {\n a = +a\n b = +b\n }\n\n return a === b ? 0\n : (anum && !bnum) ? -1\n : (bnum && !anum) ? 1\n : a < b ? -1\n : 1\n}\n\nconst rcompareIdentifiers = (a, b) => compareIdentifiers(b, a)\n\nmodule.exports = {\n compareIdentifiers,\n rcompareIdentifiers,\n}\n","'use strict'\n\nconst debug = require('../internal/debug')\nconst { MAX_LENGTH, MAX_SAFE_INTEGER } = require('../internal/constants')\nconst { safeRe: re, t } = require('../internal/re')\n\nconst parseOptions = require('../internal/parse-options')\nconst { compareIdentifiers } = require('../internal/identifiers')\n\nconst isPrereleaseIdentifier = (prerelease, identifier) => {\n const identifiers = identifier.split('.')\n if (identifiers.length > prerelease.length) {\n return false\n }\n\n for (let i = 0; i < identifiers.length; i++) {\n if (compareIdentifiers(prerelease[i], identifiers[i]) !== 0) {\n return false\n }\n }\n\n return true\n}\n\nclass SemVer {\n constructor (version, options) {\n options = parseOptions(options)\n\n if (version instanceof SemVer) {\n if (version.loose === !!options.loose &&\n version.includePrerelease === !!options.includePrerelease) {\n return version\n } else {\n version = version.version\n }\n } else if (typeof version !== 'string') {\n throw new TypeError(`Invalid version. Must be a string. Got type \"${typeof version}\".`)\n }\n\n if (version.length > MAX_LENGTH) {\n throw new TypeError(\n `version is longer than ${MAX_LENGTH} characters`\n )\n }\n\n debug('SemVer', version, options)\n this.options = options\n this.loose = !!options.loose\n // this isn't actually relevant for versions, but keep it so that we\n // don't run into trouble passing this.options around.\n this.includePrerelease = !!options.includePrerelease\n\n const m = version.trim().match(options.loose ? re[t.LOOSE] : re[t.FULL])\n\n if (!m) {\n throw new TypeError(`Invalid Version: ${version}`)\n }\n\n this.raw = version\n\n // these are actually numbers\n this.major = +m[1]\n this.minor = +m[2]\n this.patch = +m[3]\n\n if (this.major > MAX_SAFE_INTEGER || this.major < 0) {\n throw new TypeError('Invalid major version')\n }\n\n if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) {\n throw new TypeError('Invalid minor version')\n }\n\n if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) {\n throw new TypeError('Invalid patch version')\n }\n\n // numberify any prerelease numeric ids\n if (!m[4]) {\n this.prerelease = []\n } else {\n this.prerelease = m[4].split('.').map((id) => {\n if (/^[0-9]+$/.test(id)) {\n const num = +id\n if (num >= 0 && num < MAX_SAFE_INTEGER) {\n return num\n }\n }\n return id\n })\n }\n\n this.build = m[5] ? m[5].split('.') : []\n this.format()\n }\n\n format () {\n this.version = `${this.major}.${this.minor}.${this.patch}`\n if (this.prerelease.length) {\n this.version += `-${this.prerelease.join('.')}`\n }\n return this.version\n }\n\n toString () {\n return this.version\n }\n\n compare (other) {\n debug('SemVer.compare', this.version, this.options, other)\n if (!(other instanceof SemVer)) {\n if (typeof other === 'string' && other === this.version) {\n return 0\n }\n other = new SemVer(other, this.options)\n }\n\n if (other.version === this.version) {\n return 0\n }\n\n return this.compareMain(other) || this.comparePre(other)\n }\n\n compareMain (other) {\n if (!(other instanceof SemVer)) {\n other = new SemVer(other, this.options)\n }\n\n if (this.major < other.major) {\n return -1\n }\n if (this.major > other.major) {\n return 1\n }\n if (this.minor < other.minor) {\n return -1\n }\n if (this.minor > other.minor) {\n return 1\n }\n if (this.patch < other.patch) {\n return -1\n }\n if (this.patch > other.patch) {\n return 1\n }\n return 0\n }\n\n comparePre (other) {\n if (!(other instanceof SemVer)) {\n other = new SemVer(other, this.options)\n }\n\n // NOT having a prerelease is > having one\n if (this.prerelease.length && !other.prerelease.length) {\n return -1\n } else if (!this.prerelease.length && other.prerelease.length) {\n return 1\n } else if (!this.prerelease.length && !other.prerelease.length) {\n return 0\n }\n\n let i = 0\n do {\n const a = this.prerelease[i]\n const b = other.prerelease[i]\n debug('prerelease compare', i, a, b)\n if (a === undefined && b === undefined) {\n return 0\n } else if (b === undefined) {\n return 1\n } else if (a === undefined) {\n return -1\n } else if (a === b) {\n continue\n } else {\n return compareIdentifiers(a, b)\n }\n } while (++i)\n }\n\n compareBuild (other) {\n if (!(other instanceof SemVer)) {\n other = new SemVer(other, this.options)\n }\n\n let i = 0\n do {\n const a = this.build[i]\n const b = other.build[i]\n debug('build compare', i, a, b)\n if (a === undefined && b === undefined) {\n return 0\n } else if (b === undefined) {\n return 1\n } else if (a === undefined) {\n return -1\n } else if (a === b) {\n continue\n } else {\n return compareIdentifiers(a, b)\n }\n } while (++i)\n }\n\n // preminor will bump the version up to the next minor release, and immediately\n // down to pre-release. premajor and prepatch work the same way.\n inc (release, identifier, identifierBase) {\n if (release.startsWith('pre')) {\n if (!identifier && identifierBase === false) {\n throw new Error('invalid increment argument: identifier is empty')\n }\n // Avoid an invalid semver results\n if (identifier) {\n const match = `-${identifier}`.match(this.options.loose ? re[t.PRERELEASELOOSE] : re[t.PRERELEASE])\n if (!match || match[1] !== identifier) {\n throw new Error(`invalid identifier: ${identifier}`)\n }\n }\n }\n\n switch (release) {\n case 'premajor':\n this.prerelease.length = 0\n this.patch = 0\n this.minor = 0\n this.major++\n this.inc('pre', identifier, identifierBase)\n break\n case 'preminor':\n this.prerelease.length = 0\n this.patch = 0\n this.minor++\n this.inc('pre', identifier, identifierBase)\n break\n case 'prepatch':\n // If this is already a prerelease, it will bump to the next version\n // drop any prereleases that might already exist, since they are not\n // relevant at this point.\n this.prerelease.length = 0\n this.inc('patch', identifier, identifierBase)\n this.inc('pre', identifier, identifierBase)\n break\n // If the input is a non-prerelease version, this acts the same as\n // prepatch.\n case 'prerelease':\n if (this.prerelease.length === 0) {\n this.inc('patch', identifier, identifierBase)\n }\n this.inc('pre', identifier, identifierBase)\n break\n case 'release':\n if (this.prerelease.length === 0) {\n throw new Error(`version ${this.raw} is not a prerelease`)\n }\n this.prerelease.length = 0\n break\n\n case 'major':\n // If this is a pre-major version, bump up to the same major version.\n // Otherwise increment major.\n // 1.0.0-5 bumps to 1.0.0\n // 1.1.0 bumps to 2.0.0\n if (\n this.minor !== 0 ||\n this.patch !== 0 ||\n this.prerelease.length === 0\n ) {\n this.major++\n }\n this.minor = 0\n this.patch = 0\n this.prerelease = []\n break\n case 'minor':\n // If this is a pre-minor version, bump up to the same minor version.\n // Otherwise increment minor.\n // 1.2.0-5 bumps to 1.2.0\n // 1.2.1 bumps to 1.3.0\n if (this.patch !== 0 || this.prerelease.length === 0) {\n this.minor++\n }\n this.patch = 0\n this.prerelease = []\n break\n case 'patch':\n // If this is not a pre-release version, it will increment the patch.\n // If it is a pre-release it will bump up to the same patch version.\n // 1.2.0-5 patches to 1.2.0\n // 1.2.0 patches to 1.2.1\n if (this.prerelease.length === 0) {\n this.patch++\n }\n this.prerelease = []\n break\n // This probably shouldn't be used publicly.\n // 1.0.0 'pre' would become 1.0.0-0 which is the wrong direction.\n case 'pre': {\n const base = Number(identifierBase) ? 1 : 0\n\n if (this.prerelease.length === 0) {\n this.prerelease = [base]\n } else {\n let i = this.prerelease.length\n while (--i >= 0) {\n if (typeof this.prerelease[i] === 'number') {\n this.prerelease[i]++\n i = -2\n }\n }\n if (i === -1) {\n // didn't increment anything\n if (identifier === this.prerelease.join('.') && identifierBase === false) {\n throw new Error('invalid increment argument: identifier already exists')\n }\n this.prerelease.push(base)\n }\n }\n if (identifier) {\n // 1.2.0-beta.1 bumps to 1.2.0-beta.2,\n // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0\n let prerelease = [identifier, base]\n if (identifierBase === false) {\n prerelease = [identifier]\n }\n if (isPrereleaseIdentifier(this.prerelease, identifier)) {\n const prereleaseBase = this.prerelease[identifier.split('.').length]\n if (isNaN(prereleaseBase)) {\n this.prerelease = prerelease\n }\n } else {\n this.prerelease = prerelease\n }\n }\n break\n }\n default:\n throw new Error(`invalid increment argument: ${release}`)\n }\n this.raw = this.format()\n if (this.build.length) {\n this.raw += `+${this.build.join('.')}`\n }\n return this\n }\n}\n\nmodule.exports = SemVer\n","'use strict'\n\nconst SemVer = require('../classes/semver')\nconst major = (a, loose) => new SemVer(a, loose).major\nmodule.exports = major\n","'use strict'\n\nconst SemVer = require('../classes/semver')\nconst parse = (version, options, throwErrors = false) => {\n if (version instanceof SemVer) {\n return version\n }\n try {\n return new SemVer(version, options)\n } catch (er) {\n if (!throwErrors) {\n return null\n }\n throw er\n }\n}\n\nmodule.exports = parse\n","'use strict'\n\nconst parse = require('./parse')\nconst valid = (version, options) => {\n const v = parse(version, options)\n return v ? v.version : null\n}\nmodule.exports = valid\n","import major from \"semver/functions/major.js\";\nimport valid from \"semver/functions/valid.js\";\n/*!\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: GPL-3.0-or-later\n */\nclass ProxyBus {\n bus;\n constructor(bus2) {\n if (typeof bus2.getVersion !== \"function\" || !valid(bus2.getVersion())) {\n console.warn(\"Proxying an event bus with an unknown or invalid version\");\n } else if (major(bus2.getVersion()) !== major(this.getVersion())) {\n console.warn(\n \"Proxying an event bus of version \" + bus2.getVersion() + \" with \" + this.getVersion()\n );\n }\n this.bus = bus2;\n }\n getVersion() {\n return \"3.3.3\";\n }\n subscribe(name, handler) {\n this.bus.subscribe(name, handler);\n }\n unsubscribe(name, handler) {\n this.bus.unsubscribe(name, handler);\n }\n emit(name, ...event) {\n this.bus.emit(name, ...event);\n }\n}\n/*!\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: GPL-3.0-or-later\n */\nclass SimpleBus {\n handlers = /* @__PURE__ */ new Map();\n getVersion() {\n return \"3.3.3\";\n }\n subscribe(name, handler) {\n this.handlers.set(\n name,\n (this.handlers.get(name) || []).concat(\n handler\n )\n );\n }\n unsubscribe(name, handler) {\n this.handlers.set(\n name,\n (this.handlers.get(name) || []).filter((h) => h !== handler)\n );\n }\n emit(name, ...event) {\n const handlers = this.handlers.get(name) || [];\n handlers.forEach((h) => {\n try {\n ;\n h(event[0]);\n } catch (e) {\n console.error(\"could not invoke event listener\", e);\n }\n });\n }\n}\n/*!\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: GPL-3.0-or-later\n */\nlet bus = null;\nfunction getBus() {\n if (bus !== null) {\n return bus;\n }\n if (typeof window === \"undefined\") {\n return new Proxy({}, {\n get: () => {\n return () => console.error(\n \"Window not available, EventBus can not be established!\"\n );\n }\n });\n }\n if (window.OC?._eventBus && typeof window._nc_event_bus === \"undefined\") {\n console.warn(\n \"found old event bus instance at OC._eventBus. Update your version!\"\n );\n window._nc_event_bus = window.OC._eventBus;\n }\n if (typeof window?._nc_event_bus !== \"undefined\") {\n bus = new ProxyBus(window._nc_event_bus);\n } else {\n bus = window._nc_event_bus = new SimpleBus();\n }\n return bus;\n}\nfunction subscribe(name, handler) {\n getBus().subscribe(name, handler);\n}\nfunction unsubscribe(name, handler) {\n getBus().unsubscribe(name, handler);\n}\nfunction emit(name, ...event) {\n getBus().emit(name, ...event);\n}\nexport {\n ProxyBus,\n SimpleBus,\n emit,\n subscribe,\n unsubscribe\n};\n//# sourceMappingURL=index.mjs.map\n","/*\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: GPL-3.0-or-later\n */\nexport default class ScopedStorage {\n static GLOBAL_SCOPE_VOLATILE = 'nextcloud_vol';\n static GLOBAL_SCOPE_PERSISTENT = 'nextcloud_per';\n scope;\n wrapped;\n constructor(scope, wrapped, persistent) {\n this.scope = `${persistent ? ScopedStorage.GLOBAL_SCOPE_PERSISTENT : ScopedStorage.GLOBAL_SCOPE_VOLATILE}_${btoa(scope)}_`;\n this.wrapped = wrapped;\n }\n scopeKey(key) {\n return `${this.scope}${key}`;\n }\n setItem(key, value) {\n this.wrapped.setItem(this.scopeKey(key), value);\n }\n getItem(key) {\n return this.wrapped.getItem(this.scopeKey(key));\n }\n removeItem(key) {\n this.wrapped.removeItem(this.scopeKey(key));\n }\n clear() {\n Object.keys(this.wrapped)\n .filter((key) => key.startsWith(this.scope))\n .map(this.wrapped.removeItem.bind(this.wrapped));\n }\n}\n","/*\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: GPL-3.0-or-later\n */\nimport ScopedStorage from \"./ScopedStorage.js\";\nexport default class StorageBuilder {\n appId;\n persisted = false;\n clearedOnLogout = false;\n constructor(appId) {\n this.appId = appId;\n }\n persist(persist = true) {\n this.persisted = persist;\n return this;\n }\n clearOnLogout(clear = true) {\n this.clearedOnLogout = clear;\n return this;\n }\n build() {\n return new ScopedStorage(this.appId, this.persisted ? window.localStorage : window.sessionStorage, !this.clearedOnLogout);\n }\n}\n","/*\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: GPL-3.0-or-later\n */\nimport ScopedStorage from \"./ScopedStorage.js\";\nimport StorageBuilder from \"./StorageBuilder.js\";\n/**\n * Get the storage builder for an app\n *\n * @param appId App ID to scope storage\n */\nexport function getBuilder(appId) {\n return new StorageBuilder(appId);\n}\n/**\n * Clear values from storage\n *\n * @param storage The storage to clear\n * @param pred Callback to check if value should be cleared\n */\nfunction clearStorage(storage, pred) {\n Object.keys(storage)\n .filter((k) => pred ? pred(k) : true)\n .map(storage.removeItem.bind(storage));\n}\n/**\n * Clear all values from all storages\n */\nexport function clearAll() {\n const storages = [\n window.sessionStorage,\n window.localStorage,\n ];\n storages.map((s) => clearStorage(s));\n}\n/**\n * Clear ony non persistent values\n */\nexport function clearNonPersistent() {\n const storages = [\n window.sessionStorage,\n window.localStorage,\n ];\n storages.map((s) => clearStorage(s, (k) => !k.startsWith(ScopedStorage.GLOBAL_SCOPE_PERSISTENT)));\n}\n","import { subscribe, emit, unsubscribe } from \"@nextcloud/event-bus\";\nimport { generateUrl } from \"@nextcloud/router\";\nimport { getBuilder } from \"@nextcloud/browser-storage\";\n_subscribeToTokenUpdates();\nfunction getRequestToken() {\n if (globalThis._nc_auth_requestToken) {\n return globalThis._nc_auth_requestToken;\n }\n if (globalThis.document) {\n return document.head.dataset.requesttoken ?? null;\n }\n return null;\n}\nfunction setRequestToken(token) {\n if (!token || typeof token !== \"string\") {\n throw new Error(\"Invalid CSRF token given\", { cause: { token } });\n }\n if (globalThis._nc_auth_requestToken === token) {\n return;\n }\n globalThis._nc_auth_requestToken = token;\n if (globalThis.document) {\n document.head.dataset.requesttoken = token;\n }\n emit(\"csrf-token-update\", { token, _internal: true });\n}\nasync function fetchRequestToken() {\n const url = generateUrl(\"/csrftoken\");\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(\"Could not fetch CSRF token from API\", { cause: response });\n }\n try {\n const { token } = await response.json();\n setRequestToken(token);\n return token;\n } catch (error) {\n throw new Error(\"Could not parse CSRF token from API response\", { cause: error });\n }\n}\nfunction onRequestTokenUpdate(observer) {\n const wrapper = async ({ token }) => {\n try {\n observer(token);\n } catch (error) {\n console.error(\"Error updating CSRF token observer\", error);\n }\n };\n subscribe(\"csrf-token-update\", wrapper);\n return () => unsubscribe(\"csrf-token-update\", wrapper);\n}\nfunction _subscribeToTokenUpdates() {\n subscribe(\"csrf-token-update\", ({ token, _internal }) => {\n if (!_internal) {\n setRequestToken(token);\n }\n });\n}\nfunction getCSPNonce() {\n const meta = document?.querySelector('meta[name=\"csp-nonce\"]');\n if (!meta) {\n const token = getRequestToken();\n return token ? btoa(token) : void 0;\n }\n return meta.nonce;\n}\n/*!\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: GPL-3.0-or-later\n */\nconst browserStorage = getBuilder(\"public\").persist().build();\nclass GuestUser {\n _displayName;\n uid;\n isAdmin;\n constructor() {\n if (!browserStorage.getItem(\"guestUid\")) {\n browserStorage.setItem(\"guestUid\", randomUUID());\n }\n this._displayName = browserStorage.getItem(\"guestNickname\") || \"\";\n this.uid = browserStorage.getItem(\"guestUid\") || randomUUID();\n this.isAdmin = false;\n subscribe(\"user:info:changed\", (guest) => {\n this._displayName = guest.displayName;\n browserStorage.setItem(\"guestNickname\", guest.displayName || \"\");\n });\n }\n get displayName() {\n return this._displayName;\n }\n set displayName(displayName) {\n this._displayName = displayName;\n browserStorage.setItem(\"guestNickname\", displayName);\n emit(\"user:info:changed\", this);\n }\n}\nlet currentUser$1;\nfunction getGuestUser() {\n if (!currentUser$1) {\n currentUser$1 = new GuestUser();\n }\n return currentUser$1;\n}\nfunction getGuestNickname() {\n return getGuestUser()?.displayName || null;\n}\nfunction setGuestNickname(nickname) {\n if (!nickname || nickname.trim().length === 0) {\n throw new Error(\"Nickname cannot be empty\");\n }\n getGuestUser().displayName = nickname;\n}\nfunction randomUUID() {\n if (globalThis.crypto?.randomUUID) {\n return globalThis.crypto.randomUUID();\n }\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = Math.random() * 16 | 0;\n const v = c === \"x\" ? r : r & 3 | 8;\n return v.toString(16);\n });\n}\nlet currentUser;\nfunction getAttribute(el, attribute) {\n if (el) {\n return el.getAttribute(attribute);\n }\n return null;\n}\nfunction getCurrentUser() {\n if (currentUser !== void 0) {\n return currentUser;\n }\n const head = document?.getElementsByTagName(\"head\")[0];\n if (!head) {\n return null;\n }\n const uid = getAttribute(head, \"data-user\");\n if (uid === null) {\n currentUser = null;\n return currentUser;\n }\n currentUser = {\n uid,\n displayName: getAttribute(head, \"data-user-displayname\"),\n isAdmin: !!window._oc_isadmin\n };\n return currentUser;\n}\nexport {\n fetchRequestToken,\n getCSPNonce,\n getCurrentUser,\n getGuestNickname,\n getGuestUser,\n getRequestToken,\n onRequestTokenUpdate,\n setGuestNickname,\n setRequestToken\n};\n//# sourceMappingURL=index.mjs.map\n","import { getCurrentUser } from \"@nextcloud/auth\";\nvar LogLevel = /* @__PURE__ */ ((LogLevel2) => {\n LogLevel2[LogLevel2[\"Debug\"] = 0] = \"Debug\";\n LogLevel2[LogLevel2[\"Info\"] = 1] = \"Info\";\n LogLevel2[LogLevel2[\"Warn\"] = 2] = \"Warn\";\n LogLevel2[LogLevel2[\"Error\"] = 3] = \"Error\";\n LogLevel2[LogLevel2[\"Fatal\"] = 4] = \"Fatal\";\n return LogLevel2;\n})(LogLevel || {});\nclass ConsoleLogger {\n context;\n constructor(context) {\n this.context = context || {};\n }\n formatMessage(message, level, context) {\n let msg = \"[\" + LogLevel[level].toUpperCase() + \"] \";\n if (context && context.app) {\n msg += context.app + \": \";\n }\n if (typeof message === \"string\") return msg + message;\n msg += `Unexpected ${message.name}`;\n if (message.message) msg += ` \"${message.message}\"`;\n if (level === LogLevel.Debug && message.stack) msg += `\n\nStack trace:\n${message.stack}`;\n return msg;\n }\n log(level, message, context) {\n if (typeof this.context?.level === \"number\" && level < this.context?.level) {\n return;\n }\n if (typeof message === \"object\" && context?.error === void 0) {\n context.error = message;\n }\n switch (level) {\n case LogLevel.Debug:\n console.debug(this.formatMessage(message, LogLevel.Debug, context), context);\n break;\n case LogLevel.Info:\n console.info(this.formatMessage(message, LogLevel.Info, context), context);\n break;\n case LogLevel.Warn:\n console.warn(this.formatMessage(message, LogLevel.Warn, context), context);\n break;\n case LogLevel.Error:\n console.error(this.formatMessage(message, LogLevel.Error, context), context);\n break;\n case LogLevel.Fatal:\n default:\n console.error(this.formatMessage(message, LogLevel.Fatal, context), context);\n break;\n }\n }\n debug(message, context) {\n this.log(LogLevel.Debug, message, Object.assign({}, this.context, context));\n }\n info(message, context) {\n this.log(LogLevel.Info, message, Object.assign({}, this.context, context));\n }\n warn(message, context) {\n this.log(LogLevel.Warn, message, Object.assign({}, this.context, context));\n }\n error(message, context) {\n this.log(LogLevel.Error, message, Object.assign({}, this.context, context));\n }\n fatal(message, context) {\n this.log(LogLevel.Fatal, message, Object.assign({}, this.context, context));\n }\n}\nfunction buildConsoleLogger(context) {\n return new ConsoleLogger(context);\n}\nclass LoggerBuilder {\n context;\n factory;\n constructor(factory) {\n this.context = {};\n this.factory = factory;\n }\n /**\n * Set the app name within the logging context\n *\n * @param appId App name\n */\n setApp(appId) {\n this.context.app = appId;\n return this;\n }\n /**\n * Set the logging level within the logging context\n *\n * @param level Logging level\n */\n setLogLevel(level) {\n this.context.level = level;\n return this;\n }\n /* eslint-disable jsdoc/no-undefined-types */\n /**\n * Set the user id within the logging context\n * @param uid User ID\n * @see {@link detectUser}\n */\n /* eslint-enable jsdoc/no-undefined-types */\n setUid(uid) {\n this.context.uid = uid;\n return this;\n }\n /**\n * Detect the currently logged in user and set the user id within the logging context\n */\n detectUser() {\n const user = getCurrentUser();\n if (user !== null) {\n this.context.uid = user.uid;\n }\n return this;\n }\n /**\n * Detect and use logging level configured in nextcloud config\n */\n detectLogLevel() {\n const self = this;\n const onLoaded = () => {\n if (document.readyState === \"complete\" || document.readyState === \"interactive\") {\n self.context.level = window._oc_config?.loglevel ?? LogLevel.Warn;\n if (window._oc_debug) {\n self.context.level = LogLevel.Debug;\n }\n document.removeEventListener(\"readystatechange\", onLoaded);\n } else {\n document.addEventListener(\"readystatechange\", onLoaded);\n }\n };\n onLoaded();\n return this;\n }\n /** Build a logger using the logging context and factory */\n build() {\n if (this.context.level === void 0) {\n this.detectLogLevel();\n }\n return this.factory(this.context);\n }\n}\nfunction getLoggerBuilder() {\n return new LoggerBuilder(buildConsoleLogger);\n}\nfunction getLogger() {\n return getLoggerBuilder().build();\n}\nexport {\n LogLevel,\n getLogger,\n getLoggerBuilder\n};\n//# sourceMappingURL=index.mjs.map\n","/*!\n * Toastify js 1.12.0\n * https://github.com/apvarun/toastify-js\n * @license MIT licensed\n *\n * Copyright (C) Varun A P\n */\n(function(root, factory) {\n if (typeof module === \"object\" && module.exports) {\n module.exports = factory();\n } else {\n root.Toastify = factory();\n }\n})(this, function(global) {\n // Object initialization\n var Toastify = function(options) {\n // Returning a new init object\n return new Toastify.lib.init(options);\n },\n // Library version\n version = \"1.12.0\";\n\n // Set the default global options\n Toastify.defaults = {\n oldestFirst: true,\n text: \"Toastify is awesome!\",\n node: undefined,\n duration: 3000,\n selector: undefined,\n callback: function () {\n },\n destination: undefined,\n newWindow: false,\n close: false,\n gravity: \"toastify-top\",\n positionLeft: false,\n position: '',\n backgroundColor: '',\n avatar: \"\",\n className: \"\",\n stopOnFocus: true,\n onClick: function () {\n },\n offset: {x: 0, y: 0},\n escapeMarkup: true,\n ariaLive: 'polite',\n style: {background: ''}\n };\n\n // Defining the prototype of the object\n Toastify.lib = Toastify.prototype = {\n toastify: version,\n\n constructor: Toastify,\n\n // Initializing the object with required parameters\n init: function(options) {\n // Verifying and validating the input object\n if (!options) {\n options = {};\n }\n\n // Creating the options object\n this.options = {};\n\n this.toastElement = null;\n\n // Validating the options\n this.options.text = options.text || Toastify.defaults.text; // Display message\n this.options.node = options.node || Toastify.defaults.node; // Display content as node\n this.options.duration = options.duration === 0 ? 0 : options.duration || Toastify.defaults.duration; // Display duration\n this.options.selector = options.selector || Toastify.defaults.selector; // Parent selector\n this.options.callback = options.callback || Toastify.defaults.callback; // Callback after display\n this.options.destination = options.destination || Toastify.defaults.destination; // On-click destination\n this.options.newWindow = options.newWindow || Toastify.defaults.newWindow; // Open destination in new window\n this.options.close = options.close || Toastify.defaults.close; // Show toast close icon\n this.options.gravity = options.gravity === \"bottom\" ? \"toastify-bottom\" : Toastify.defaults.gravity; // toast position - top or bottom\n this.options.positionLeft = options.positionLeft || Toastify.defaults.positionLeft; // toast position - left or right\n this.options.position = options.position || Toastify.defaults.position; // toast position - left or right\n this.options.backgroundColor = options.backgroundColor || Toastify.defaults.backgroundColor; // toast background color\n this.options.avatar = options.avatar || Toastify.defaults.avatar; // img element src - url or a path\n this.options.className = options.className || Toastify.defaults.className; // additional class names for the toast\n this.options.stopOnFocus = options.stopOnFocus === undefined ? Toastify.defaults.stopOnFocus : options.stopOnFocus; // stop timeout on focus\n this.options.onClick = options.onClick || Toastify.defaults.onClick; // Callback after click\n this.options.offset = options.offset || Toastify.defaults.offset; // toast offset\n this.options.escapeMarkup = options.escapeMarkup !== undefined ? options.escapeMarkup : Toastify.defaults.escapeMarkup;\n this.options.ariaLive = options.ariaLive || Toastify.defaults.ariaLive;\n this.options.style = options.style || Toastify.defaults.style;\n if(options.backgroundColor) {\n this.options.style.background = options.backgroundColor;\n }\n\n // Returning the current object for chaining functions\n return this;\n },\n\n // Building the DOM element\n buildToast: function() {\n // Validating if the options are defined\n if (!this.options) {\n throw \"Toastify is not initialized\";\n }\n\n // Creating the DOM object\n var divElement = document.createElement(\"div\");\n divElement.className = \"toastify on \" + this.options.className;\n\n // Positioning toast to left or right or center\n if (!!this.options.position) {\n divElement.className += \" toastify-\" + this.options.position;\n } else {\n // To be depreciated in further versions\n if (this.options.positionLeft === true) {\n divElement.className += \" toastify-left\";\n console.warn('Property `positionLeft` will be depreciated in further versions. Please use `position` instead.')\n } else {\n // Default position\n divElement.className += \" toastify-right\";\n }\n }\n\n // Assigning gravity of element\n divElement.className += \" \" + this.options.gravity;\n\n if (this.options.backgroundColor) {\n // This is being deprecated in favor of using the style HTML DOM property\n console.warn('DEPRECATION NOTICE: \"backgroundColor\" is being deprecated. Please use the \"style.background\" property.');\n }\n\n // Loop through our style object and apply styles to divElement\n for (var property in this.options.style) {\n divElement.style[property] = this.options.style[property];\n }\n\n // Announce the toast to screen readers\n if (this.options.ariaLive) {\n divElement.setAttribute('aria-live', this.options.ariaLive)\n }\n\n // Adding the toast message/node\n if (this.options.node && this.options.node.nodeType === Node.ELEMENT_NODE) {\n // If we have a valid node, we insert it\n divElement.appendChild(this.options.node)\n } else {\n if (this.options.escapeMarkup) {\n divElement.innerText = this.options.text;\n } else {\n divElement.innerHTML = this.options.text;\n }\n\n if (this.options.avatar !== \"\") {\n var avatarElement = document.createElement(\"img\");\n avatarElement.src = this.options.avatar;\n\n avatarElement.className = \"toastify-avatar\";\n\n if (this.options.position == \"left\" || this.options.positionLeft === true) {\n // Adding close icon on the left of content\n divElement.appendChild(avatarElement);\n } else {\n // Adding close icon on the right of content\n divElement.insertAdjacentElement(\"afterbegin\", avatarElement);\n }\n }\n }\n\n // Adding a close icon to the toast\n if (this.options.close === true) {\n // Create a span for close element\n var closeElement = document.createElement(\"button\");\n closeElement.type = \"button\";\n closeElement.setAttribute(\"aria-label\", \"Close\");\n closeElement.className = \"toast-close\";\n closeElement.innerHTML = \"✖\";\n\n // Triggering the removal of toast from DOM on close click\n closeElement.addEventListener(\n \"click\",\n function(event) {\n event.stopPropagation();\n this.removeElement(this.toastElement);\n window.clearTimeout(this.toastElement.timeOutValue);\n }.bind(this)\n );\n\n //Calculating screen width\n var width = window.innerWidth > 0 ? window.innerWidth : screen.width;\n\n // Adding the close icon to the toast element\n // Display on the right if screen width is less than or equal to 360px\n if ((this.options.position == \"left\" || this.options.positionLeft === true) && width > 360) {\n // Adding close icon on the left of content\n divElement.insertAdjacentElement(\"afterbegin\", closeElement);\n } else {\n // Adding close icon on the right of content\n divElement.appendChild(closeElement);\n }\n }\n\n // Clear timeout while toast is focused\n if (this.options.stopOnFocus && this.options.duration > 0) {\n var self = this;\n // stop countdown\n divElement.addEventListener(\n \"mouseover\",\n function(event) {\n window.clearTimeout(divElement.timeOutValue);\n }\n )\n // add back the timeout\n divElement.addEventListener(\n \"mouseleave\",\n function() {\n divElement.timeOutValue = window.setTimeout(\n function() {\n // Remove the toast from DOM\n self.removeElement(divElement);\n },\n self.options.duration\n )\n }\n )\n }\n\n // Adding an on-click destination path\n if (typeof this.options.destination !== \"undefined\") {\n divElement.addEventListener(\n \"click\",\n function(event) {\n event.stopPropagation();\n if (this.options.newWindow === true) {\n window.open(this.options.destination, \"_blank\");\n } else {\n window.location = this.options.destination;\n }\n }.bind(this)\n );\n }\n\n if (typeof this.options.onClick === \"function\" && typeof this.options.destination === \"undefined\") {\n divElement.addEventListener(\n \"click\",\n function(event) {\n event.stopPropagation();\n this.options.onClick();\n }.bind(this)\n );\n }\n\n // Adding offset\n if(typeof this.options.offset === \"object\") {\n\n var x = getAxisOffsetAValue(\"x\", this.options);\n var y = getAxisOffsetAValue(\"y\", this.options);\n\n var xOffset = this.options.position == \"left\" ? x : \"-\" + x;\n var yOffset = this.options.gravity == \"toastify-top\" ? y : \"-\" + y;\n\n divElement.style.transform = \"translate(\" + xOffset + \",\" + yOffset + \")\";\n\n }\n\n // Returning the generated element\n return divElement;\n },\n\n // Displaying the toast\n showToast: functi
SPDX-FileCopyrightText: 2026, 2026 Nextcloud GmbH and Nextcloud contributors\r\n * SPDX-License-Identifier: AGPL-3.0-or-later\r\n */\r\n\r\nimport type { RouteRecordRaw } from 'vue-router'\r\n\r\nimport { createRouter, createWebHashHistory } from 'vue-router'\r\nimport Dashboard from './views/Dashboard.vue'\r\n\r\nconst routes: RouteRecordRaw[] = [\r\n\t{ path: '/', name: 'dashboard', component: Dashboard },\r\n]\r\n\r\n// Hash history keeps the SPA from clashing with the surrounding\r\n// admin-settings page's own routing.\r\nexport default createRouter({\r\n\thistory: createWebHashHistory(),\r\n\troutes,\r\n})\r\n","/**\r\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\r\n * SPDX-License-Identifier: AGPL-3.0-or-later\r\n */\r\n\r\nimport { translate, translatePlural } from '@nextcloud/l10n'\r\nimport { createPinia } from 'pinia'\r\nimport { createApp } from 'vue'\r\nimport App from './App.vue'\r\nimport router from './router'\r\n\r\nimport './styles/tokens.scss'\r\n\r\nconst mount = document.getElementById('dbdoctor-app')\r\nif (mount === null) {\r\n\t// Settings page hasn't rendered the mount node yet — bail out\r\n\t// quietly. Re-running the script on a different page (e.g. when\r\n\t// Nextcloud admin section caches scripts) wouldn't crash.\r\n\t// eslint-disable-next-line no-console\r\n\tconsole.warn('[dbdoctor] mount node #dbdoctor-app not found; skipping app boot.')\r\n} else {\r\n\tconst app = createApp(App)\r\n\r\n\t// Make `t` / `n` available on every component without per-component\r\n\t// imports — matches the convention in the activity / webtrack apps.\r\n\tapp.config.globalProperties.t = translate\r\n\tapp.config.globalProperties.n = translatePlural\r\n\r\n\tapp.use(createPinia())\r\n\tapp.use(router)\r\n\tapp.mount(mount)\r\n}\r\n"],"file":"dbdoctor-main.mjs"
SPDX-FileCopyrightText: 2026, 2026, 2026, 2026, 4000, 2026, 2026, 2026, 2026, 2026, 1024, 2026 Nextcloud GmbH and Nextcloud contributors\r\n - SPDX-License-Identifier: AGPL-3.0-or-later\r\n-->\r\n\r\n\t\r\n\t\r\n\t\t\r\n\t\r\n\r\n\r\n\r\n\r\n\r\n","import '../assets/NcEmptyContent-DJMDuGVz.css';\nimport { defineComponent, openBlock, createElementBlock, unref, renderSlot, createCommentVNode, createTextVNode, toDisplayString } from \"vue\";\nimport { c as createElementId } from \"./createElementId-DhjFt1I9.mjs\";\nimport { _ as _export_sfc } from \"./_plugin-vue_export-helper-1tPrXgE0.mjs\";\nconst _hoisted_1 = [\"aria-labelledby\"];\nconst _hoisted_2 = {\n key: 0,\n class: \"empty-content__icon\",\n \"aria-hidden\": \"true\"\n};\nconst _hoisted_3 = [\"id\"];\nconst _hoisted_4 = {\n key: 2,\n class: \"empty-content__description\"\n};\nconst _hoisted_5 = {\n key: 3,\n class: \"empty-content__action\"\n};\nconst _sfc_main = /* @__PURE__ */ defineComponent({\n __name: \"NcEmptyContent\",\n props: {\n description: { default: \"\" },\n name: { default: \"\" }\n },\n setup(__props) {\n const nameId = createElementId();\n return (_ctx, _cache) => {\n return openBlock(), createElementBlock(\"div\", {\n \"aria-labelledby\": unref(nameId),\n class: \"empty-content\",\n role: \"note\"\n }, [\n _ctx.$slots.icon ? (openBlock(), createElementBlock(\"div\", _hoisted_2, [\n renderSlot(_ctx.$slots, \"icon\", {}, void 0, true)\n ])) : createCommentVNode(\"\", true),\n __props.name !== \"\" || _ctx.$slots.name ? (openBlock(), createElementBlock(\"div\", {\n key: 1,\n id: unref(nameId),\n class: \"empty-content__name\"\n }, [\n renderSlot(_ctx.$slots, \"name\", {}, () => [\n createTextVNode(toDisplayString(__props.name), 1)\n ], true)\n ], 8, _hoisted_3)) : createCommentVNode(\"\", true),\n __props.description !== \"\" || _ctx.$slots.description ? (openBlock(), createElementBlock(\"p\", _hoisted_4, [\n renderSlot(_ctx.$slots, \"description\", {}, () => [\n createTextVNode(toDisplayString(__props.description), 1)\n ], true)\n ])) : createCommentVNode(\"\", true),\n _ctx.$slots.action ? (openBlock(), createElementBlock(\"div\", _hoisted_5, [\n renderSlot(_ctx.$slots, \"action\", {}, void 0, true)\n ])) : createCommentVNode(\"\", true)\n ], 8, _hoisted_1);\n };\n }\n});\nconst NcEmptyContent = /* @__PURE__ */ _export_sfc(_sfc_main, [[\"__scopeId\", \"data-v-8609a4c1\"]]);\nexport {\n NcEmptyContent as N\n};\n//# sourceMappingURL=NcEmptyContent-CGAPqk4S.mjs.map\n","\r\n\r\n\t
\r\n\t\t\r\n\t
\r\n\r\n\r\n\r\n\r\n\r\n","\r\n\r\n\t\r\n\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t{{ cta }}\r\n\t\t\t\r\n\t\t\r\n\t\r\n\r\n\r\n\r\n\r\n\r\n","/**\r\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\r\n * SPDX-License-Identifier: AGPL-3.0-or-later\r\n */\r\n\r\nimport type {\r\n\tAuditRow,\r\n\tLiveMetrics,\r\n\tRevertedFix,\r\n\tRunResult,\r\n\tScorePoint,\r\n\tSeriesPoint,\r\n\tSettings,\r\n} from './types'\r\n\r\nimport axios from '@nextcloud/axios'\r\nimport { generateOcsUrl } from '@nextcloud/router'\r\n\r\ninterface OcsEnvelope {\r\n\tocs: { meta: unknown, data: T }\r\n}\r\n\r\nfunction url(path: string): string {\r\n\t// Force JSON: OCS endpoints default to XML when no format is set\r\n\t// and @nextcloud/axios doesn't always negotiate it via Accept.\r\n\tconst base = generateOcsUrl('apps/dbdoctor/api/v1' + path)\r\n\treturn base + (base.includes('?') ? '&' : '?') + 'format=json'\r\n}\r\n\r\n// Some installs serve OCS responses with a content type axios doesn't\r\n// auto-parse as JSON. Forcing the Accept header and a manual JSON\r\n// transform on the response side keeps `res.data` an object even when\r\n// the server's Content-Type is text/plain or text/xml.\r\nconst REQUEST_OPTS = {\r\n\theaders: { Accept: 'application/json' },\r\n\tresponseType: 'json' as const,\r\n\ttransformResponse: [\r\n\t\t(data: unknown): unknown => {\r\n\t\t\tif (typeof data === 'string') {\r\n\t\t\t\ttry {\r\n\t\t\t\t\treturn JSON.parse(data)\r\n\t\t\t\t} catch {\r\n\t\t\t\t\treturn data\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn data\r\n\t\t},\r\n\t],\r\n}\r\n\r\n// Surfaces a useful message when the backend returns something that\r\n// isn't an OCS envelope (PHP fatal, HTML 500 page, redirect to login).\r\n// Without this, callers see a cryptic \"undefined is not an object\"\r\n// when they try to read `.ocs.data` on a non-OCS body.\r\nfunction unwrap(body: unknown, label: string): T {\r\n\tif (body === null || typeof body !== 'object') {\r\n\t\tconst preview = typeof body === 'string'\r\n\t\t\t? body.slice(0, 200)\r\n\t\t\t: String(body)\r\n\t\tthrow new Error(`${label}: server returned a non-JSON response (got: ${preview || 'empty body'}). Check the server log.`)\r\n\t}\r\n\tconst ocs = (body as { ocs?: { data?: T } }).ocs\r\n\tif (!ocs || !('data' in ocs)) {\r\n\t\tthrow new Error(`${label}: response is missing the OCS envelope. The endpoint may have errored before reaching the controller.`)\r\n\t}\r\n\treturn ocs.data as T\r\n}\r\n\r\nexport async function getLatest(): Promise {\r\n\tconst res = await axios.get>(url('/check/latest'), REQUEST_OPTS)\r\n\t// 204 No Content gives axios an empty body; treat as \"no run yet\".\r\n\tif (res.status === 204 || res.data === '' || res.data === null) {\r\n\t\treturn null\r\n\t}\r\n\treturn unwrap(res.data, 'getLatest')\r\n}\r\n\r\nexport async function runCheck(): Promise {\r\n\tconst res = await axios.post>(url('/check/run'), null, REQUEST_OPTS)\r\n\treturn unwrap(res.data, 'runCheck')\r\n}\r\n\r\nexport interface PingResult {\r\n\telapsedMs: number\r\n\tok: boolean\r\n\terror?: string\r\n}\r\n\r\n// 1 Hz poll target. We pass a per-request timeout shorter than the\r\n// poll interval so a stalled DB doesn't queue up backed-up requests\r\n// behind each other; the chart shows the timeout as a sentinel point.\r\nconst PING_TIMEOUT_MS = 800\r\n\r\nexport async function pingDatabase(): Promise {\r\n\tconst res = await axios.get>(\r\n\t\turl('/check/ping'),\r\n\t\t{ ...REQUEST_OPTS, timeout: PING_TIMEOUT_MS },\r\n\t)\r\n\treturn unwrap(res.data, 'pingDatabase')\r\n}\r\n\r\nexport async function getHistory(ruleId: string, days: number = 30): Promise {\r\n\tconst res = await axios.get>(\r\n\t\turl('/check/history'),\r\n\t\t{ ...REQUEST_OPTS, params: { ruleId, days } },\r\n\t)\r\n\treturn unwrap<{ series: SeriesPoint[] }>(res.data, 'getHistory').series\r\n}\r\n\r\nexport async function getScoreHistory(days: number = 30): Promise {\r\n\tconst res = await axios.get>(\r\n\t\turl('/check/score-history'),\r\n\t\t{ ...REQUEST_OPTS, params: { days } },\r\n\t)\r\n\treturn unwrap<{ series: ScorePoint[] }>(res.data, 'getScoreHistory').series\r\n}\r\n\r\nexport async function getRevertedFixes(): Promise {\r\n\tconst res = await axios.get>(\r\n\t\turl('/check/reverted-fixes'),\r\n\t\tREQUEST_OPTS,\r\n\t)\r\n\treturn unwrap<{ reverted: RevertedFix[] }>(res.data, 'getRevertedFixes').reverted\r\n}\r\n\r\nexport async function applyChange(ruleId: string, variable: string, value: string): Promise<{\r\n\tsuccess: boolean\r\n\toldValue: string | null\r\n\tnewValue: string | null\r\n\terror?: string\r\n}> {\r\n\ttry {\r\n\t\tconst res = await axios.post>(url('/apply'), { ruleId, variable, value }, REQUEST_OPTS)\r\n\t\treturn unwrap(res.data, 'applyChange')\r\n\t} catch (e) {\r\n\t\t// A non-2xx (bad value → 400, or an unexpected server fault) still\r\n\t\t// carries a useful message in the OCS body. Surface it as a\r\n\t\t// failed result the dialog can render, rather than letting axios's\r\n\t\t// generic \"Request failed with status code NNN\" bubble up.\r\n\t\tconst body = (e as { response?: { data?: unknown } })?.response?.data\r\n\t\tconst data = (body as { ocs?: { data?: { error?: string } } })?.ocs?.data\r\n\t\tconst meta = (body as { ocs?: { meta?: { message?: string } } })?.ocs?.meta\r\n\t\tconst message = data?.error || meta?.message || (e instanceof Error ? e.message : String(e))\r\n\t\treturn { success: false, oldValue: null, newValue: null, error: message }\r\n\t}\r\n}\r\n\r\nexport async function getSettings(): Promise {\r\n\tconst res = await axios.get>(url('/settings'), REQUEST_OPTS)\r\n\treturn unwrap(res.data, 'getSettings')\r\n}\r\n\r\nexport async function updateSettings(patch: Partial<{\r\n\thost: string\r\n\tport: number\r\n\tuser: string\r\n\tdatabase: string\r\n\tdriver: 'pdo_mysql' | 'pdo_pgsql'\r\n\tpassword: string\r\n\tclearPassword: boolean\r\n}>): Promise {\r\n\tconst res = await axios.put>(url('/settings'), patch, REQUEST_OPTS)\r\n\treturn unwrap(res.data, 'updateSettings')\r\n}\r\n\r\nexport async function testConnection(payload: {\r\n\thost: string\r\n\tport: number\r\n\tuser: string\r\n\tpassword: string\r\n\tdatabase: string\r\n\tdriver: 'pdo_mysql' | 'pdo_pgsql'\r\n}): Promise<{ ok: boolean, message: string }> {\r\n\tconst res = await axios.post>(\r\n\t\turl('/settings/test-connection'),\r\n\t\tpayload,\r\n\t\tREQUEST_OPTS,\r\n\t)\r\n\treturn unwrap(res.data, 'testConnection')\r\n}\r\n\r\nexport async function getAudit(limit: number = 50): Promise {\r\n\tconst res = await axios.get>(url('/audit'), { ...REQUEST_OPTS, params: { limit } })\r\n\treturn unwrap<{ entries: AuditRow[] }>(res.data, 'getAudit').entries\r\n}\r\n\r\n// ── Live metrics (dashboard tiles) ──────────────────────────────────\r\n\r\n// Axios reports HTTP failures as a generic \"Request failed with status\r\n// code NNN\". When the body is an OCS error envelope the real cause\r\n// (e.g. the SQL error the controller wrapped) is in ocs.meta.message —\r\n// surface that instead.\r\nfunction toReadableError(e: unknown, label: string): Error {\r\n\tconst body = (e as { response?: { data?: unknown } })?.response?.data\r\n\tconst message = (body as { ocs?: { meta?: { message?: string } } })?.ocs?.meta?.message\r\n\tif (typeof message === 'string' && message !== '') {\r\n\t\treturn new Error(`${label}: ${message}`)\r\n\t}\r\n\treturn e instanceof Error ? e : new Error(String(e))\r\n}\r\n\r\nasync function ocsGet(label: string, path: string, opts: object = {}): Promise {\r\n\ttry {\r\n\t\tconst res = await axios.get>(url(path), { ...REQUEST_OPTS, ...opts })\r\n\t\treturn unwrap(res.data, label)\r\n\t} catch (e) {\r\n\t\tthrow toReadableError(e, label)\r\n\t}\r\n}\r\n\r\nexport async function getLiveMetrics(): Promise {\r\n\treturn ocsGet('getLiveMetrics', '/insights/metrics', { timeout: })\r\n}\r\n","/**\r\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\r\n * SPDX-License-Identifier: AGPL-3.0-or-later\r\n */\r\n\r\nconst APP = 'dbdoctor'\r\n\r\n// Lightweight logger. We keep this thin (no external deps) so the\r\n// bundle stays small. Output goes through `console` which Nextcloud\r\n// already wires to its log surfaces in dev builds.\r\nexport default {\r\n\tdebug(msg: string, data?: unknown): void {\r\n\t\tif (typeof console !== 'undefined') { console.debug(`[${APP}] ${msg}`, data ?? '') }\r\n\t},\r\n\tinfo(msg: string, data?: unknown): void {\r\n\t\tif (typeof console !== 'undefined') { console.info(`[${APP}] ${msg}`, data ?? '') }\r\n\t},\r\n\twarn(msg: string, data?: unknown): void {\r\n\t\tif (typeof console !== 'undefined') { console.warn(`[${APP}] ${msg}`, data ?? '') }\r\n\t},\r\n\terror(msg: string, data?: unknown): void {\r\n\t\tif (typeof console !== 'undefined') { console.error(`[${APP}] ${msg}`, data ?? '') }\r\n\t},\r\n}\r\n","/**\r\n * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors\r\n * SPDX-License-Identifier: AGPL-3.0-or-later\r\n */\r\n\r\nimport { defineStore } from 'pinia'\r\nimport { computed, ref } from 'vue'\r\nimport { pingDatabase } from '../api/client'\r\nimport logger from '../utils/logger'\r\n\r\n/**\r\n * Live database-latency telemetry, shared between the LatencyChart and\r\n * the score-card Heartbeat.\r\n *\r\n * Two consumers needed a single source: putting the polling here keeps\r\n * them in lockstep (the heartbeat's BPM matches the chart's last point\r\n * to the millisecond) and lets us refcount subscribers — polling only\r\n * runs when at least one consumer is mounted, and pauses when the tab\r\n * is hidden.\r\n *\r\n * Smoothing: we expose both the raw last reading (`currentMs`) for the\r\n * chart's \"now\" readout and an exponentially-weighted moving average\r\n * (`smoothedMs`) for the heartbeat — a single 200ms spike shouldn't\r\n * make the mascot's heart race for 30 seconds.\r\n */\r\nexport const useLatencyStore = defineStore('dbdoctor/latency', () => {\r\n\tconst MAX_SAMPLES = 60\r\n\tconst POLL_INTERVAL_MS = 1000\r\n\t// EMA weight on the newest sample. 0.25 trades roughly 4 samples\r\n\t// of inertia for a still-responsive smoothed trace.\r\n\tconst EMA_ALPHA = 0.25\r\n\r\n\t// NaN entries denote a failed / timed-out ping; consumers render\r\n\t// them as a gap rather than smoothing them away.\r\n\tconst samples = ref([])\r\n\tconst ema = ref(null)\r\n\r\n\tconst subscribers = ref(0)\r\n\tlet timer: number | null = null\r\n\tlet inflight = false\r\n\tlet visibilityBound = false\r\n\r\n\tconst currentMs = computed(() => {\r\n\t\tconst v = samples.value[samples.value.length - 1]\r\n\t\treturn Number.isFinite(v) ? v : null\r\n\t})\r\n\tconst smoothedMs = computed(() => ema.value)\r\n\r\n\tasync function tick(): Promise {\r\n\t\t// Don't pile up requests if the previous one is still in flight\r\n\t\t// — better to drop a sample than queue them.\r\n\t\tif (inflight) { return }\r\n\t\tinflight = true\r\n\t\ttry {\r\n\t\t\tconst r = await pingDatabase()\r\n\t\t\tconst v = r.ok ? r.elapsedMs : Number.NaN\r\n\t\t\tsamples.value.push(v)\r\n\t\t\tif (Number.isFinite(v)) {\r\n\t\t\t\tema.value = ema.value === null\r\n\t\t\t\t\t? v\r\n\t\t\t\t\t: EMA_ALPHA * v + (1 - EMA_ALPHA) * ema.value\r\n\t\t\t}\r\n\t\t} catch (e) {\r\n\t\t\tsamples.value.push(Number.NaN)\r\n\t\t\tlogger.debug('latency ping failed', e)\r\n\t\t} finally {\r\n\t\t\twhile (samples.value.length > MAX_SAMPLES) { samples.value.shift() }\r\n\t\t\tinflight = false\r\n\t\t}\r\n\t}\r\n\r\n\tfunction start(): void {\r\n\t\tif (timer !== null) { return }\r\n\t\tvoid tick()\r\n\t\ttimer = window.setInterval(() => { void tick() }, POLL_INTERVAL_MS)\r\n\t}\r\n\r\n\tfunction stop(): void {\r\n\t\tif (timer !== null) {\r\n\t\t\twindow.clearInterval(timer)\r\n\t\t\ttimer = null\r\n\t\t}\r\n\t}\r\n\r\n\tfunction onVisibility(): void {\r\n\t\tif (subscribers.value === 0) { return }\r\n\t\tif (document.hidden) {\r\n\t\t\tstop()\r\n\t\t} else {\r\n\t\t\tstart()\r\n\t\t}\r\n\t}\r\n\r\n\tfunction bindVisibility(): void {\r\n\t\tif (visibilityBound) { return }\r\n\t\tdocument.addEventListener('visibilitychange', onVisibility)\r\n\t\tvisibilityBound = true\r\n\t}\r\n\r\n\tfunction unbindVisibility(): void {\r\n\t\tif (!visibilityBound) { return }\r\n\t\tdocument.removeEventListener('visibilitychange', onVisibility)\r\n\t\tvisibilityBound = false\r\n\t}\r\n\r\n\t/**\r\n\t * Refcounted lifecycle. The first subscriber starts polling; the\r\n\t * last unsubscribe stops it. Components should call subscribe()\r\n\t * in onMounted and unsubscribe() in onBeforeUnmount.\r\n\t */\r\n\tfunction subscribe(): void {\r\n\t\tsubscribers.value++\r\n\t\tif (subscribers.value === 1) {\r\n\t\t\tbindVisibility()\r\n\t\t\tif (!document.hidden) { start() }\r\n\t\t}\r\n\t}\r\n\r\n\tfunction unsubscribe(): void {\r\n\t\tsubscribers.value = Math.max(0, subscribers.value - 1)\r\n\t\tif (subscribers.value === 0) {\r\n\t\t\tstop()\r\n\t\t\tunbindVisibility()\r\n\t\t\t// Reset the rolling state so a future mount starts cleanly.\r\n\t\t\tsamples.value = []\r\n\t\t\tema.value = null\r\n\t\t}\r\n\t}\r\n\r\n\treturn {\r\n\t\t// state\r\n\t\tsamples,\r\n\t\t// derived\r\n\t\tcurrentMs,\r\n\t\tsmoothedMs,\r\n\t\t// actions\r\n\t\tsubscribe,\r\n\t\tunsubscribe,\r\n\t}\r\n})\r\n","\r\n\r\n\t\r\n\t\t\r\n\t\t\t