diff --git a/server/app/data.yml b/server/app/data.yml index cb66ecbd..96aacea1 100644 --- a/server/app/data.yml +++ b/server/app/data.yml @@ -71,8 +71,9 @@ defaults: authJwtRenewablePeriod: '14d' enforceSameOriginReferrerPolicy: true flags: - ldapdebug: false - sqllog: false + experimental: false + authDebug: false + sqlLog: false # System defaults channel: NEXT cors: diff --git a/server/core/config.js b/server/core/config.js index 6108a54a..bd535147 100644 --- a/server/core/config.js +++ b/server/core/config.js @@ -133,7 +133,7 @@ module.exports = { * Apply Dev Flags */ async applyFlags() { - WIKI.db.knex.client.config.debug = WIKI.config.flags.sqllog + WIKI.db.knex.client.config.debug = WIKI.config.flags.sqlLog }, /** diff --git a/server/db/migrations/3.0.0.js b/server/db/migrations/3.0.0.js index c7e4e3eb..64217a2b 100644 --- a/server/db/migrations/3.0.0.js +++ b/server/db/migrations/3.0.0.js @@ -285,7 +285,7 @@ exports.up = async knex => { table.specificType('folderPath', 'ltree').index().index('tree_folderpath_gist_index', { indexType: 'GIST' }) table.string('fileName').notNullable().index() table.enu('type', ['folder', 'page', 'asset']).notNullable().index() - table.uuid('targetId').index() + table.string('localeCode', 5).notNullable().defaultTo('en').index() table.string('title').notNullable() table.jsonb('meta').notNullable().defaultTo('{}') table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now()) @@ -372,6 +372,7 @@ exports.up = async knex => { table.string('localeCode', 5).references('code').inTable('locales').index() table.uuid('authorId').notNullable().references('id').inTable('users').index() table.uuid('creatorId').notNullable().references('id').inTable('users').index() + table.uuid('ownerId').notNullable().references('id').inTable('users').index() table.uuid('siteId').notNullable().references('id').inTable('sites').index() }) .table('storage', table => { @@ -439,6 +440,14 @@ exports.up = async knex => { guestUserId: userGuestId } }, + { + key: 'flags', + value: { + experimental: false, + authDebug: false, + sqlLog: false + } + }, { key: 'icons', value: { diff --git a/server/graph/resolvers/system.js b/server/graph/resolvers/system.js index 36d777b3..8e636890 100644 --- a/server/graph/resolvers/system.js +++ b/server/graph/resolvers/system.js @@ -11,9 +11,7 @@ const graphHelper = require('../../helpers/graph') module.exports = { Query: { systemFlags () { - return _.transform(WIKI.config.flags, (result, value, key) => { - result.push({ key, value }) - }, []) + return WIKI.config.flags }, async systemInfo () { return {} }, async systemExtensions () { @@ -150,9 +148,10 @@ module.exports = { } }, async updateSystemFlags (obj, args, context) { - WIKI.config.flags = _.transform(args.flags, (result, row) => { - _.set(result, row.key, row.value) - }, {}) + WIKI.config.flags = { + ...WIKI.config.flags, + ...args.flags + } await WIKI.configSvc.applyFlags() await WIKI.configSvc.saveToDb(['flags']) return { @@ -164,7 +163,7 @@ module.exports = { // TODO: broadcast config update await WIKI.configSvc.saveToDb(['security']) return { - status: graphHelper.generateSuccess('System Security configuration applied successfully') + operation: graphHelper.generateSuccess('System Security configuration applied successfully') } } }, diff --git a/server/graph/resolvers/tree.js b/server/graph/resolvers/tree.js index 5e9a600e..7787d280 100644 --- a/server/graph/resolvers/tree.js +++ b/server/graph/resolvers/tree.js @@ -7,7 +7,7 @@ const typeResolvers = { asset: 'TreeItemAsset' } -const rePathName = /^[a-z0-9_]+$/ +const rePathName = /^[a-z0-9-]+$/ const reTitle = /^[^<>"]+$/ module.exports = { @@ -41,7 +41,7 @@ module.exports = { if (args.parentId) { const parent = await WIKI.db.knex('tree').where('id', args.parentId).first() if (parent) { - parentPath = parent.folderPath ? `${parent.folderPath}.${parent.fileName}` : parent.fileName + parentPath = (parent.folderPath ? `${parent.folderPath}.${parent.fileName}` : parent.fileName).replaceAll('-', '_') } } else if (args.parentPath) { parentPath = args.parentPath.replaceAll('/', '.').replaceAll('-', '_').toLowerCase() @@ -101,11 +101,11 @@ module.exports = { if (parent) { parentPath = parent.folderPath ? `${parent.folderPath}.${parent.fileName}` : parent.fileName } + parentPath = parentPath.replaceAll('-', '_') } // Validate path name - const pathName = args.pathName.replaceAll('-', '_') - if (!rePathName.test(pathName)) { + if (!rePathName.test(args.pathName)) { throw new Error('ERR_INVALID_PATH_NAME') } @@ -118,7 +118,7 @@ module.exports = { const existingFolder = await WIKI.db.knex('tree').where({ siteId: args.siteId, folderPath: parentPath, - fileName: pathName + fileName: args.pathName }).first() if (existingFolder) { throw new Error('ERR_FOLDER_ALREADY_EXISTS') @@ -127,7 +127,7 @@ module.exports = { // Create folder await WIKI.db.knex('tree').insert({ folderPath: parentPath, - fileName: pathName, + fileName: args.pathName, type: 'folder', title: args.title, siteId: args.siteId diff --git a/server/graph/schemas/system.graphql b/server/graph/schemas/system.graphql index c79428ae..8b71030f 100644 --- a/server/graph/schemas/system.graphql +++ b/server/graph/schemas/system.graphql @@ -4,7 +4,7 @@ extend type Query { systemExtensions: [SystemExtension] - systemFlags: [SystemFlag] + systemFlags: JSON systemInfo: SystemInfo systemInstances: [SystemInstance] systemSecurity: SystemSecurity @@ -31,7 +31,7 @@ extend type Mutation { ): DefaultResponse updateSystemFlags( - flags: [SystemFlagInput]! + flags: JSON! ): DefaultResponse updateSystemSecurity( @@ -60,16 +60,6 @@ extend type Mutation { # TYPES # ----------------------------------------------- -type SystemFlag { - key: String - value: Boolean -} - -input SystemFlagInput { - key: String! - value: Boolean! -} - type SystemInfo { configFile: String cpuCores: Int diff --git a/server/models/pages.js b/server/models/pages.js index a7bac632..9356239a 100644 --- a/server/models/pages.js +++ b/server/models/pages.js @@ -310,12 +310,12 @@ module.exports = class Page extends Model { }, contentType: WIKI.data.editors[opts.editor]?.contentType ?? 'text', description: opts.description, - // dotPath: dotPath, editor: opts.editor, hash: pageHelper.generateHash({ path: opts.path, locale: opts.locale }), icon: opts.icon, isBrowsable: opts.isBrowsable ?? true, localeCode: opts.locale, + ownerId: opts.user.id, path: opts.path, publishState: opts.publishState, publishEndDate: opts.publishEndDate?.toISO(), @@ -339,6 +339,29 @@ module.exports = class Page extends Model { // -> Render page to HTML await WIKI.db.pages.renderPage(page) + // -> Add to tree + const pathParts = page.path.split('/') + await WIKI.db.knex('tree').insert({ + id: page.id, + folderPath: _.initial(pathParts).join('/'), + fileName: _.last(pathParts), + type: 'page', + localeCode: page.localeCode, + title: page.title, + meta: { + authorId: page.authorId, + contentType: page.contentType, + creatorId: page.creatorId, + description: page.description, + isBrowsable: page.isBrowsable, + ownerId: page.ownerId, + publishState: page.publishState, + publishEndDate: page.publishEndDate, + publishStartDate: page.publishStartDate + }, + siteId: page.siteId + }) + return page // TODO: Handle remaining flow @@ -590,6 +613,23 @@ module.exports = class Page extends Model { } WIKI.events.outbound.emit('deletePageFromCache', page.hash) + // -> Update tree + await WIKI.db.knex('tree').where('id', page.id).update({ + title: page.title, + meta: { + authorId: page.authorId, + contentType: page.contentType, + creatorId: page.creatorId, + description: page.description, + isBrowsable: page.isBrowsable, + ownerId: page.ownerId, + publishState: page.publishState, + publishEndDate: page.publishEndDate, + publishStartDate: page.publishStartDate + }, + updatedAt: page.updatedAt + }) + // // -> Update Search Index // const pageContents = await WIKI.db.pages.query().findById(page.id).select('render') // page.safeContent = WIKI.db.pages.cleanHTML(pageContents.render) @@ -948,12 +988,10 @@ module.exports = class Page extends Model { // -> Delete page await WIKI.db.pages.query().delete().where('id', page.id) + await WIKI.db.knex('tree').where('id', page.id).del() await WIKI.db.pages.deletePageFromCache(page.hash) WIKI.events.outbound.emit('deletePageFromCache', page.hash) - // -> Rebuild page tree - await WIKI.db.pages.rebuildTree() - // -> Delete from Search Index await WIKI.data.searchEngine.deleted(page) diff --git a/ux/public/_assets/icons/ultraviolet-administrative-tools.svg b/ux/public/_assets/icons/ultraviolet-administrative-tools.svg new file mode 100644 index 00000000..9c924a18 --- /dev/null +++ b/ux/public/_assets/icons/ultraviolet-administrative-tools.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ux/public/_assets/icons/ultraviolet-asciidoc.svg b/ux/public/_assets/icons/ultraviolet-asciidoc.svg new file mode 100644 index 00000000..b423da0b --- /dev/null +++ b/ux/public/_assets/icons/ultraviolet-asciidoc.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/ux/src/App.vue b/ux/src/App.vue index b88ec1bf..74c2e44d 100644 --- a/ux/src/App.vue +++ b/ux/src/App.vue @@ -5,6 +5,7 @@ router-view diff --git a/ux/src/components/PageNewMenu.vue b/ux/src/components/PageNewMenu.vue index 1f2ec6a4..867162af 100644 --- a/ux/src/components/PageNewMenu.vue +++ b/ux/src/components/PageNewMenu.vue @@ -11,15 +11,19 @@ q-menu.translucent-menu( q-item(clickable, @click='create(`markdown`)') blueprint-icon(icon='markdown') q-item-section.q-pr-sm New Markdown Page - q-item(clickable, @click='create(`channel`)') - blueprint-icon(icon='chat') - q-item-section.q-pr-sm New Discussion Space - q-item(clickable, @click='create(`blog`)') - blueprint-icon(icon='typewriter-with-paper') - q-item-section.q-pr-sm New Blog Page - q-item(clickable, @click='create(`api`)') - blueprint-icon(icon='api') - q-item-section.q-pr-sm New API Documentation + q-item(clickable, @click='create(`asciidoc`)') + blueprint-icon(icon='asciidoc') + q-item-section.q-pr-sm New AsciiDoc Page + template(v-if='flagsStore.experimental') + q-item(clickable, @click='create(`channel`)') + blueprint-icon(icon='chat') + q-item-section.q-pr-sm New Discussion Space + q-item(clickable, @click='create(`blog`)') + blueprint-icon(icon='typewriter-with-paper') + q-item-section.q-pr-sm New Blog Page + q-item(clickable, @click='create(`api`)') + blueprint-icon(icon='api') + q-item-section.q-pr-sm New API Documentation q-item(clickable, @click='create(`redirect`)') blueprint-icon(icon='advance') q-item-section.q-pr-sm New Redirection @@ -41,6 +45,7 @@ import { useQuasar } from 'quasar' import { usePageStore } from 'src/stores/page' import { useSiteStore } from 'src/stores/site' +import { useFlagsStore } from 'src/stores/flags' // PROPS @@ -65,6 +70,7 @@ const $q = useQuasar() // STORES +const flagsStore = useFlagsStore() const pageStore = usePageStore() const siteStore = useSiteStore() diff --git a/ux/src/components/PageSaveDialog.vue b/ux/src/components/PageSaveDialog.vue index a929fdfc..357cd8c5 100644 --- a/ux/src/components/PageSaveDialog.vue +++ b/ux/src/components/PageSaveDialog.vue @@ -5,17 +5,23 @@ q-dialog(ref='dialogRef', @hide='onDialogHide') q-icon(name='img:/_assets/icons/fluent-save-as.svg', left, size='sm') span {{t('pageSaveDialog.title')}} .row.page-save-dialog-browser - .col-4.q-px-sm - tree( - :nodes='state.treeNodes' - :roots='state.treeRoots' - v-model:selected='state.currentFolderId' - @lazy-load='treeLazyLoad' - :use-lazy-load='true' - @context-action='treeContextAction' - :context-action-list='[`newFolder`]' - :display-mode='state.displayMode' - ) + .col-4 + q-scroll-area( + :thumb-style='thumbStyle' + :bar-style='barStyle' + style='height: 300px' + ) + .q-px-sm + tree( + :nodes='state.treeNodes' + :roots='state.treeRoots' + v-model:selected='state.currentFolderId' + @lazy-load='treeLazyLoad' + :use-lazy-load='true' + @context-action='treeContextAction' + :context-action-list='[`newFolder`]' + :display-mode='state.displayMode' + ) .col-8 q-list.page-save-dialog-filelist(dense) q-item( @@ -31,6 +37,7 @@ q-dialog(ref='dialogRef', @hide='onDialogHide') q-icon(:name='item.icon', size='sm') q-item-section q-item-label {{item.title}} + .page-save-dialog-path.font-robotomono {{folderPath}} q-list.q-py-sm q-item blueprint-icon(icon='new-document') @@ -197,8 +204,28 @@ const displayModes = [ { value: 'path', label: t('pageSaveDialog.displayModePath') } ] +const thumbStyle = { + right: '1px', + borderRadius: '5px', + backgroundColor: '#666', + width: '5px', + opacity: 0.5 +} +const barStyle = { + width: '7px' +} + // COMPUTED +const folderPath = computed(() => { + if (!state.currentFolderId) { + return '/' + } else { + const folderNode = state.treeNodes[state.currentFolderId] ?? {} + return folderNode.folderPath ? `/${folderNode.folderPath}/${folderNode.fileName}/` : `/${folderNode.fileName}/` + } +}) + const files = computed(() => { return state.fileList.map(f => { switch (f.type) { @@ -274,8 +301,9 @@ async function loadTree (parentId, types) { switch (item.__typename) { case 'TreeItemFolder': { state.treeNodes[item.id] = { - text: item.title, + folderPath: item.folderPath, fileName: item.fileName, + title: item.title, children: [] } if (!item.folderPath) { @@ -336,16 +364,23 @@ onMounted(() => { &-browser { height: 300px; max-height: 90vh; - border-bottom: 1px solid $blue-grey-1; + border-bottom: 1px solid #FFF; + + @at-root .body--light & { + border-bottom-color: $blue-grey-1; + } + @at-root .body--dark & { + border-bottom-color: $dark-3; + } > .col-4 { + height: 300px; + @at-root .body--light & { background-color: $blue-grey-1; - border-bottom-color: $blue-grey-1; } @at-root .body--dark & { background-color: $dark-4; - border-bottom-color: $dark-4; } } } @@ -372,5 +407,22 @@ onMounted(() => { } } + &-path { + padding: 5px 16px; + font-size: 12px; + border-bottom: 1px solid #FFF; + + @at-root .body--light & { + background-color: lighten($blue-grey-1, 4%); + border-bottom-color: $blue-grey-1; + color: $blue-grey-9; + } + @at-root .body--dark & { + background-color: darken($dark-4, 1%); + border-bottom-color: $dark-1; + color: $blue-grey-3; + } + } + } diff --git a/ux/src/components/TreeNav.vue b/ux/src/components/TreeNav.vue index 4b42de0e..45d651ce 100644 --- a/ux/src/components/TreeNav.vue +++ b/ux/src/components/TreeNav.vue @@ -95,7 +95,7 @@ const state = reactive({ opened: {} }) -// COMPOUTED +// COMPUTED const selection = computed({ get () { @@ -120,6 +120,16 @@ function emitContextAction (nodeId, action) { emit('contextAction', nodeId, action) } +function setOpened (nodeId) { + state.opened[nodeId] = true +} +function isLoaded (nodeId) { + return state.loaded[nodeId] +} +function resetLoaded (nodeId) { + state.loaded[nodeId] = false +} + // PROVIDE provide('roots', toRef(props, 'roots')) @@ -131,6 +141,14 @@ provide('selection', selection) provide('emitLazyLoad', emitLazyLoad) provide('emitContextAction', emitContextAction) +// EXPOSE + +defineExpose({ + setOpened, + isLoaded, + resetLoaded +}) + // MOUNTED onMounted(() => { diff --git a/ux/src/components/TreeNode.vue b/ux/src/components/TreeNode.vue index 29f991d0..5e969edf 100644 --- a/ux/src/components/TreeNode.vue +++ b/ux/src/components/TreeNode.vue @@ -7,7 +7,7 @@ li.treeview-node size='sm' @click.stop='hasChildren ? toggleNode() : openNode()' ) - .treeview-label-text {{displayMode === 'path' ? node.fileName : node.text}} + .treeview-label-text {{displayMode === 'path' ? node.fileName : node.title}} q-spinner.q-mr-xs( color='primary' v-if='state.isLoading' diff --git a/ux/src/i18n/locales/en.json b/ux/src/i18n/locales/en.json index 66e08e82..50d2ac4e 100644 --- a/ux/src/i18n/locales/en.json +++ b/ux/src/i18n/locales/en.json @@ -157,15 +157,13 @@ "admin.extensions.requiresSharp": "Requires Sharp extension", "admin.extensions.subtitle": "Install extensions for extra functionality", "admin.extensions.title": "Extensions", - "admin.flags.hidedonatebtn.hint": "You have already donated to this project (thank you!) and want to hide the button from the administration area.", - "admin.flags.hidedonatebtn.label": "Hide Donate Button", - "admin.flags.ldapdebug.hint": "Log detailed debug info on LDAP/AD login attempts.", - "admin.flags.ldapdebug.label": "LDAP Debug", - "admin.flags.sqllog.hint": "Log all queries made to the database to console.", - "admin.flags.sqllog.label": "SQL Query Logging", + "admin.flags.authDebug.hint": "Log detailed debug info of all login / registration attempts.", + "admin.flags.authDebug.label": "Auth Debug", + "admin.flags.sqlLog.hint": "Log all queries made to the database to console.", + "admin.flags.sqlLog.label": "SQL Query Logging", "admin.flags.subtitle": "Low-level system flags for debugging or experimental purposes", "admin.flags.title": "Flags", - "admin.flags.warn.hint": "Doing so may result in data loss or broken installation!", + "admin.flags.warn.hint": "Doing so may result in data loss, performance issues or a broken installation!", "admin.flags.warn.label": "Do NOT enable these flags unless you know what you're doing!", "admin.general.allowComments": "Allow Comments", "admin.general.allowCommentsHint": "Can users leave comments on pages? Can be restricted using Page Rules.", @@ -1603,5 +1601,14 @@ "common.actions.duplicate": "Duplicate", "common.actions.moveTo": "Move To", "pageSaveDialog.displayModeTitle": "Title", - "pageSaveDialog.displayModePath": "Path" + "pageSaveDialog.displayModePath": "Path", + "folderDeleteDialog.title": "Confirm Delete Folder", + "folderDeleteDialog.confirm": "Are you sure you want to delete folder {name} and all its content?", + "folderDeleteDialog.folderId": "Folder ID {id}", + "folderDeleteDialog.deleteSuccess": "Folder has been deleted successfully.", + "admin.flags.experimental.label": "Experimental Features", + "admin.flags.experimental.hint": "Enable unstable / unfinished features. DO NOT enable in a production environment!", + "admin.flags.advanced.label": "Custom Configuration", + "admin.flags.advanced.hint": "Set custom configuration flags. Note that all values are public to all users! Do not insert senstive data.", + "admin.flags.saveSuccess": "Flags have been updated successfully." } diff --git a/ux/src/layouts/AdminLayout.vue b/ux/src/layouts/AdminLayout.vue index b39f64a1..41560247 100644 --- a/ux/src/layouts/AdminLayout.vue +++ b/ux/src/layouts/AdminLayout.vue @@ -29,7 +29,7 @@ q-layout.admin(view='hHh Lpr lff') :thumb-style='thumbStyle' :bar-style='barStyle' ) - q-list.text-white(padding, dense) + q-list.text-white.q-pb-lg(padding, dense) q-item.q-mb-sm q-item-section q-btn.acrylic-btn( diff --git a/ux/src/layouts/ProfileLayout.vue b/ux/src/layouts/ProfileLayout.vue index 2d8fb3db..126d1687 100644 --- a/ux/src/layouts/ProfileLayout.vue +++ b/ux/src/layouts/ProfileLayout.vue @@ -5,19 +5,19 @@ q-layout(view='hHh Lpr lff') .layout-profile-card .layout-profile-sd q-list - q-item( - v-for='navItem of sidenav' - :key='navItem.key' - clickable - :to='`/_profile/` + navItem.key' - active-class='is-active' - :disabled='navItem.disabled' - v-ripple - ) - q-item-section(side) - q-icon(:name='navItem.icon') - q-item-section - q-item-label {{navItem.label}} + template(v-for='navItem of sidenav' :key='navItem.key') + q-item( + v-if='!navItem.disabled || flagsStore.experimental' + clickable + :to='`/_profile/` + navItem.key' + active-class='is-active' + :disabled='navItem.disabled' + v-ripple + ) + q-item-section(side) + q-icon(:name='navItem.icon') + q-item-section + q-item-label {{navItem.label}} q-separator.q-my-sm(inset) q-item( clickable @@ -48,6 +48,7 @@ import { useI18n } from 'vue-i18n' import { useMeta, useQuasar } from 'quasar' import { onMounted, reactive, watch } from 'vue' +import { useFlagsStore } from 'src/stores/flags' import { useSiteStore } from 'src/stores/site' import { useUserStore } from 'src/stores/user' @@ -61,6 +62,7 @@ const $q = useQuasar() // STORES +const flagsStore = useFlagsStore() const siteStore = useSiteStore() const userStore = useUserStore() diff --git a/ux/src/pages/AdminFlags.vue b/ux/src/pages/AdminFlags.vue index 3c7893ad..4123f9ef 100644 --- a/ux/src/pages/AdminFlags.vue +++ b/ux/src/pages/AdminFlags.vue @@ -15,13 +15,20 @@ q-page.admin-flags target='_blank' type='a' ) + q-btn.q-mr-sm.acrylic-btn( + icon='las la-redo-alt' + flat + color='secondary' + :loading='state.loading > 0' + @click='load' + ) q-btn( unelevated icon='fa-solid fa-check' :label='t(`common.actions.apply`)' color='secondary' @click='save' - :loading='loading' + :loading='state.loading > 0' ) q-separator(inset) .row.q-pa-md.q-col-gutter-md @@ -39,44 +46,60 @@ q-page.admin-flags q-item(tag='label') blueprint-icon(icon='flag-filled') q-item-section - q-item-label {{t(`admin.flags.ldapdebug.label`)}} - q-item-label(caption) {{t(`admin.flags.ldapdebug.hint`)}} + q-item-label {{t(`admin.flags.experimental.label`)}} + q-item-label(caption) {{t(`admin.flags.experimental.hint`)}} q-item-section(avatar) q-toggle( - v-model='flags.ldapdebug' - color='primary' + v-model='state.flags.experimental' + color='negative' checked-icon='las la-check' unchecked-icon='las la-times' - :aria-label='t(`admin.flags.ldapdebug.label`)' + :aria-label='t(`admin.flags.experimental.label`)' ) q-separator.q-my-sm(inset) q-item(tag='label') blueprint-icon(icon='flag-filled') q-item-section - q-item-label {{t(`admin.flags.sqllog.label`)}} - q-item-label(caption) {{t(`admin.flags.sqllog.hint`)}} + q-item-label {{t(`admin.flags.authDebug.label`)}} + q-item-label(caption) {{t(`admin.flags.authDebug.hint`)}} q-item-section(avatar) q-toggle( - v-model='flags.sqllog' - color='primary' + v-model='state.flags.authDebug' + color='negative' checked-icon='las la-check' unchecked-icon='las la-times' - :aria-label='t(`admin.flags.sqllog.label`)' + :aria-label='t(`admin.flags.authDebug.label`)' ) - q-card.shadow-1.q-py-sm.q-mt-md + q-separator.q-my-sm(inset) q-item(tag='label') - blueprint-icon(icon='heart-outline') + blueprint-icon(icon='flag-filled') q-item-section - q-item-label {{t(`admin.flags.hidedonatebtn.label`)}} - q-item-label(caption) {{t(`admin.flags.hidedonatebtn.hint`)}} + q-item-label {{t(`admin.flags.sqlLog.label`)}} + q-item-label(caption) {{t(`admin.flags.sqlLog.hint`)}} q-item-section(avatar) q-toggle( - v-model='flags.hidedonatebtn' - color='primary' + v-model='state.flags.sqlLog' + color='negative' checked-icon='las la-check' unchecked-icon='las la-times' - :aria-label='t(`admin.flags.hidedonatebtn.label`)' + :aria-label='t(`admin.flags.sqlLog.label`)' ) + q-card.shadow-1.q-py-sm.q-mt-md + q-item + blueprint-icon(icon='administrative-tools') + q-item-section + q-item-label {{t(`admin.flags.advanced.label`)}} + q-item-label(caption) {{t(`admin.flags.advanced.hint`)}} + q-item-section(avatar) + q-btn( + :label='t(`common.actions.edit`)' + unelevated + icon='las la-code' + color='primary' + text-color='white' + @click='' + disabled + ) .col-12.col-lg-5.gt-md .q-pa-md.text-center @@ -85,12 +108,13 @@ q-page.admin-flags