feat: file manager improvements + admin user edit fixes

parent b4769b9a
......@@ -91,6 +91,9 @@ module.exports = {
}
},
Mutation: {
/**
* CREATE FOLDER
*/
async createFolder (obj, args, context) {
try {
// Get parent path
......@@ -138,6 +141,52 @@ module.exports = {
} catch (err) {
return graphHelper.generateError(err)
}
},
/**
* DELETE FOLDER
*/
async deleteFolder (obj, args, context) {
try {
// Get folder
const folder = await WIKI.db.knex('tree').where('id', args.folderId).first()
const folderPath = folder.folderPath ? `${folder.folderPath}.${folder.fileName}` : folder.fileName
WIKI.logger.debug(`Deleting folder ${folder.id} at path ${folderPath}...`)
// Delete all children
const deletedNodes = await WIKI.db.knex('tree').where('folderPath', '~', `${folderPath}.*`).del().returning(['id', 'type'])
// Delete folders
const deletedFolders = deletedNodes.filter(n => n.type === 'folder').map(n => n.id)
if (deletedFolders.length > 0) {
WIKI.logger.debug(`Deleted ${deletedFolders.length} children folders.`)
}
// Delete pages
const deletedPages = deletedNodes.filter(n => n.type === 'page').map(n => n.id)
if (deletedPages.length > 0) {
WIKI.logger.debug(`Deleting ${deletedPages.length} children pages...`)
// TODO: Delete page
}
// Delete assets
const deletedAssets = deletedNodes.filter(n => n.type === 'asset').map(n => n.id)
if (deletedAssets.length > 0) {
WIKI.logger.debug(`Deleting ${deletedPages.length} children assets...`)
// TODO: Delete asset
}
// Delete the folder itself
await WIKI.db.knex('tree').where('id', folder.id).del()
WIKI.logger.debug(`Deleting folder ${folder.id} successfully.`)
return {
operation: graphHelper.generateSuccess('Folder deleted successfully')
}
} catch (err) {
return graphHelper.generateError(err)
}
}
},
TreeItem: {
......
......@@ -352,8 +352,12 @@ module.exports = {
strategyKey: authStrategy.module,
strategyIcon: authModule.icon,
config: authStrategy.module === 'local' ? {
isTfaSetup: value.tfaSecret?.length > 0
} : {}
isPasswordSet: value.password?.length > 0,
isTfaSetup: value.tfaSecret?.length > 0,
isTfaRequired: value.tfaRequired ?? false,
mustChangePwd: value.mustChangePwd ?? false,
restrictLogin: value.restrictLogin ?? false
} : value
})
}, [])
},
......
......@@ -76,7 +76,7 @@ type TreeItemPage {
depth: Int
fileName: String
folderPath: String
pageEditor: String
editor: String
pageType: String
title: String
updatedAt: Date
......
......@@ -63,6 +63,7 @@
"codemirror": "6.0.1",
"filesize": "10.0.5",
"filesize-parser": "1.5.0",
"fuse.js": "6.6.2",
"graphql": "16.6.0",
"graphql-tag": "2.12.6",
"js-cookie": "3.0.1",
......
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 320 200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<rect x="0" y="0" width="320" height="200" style="fill:url(#_Linear1);"/>
<path d="M203.194,73.306L175.694,45.806C175.18,45.289 174.481,45 173.75,45L129.75,45C122.168,45 116,51.168 116,58.75L116,141.25C116,148.832 122.168,155 129.75,155L190.25,155C197.832,155 204,148.832 204,141.25L204,75.25C204,74.521 203.711,73.82 203.194,73.306Z" style="fill:rgb(107,227,162);fill-rule:nonzero;"/>
<path d="M204,78L184.75,78C177.168,78 171,71.832 171,64.25L171,45C171,43.482 172.229,42.25 173.75,42.25C175.271,42.25 176.5,43.482 176.5,45L176.5,64.25C176.5,68.798 180.202,72.5 184.75,72.5L204,72.5C205.521,72.5 206.75,73.732 206.75,75.25C206.75,76.768 205.521,78 204,78Z" style="fill:rgb(50,69,97);fill-rule:nonzero;"/>
<path d="M182,102.75L138,102.75C136.479,102.75 135.25,101.518 135.25,100C135.25,98.482 136.479,97.25 138,97.25L182,97.25C183.521,97.25 184.75,98.482 184.75,100C184.75,101.518 183.521,102.75 182,102.75Z" style="fill:rgb(50,69,97);fill-rule:nonzero;"/>
<path d="M182,116.5L138,116.5C136.479,116.5 135.25,115.268 135.25,113.75C135.25,112.232 136.479,111 138,111L182,111C183.521,111 184.75,112.232 184.75,113.75C184.75,115.268 183.521,116.5 182,116.5Z" style="fill:rgb(50,69,97);fill-rule:nonzero;"/>
<path d="M165.5,130.25L138,130.25C136.479,130.25 135.25,129.018 135.25,127.5C135.25,125.982 136.479,124.75 138,124.75L165.5,124.75C167.021,124.75 168.25,125.982 168.25,127.5C168.25,129.018 167.021,130.25 165.5,130.25Z" style="fill:rgb(50,69,97);fill-rule:nonzero;"/>
<defs>
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(320,100,-62.5,200,0,100)"><stop offset="0" style="stop-color:rgb(38,43,49);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(22,27,33);stop-opacity:1"/></linearGradient>
</defs>
</svg>
......@@ -19,6 +19,7 @@ q-dialog(ref='dialogRef', @hide='onDialogHide')
lazy-rules='ondemand'
autofocus
ref='iptTitle'
@keyup.enter='create'
)
q-item
blueprint-icon.self-start(icon='file-submodule')
......@@ -34,6 +35,7 @@ q-dialog(ref='dialogRef', @hide='onDialogHide')
:hint='t(`fileman.folderFileNameHint`)'
lazy-rules='ondemand'
@focus='state.pathDirty = true'
@keyup.enter='create'
)
q-card-actions.card-actions
q-space
......
......@@ -126,8 +126,11 @@ function setOpened (nodeId) {
function isLoaded (nodeId) {
return state.loaded[nodeId]
}
function resetLoaded (nodeId) {
state.loaded[nodeId] = false
function setLoaded (nodeId, value) {
state.loaded[nodeId] = value
}
function resetLoaded () {
state.loaded = {}
}
// PROVIDE
......@@ -146,6 +149,7 @@ provide('emitContextAction', emitContextAction)
defineExpose({
setOpened,
isLoaded,
setLoaded,
resetLoaded
})
......
......@@ -38,9 +38,12 @@ q-layout(view='hHh lpR fFf', container)
)
q-drawer.bg-dark-6(:model-value='true', :width='250', dark)
q-list(padding, v-if='state.loading < 1')
q-item(
template(
v-for='sc of sections'
:key='`section-` + sc.key'
)
q-item(
v-if='!sc.disabled || flagsStore.experimental'
clickable
:to='{ params: { section: sc.key } }'
active-class='bg-primary text-white'
......@@ -268,7 +271,7 @@ q-layout(view='hHh lpR fFf', container)
q-item-section
q-item-label {{t(`admin.users.changePassword`)}}
q-item-label(caption) {{t(`admin.users.changePasswordHint`)}}
q-item-label(caption): strong(:class='localAuth.password ? `text-positive` : `text-negative`') {{localAuth.password ? t(`admin.users.pwdSet`) : t(`admin.users.pwdNotSet`)}}
q-item-label(caption): strong(:class='localAuth.isPasswordSet ? `text-positive` : `text-negative`') {{localAuth.isPasswordSet ? t(`admin.users.pwdSet`) : t(`admin.users.pwdNotSet`)}}
q-item-section(side)
q-btn.acrylic-btn(
flat
......@@ -316,7 +319,7 @@ q-layout(view='hHh lpR fFf', container)
q-item-label(caption) {{t(`admin.users.tfaRequiredHint`)}}
q-item-section(avatar)
q-toggle(
v-model='localAuth.tfaRequired'
v-model='localAuth.isTfaRequired'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
......@@ -328,7 +331,7 @@ q-layout(view='hHh lpR fFf', container)
q-item-section
q-item-label {{t(`admin.users.tfaInvalidate`)}}
q-item-label(caption) {{t(`admin.users.tfaInvalidateHint`)}}
q-item-label(caption): strong(:class='localAuth.tfaSecret ? `text-positive` : `text-negative`') {{localAuth.tfaSecret ? t(`admin.users.tfaSet`) : t(`admin.users.tfaNotSet`)}}
q-item-label(caption): strong(:class='localAuth.isTfaSetup ? `text-positive` : `text-negative`') {{localAuth.isTfaSetup ? t(`admin.users.tfaSet`) : t(`admin.users.tfaNotSet`)}}
q-item-section(side)
q-btn.acrylic-btn(
flat
......@@ -348,14 +351,14 @@ q-layout(view='hHh lpR fFf', container)
) {{t('admin.users.noLinkedProviders')}}
template(
v-for='(prv, idx) in linkedAuthProviders'
:key='prv._id'
:key='prv.authId'
)
q-separator.q-my-sm(inset, v-if='idx > 0')
q-item
blueprint-icon(icon='google', :hue-rotate='-45')
blueprint-icon(:icon='prv.strategyIcon', :hue-rotate='-45')
q-item-section
q-item-label {{prv._moduleName}}
q-item-label(caption) {{prv.key}}
q-item-label {{prv.authName}}
q-item-label(caption) {{prv.config.key}}
q-page(v-else-if='route.params.section === `groups`')
.q-pa-md
......@@ -506,7 +509,7 @@ q-layout(view='hHh lpR fFf', container)
<script setup>
import gql from 'graphql-tag'
import { cloneDeep, find, findKey, map, some } from 'lodash-es'
import { cloneDeep, find, map, some } from 'lodash-es'
import { DateTime } from 'luxon'
import { useI18n } from 'vue-i18n'
......@@ -515,6 +518,7 @@ import { computed, onMounted, reactive, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useAdminStore } from 'src/stores/admin'
import { useFlagsStore } from 'src/stores/flags'
import UserChangePwdDialog from './UserChangePwdDialog.vue'
import UtilCodeEditor from './UtilCodeEditor.vue'
......@@ -526,6 +530,7 @@ const $q = useQuasar()
// STORES
const adminStore = useAdminStore()
const flagsStore = useFlagsStore()
// ROUTER
......@@ -553,7 +558,7 @@ const state = reactive({
const sections = [
{ key: 'overview', text: t('admin.users.overview'), icon: 'las la-user' },
{ key: 'activity', text: t('admin.users.activity'), icon: 'las la-chart-area' },
{ key: 'activity', text: t('admin.users.activity'), icon: 'las la-chart-area', disabled: true },
{ key: 'auth', text: t('admin.users.auth'), icon: 'las la-key' },
{ key: 'groups', text: t('admin.users.groups'), icon: 'las la-users' },
{ key: 'metadata', text: t('admin.users.metadata'), icon: 'las la-clipboard-list' },
......@@ -576,17 +581,13 @@ const metadata = computed({
}
})
const localAuthId = computed(() => {
return findKey(state.user.auth, ['module', 'local'])
})
const localAuth = computed({
get () {
return localAuthId.value ? state.user.auth?.[localAuthId.value] || {} : {}
return find(state.user?.auth, ['strategyKey', 'local'])?.config ?? {}
},
set (val) {
if (localAuthId.value) {
state.user.auth[localAuthId.value] = val
if (localAuth.value.authId) {
find(state.user.auth, ['strategyKey', 'local']).config = val
}
}
})
......@@ -594,12 +595,7 @@ const localAuth = computed({
const linkedAuthProviders = computed(() => {
if (!state.user?.auth) { return [] }
return map(state.user.auth, (obj, key) => {
return {
...obj,
_id: key
}
}).filter(prv => prv.module !== 'local')
return state.user.auth.filter(prv => prv.strategyKey !== 'local')
})
// WATCHERS
......@@ -630,7 +626,13 @@ async function fetchUser () {
isSystem
isVerified
isActive
auth
auth {
authId
authName
strategyKey
strategyIcon
config
}
meta
prefs
lastLoginAt
......
......@@ -1610,5 +1610,6 @@
"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."
"admin.flags.saveSuccess": "Flags have been updated successfully.",
"fileman.copyURLSuccess": "URL has been copied to the clipboard."
}
......@@ -70,6 +70,7 @@ q-layout.admin(view='hHh Lpr lff')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-web.svg')
q-item-section {{ t('admin.general.title') }}
template(v-if='flagsStore.experimental')
q-item(:to='`/_admin/` + adminStore.currentSiteId + `/analytics`', v-ripple, active-class='bg-primary text-white', disabled)
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-bar-chart.svg')
......@@ -134,7 +135,7 @@ q-layout.admin(view='hHh Lpr lff')
q-item-section {{ t('admin.api.title') }}
q-item-section(side)
status-light(:color='adminStore.info.isApiEnabled ? `positive` : `negative`')
q-item(to='/_admin/audit', v-ripple, active-class='bg-primary text-white', disabled)
q-item(to='/_admin/audit', v-ripple, active-class='bg-primary text-white', disabled, v-if='flagsStore.experimental')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-event-log.svg')
q-item-section {{ t('admin.audit.title') }}
......@@ -156,7 +157,7 @@ q-layout.admin(view='hHh Lpr lff')
q-item-section {{ t('admin.mail.title') }}
q-item-section(side)
status-light(:color='adminStore.info.isMailConfigured ? `positive` : `warning`')
q-item(to='/_admin/rendering', v-ripple, active-class='bg-primary text-white', disabled)
q-item(to='/_admin/rendering', v-ripple, active-class='bg-primary text-white', disabled, v-if='flagsStore.experimental')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-rich-text-converter.svg')
q-item-section {{ t('admin.rendering.title') }}
......@@ -170,7 +171,7 @@ q-layout.admin(view='hHh Lpr lff')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-protect.svg')
q-item-section {{ t('admin.security.title') }}
q-item(to='/_admin/ssl', v-ripple, active-class='bg-primary text-white', disabled)
q-item(to='/_admin/ssl', v-ripple, active-class='bg-primary text-white', disabled, v-if='flagsStore.experimental')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-security-ssl.svg')
q-item-section {{ t('admin.ssl.title') }}
......@@ -218,8 +219,9 @@ import { defineAsyncComponent, onMounted, reactive, ref, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useAdminStore } from '../stores/admin'
import { useSiteStore } from '../stores/site'
import { useAdminStore } from 'src/stores/admin'
import { useFlagsStore } from 'src/stores/flags'
import { useSiteStore } from 'src/stores/site'
// COMPONENTS
......@@ -237,6 +239,7 @@ const $q = useQuasar()
// STORES
const adminStore = useAdminStore()
const flagsStore = useFlagsStore()
const siteStore = useSiteStore()
// ROUTER
......
......@@ -234,6 +234,7 @@ q-page.column
aria-label='Page Data'
@click='togglePageData'
disable
v-if='flagsStore.experimental'
)
q-tooltip(anchor='center left' self='center right') Page Data
q-separator.q-my-sm(inset)
......@@ -322,6 +323,7 @@ import { useI18n } from 'vue-i18n'
import { DateTime } from 'luxon'
import { useEditorStore } from 'src/stores/editor'
import { useFlagsStore } from 'src/stores/flags'
import { usePageStore } from 'src/stores/page'
import { useSiteStore } from 'src/stores/site'
......@@ -349,6 +351,7 @@ const $q = useQuasar()
// STORES
const editorStore = useEditorStore()
const flagsStore = useFlagsStore()
const pageStore = usePageStore()
const siteStore = useSiteStore()
......@@ -702,6 +705,7 @@ async function saveChanges () {
width: 40px;
border-radius: 4px !important;
background-color: rgba(0,0,0,.75);
backdrop-filter: blur(5px);
color: #FFF;
position: fixed;
right: 486px;
......
This diff was suppressed by a .gitattributes entry.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment