mirror of
https://github.com/requarks/wiki.git
synced 2026-08-26 13:17:10 -05:00
feat: file manager improvements + admin user edit fixes
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -13,6 +13,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
|
||||
ref='searchField'
|
||||
style='width: 100%;'
|
||||
label='Search folder...'
|
||||
:debounce='500'
|
||||
)
|
||||
template(v-slot:prepend)
|
||||
q-icon(name='las la-search')
|
||||
@@ -51,7 +52,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
|
||||
.q-pa-md
|
||||
template(v-if='currentFileDetails')
|
||||
q-img.rounded-borders.q-mb-md(
|
||||
src='https://picsum.photos/id/134/340/340'
|
||||
src='/_assets/illustrations/fileman-page.svg'
|
||||
width='100%'
|
||||
fit='cover'
|
||||
:ratio='16/10'
|
||||
@@ -129,13 +130,21 @@ q-layout.fileman(view='hHh lpR lFr', container)
|
||||
size='xs'
|
||||
)
|
||||
q-item-section.q-pr-sm Browse Using Titles
|
||||
q-item(clickable)
|
||||
q-item(clickable, @click='state.isCompact = !state.isCompact')
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-stop', color='grey', size='xs')
|
||||
q-icon(
|
||||
:name='state.isCompact ? `las la-check-square` : `las la-stop`'
|
||||
:color='state.isCompact ? `positive` : `grey`'
|
||||
size='xs'
|
||||
)
|
||||
q-item-section.q-pr-sm Compact List
|
||||
q-item(clickable)
|
||||
q-item(clickable, @click='state.shouldShowFolders = !state.shouldShowFolders')
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-check-square', color='positive', size='xs')
|
||||
q-icon(
|
||||
:name='state.shouldShowFolders ? `las la-check-square` : `las la-stop`'
|
||||
:color='state.shouldShowFolders ? `positive` : `grey`'
|
||||
size='xs'
|
||||
)
|
||||
q-item-section.q-pr-sm Show Folders
|
||||
q-btn.q-mr-sm(
|
||||
flat
|
||||
@@ -191,10 +200,10 @@ q-layout.fileman(view='hHh lpR lFr', container)
|
||||
@dblclick.native='openItem(item)'
|
||||
)
|
||||
q-item-section.fileman-filelist-icon(avatar)
|
||||
q-icon(:name='item.icon', size='xl')
|
||||
q-icon(:name='item.icon', :size='state.isCompact ? `md` : `xl`')
|
||||
q-item-section.fileman-filelist-label
|
||||
q-item-label {{item.title}}
|
||||
q-item-label(caption) {{item.caption}}
|
||||
q-item-label(caption, v-if='!state.isCompact') {{item.caption}}
|
||||
q-item-section.fileman-filelist-side(side, v-if='item.side')
|
||||
.text-caption {{item.side}}
|
||||
//- RIGHT-CLICK MENU
|
||||
@@ -211,11 +220,11 @@ q-layout.fileman(view='hHh lpR lFr', container)
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-edit', color='orange')
|
||||
q-item-section Edit
|
||||
q-item(clickable, v-if='item.type !== `folder`')
|
||||
q-item(clickable, v-if='item.type !== `folder`', @click='openItem(item)')
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-eye', color='primary')
|
||||
q-item-section View
|
||||
q-item(clickable, v-if='item.type !== `folder`')
|
||||
q-item(clickable, v-if='item.type !== `folder`', @click='copyItemURL(item)')
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-clipboard', color='primary')
|
||||
q-item-section Copy URL
|
||||
@@ -231,7 +240,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-arrow-right', color='teal')
|
||||
q-item-section Move to...
|
||||
q-item(clickable)
|
||||
q-item(clickable, @click='delItem(item)')
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-trash-alt', color='negative')
|
||||
q-item-section.text-negative Delete
|
||||
@@ -255,7 +264,9 @@ import { filesize } from 'filesize'
|
||||
import { useQuasar } from 'quasar'
|
||||
import { DateTime } from 'luxon'
|
||||
import { cloneDeep, find } from 'lodash-es'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import gql from 'graphql-tag'
|
||||
import Fuse from 'fuse.js/dist/fuse.basic.esm'
|
||||
|
||||
import NewMenu from './PageNewMenu.vue'
|
||||
import Tree from './TreeNav.vue'
|
||||
@@ -277,6 +288,11 @@ const $q = useQuasar()
|
||||
const pageStore = usePageStore()
|
||||
const siteStore = useSiteStore()
|
||||
|
||||
// ROUTER
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
// I18N
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -291,6 +307,8 @@ const state = reactive({
|
||||
treeNodes: {},
|
||||
treeRoots: [],
|
||||
displayMode: 'title',
|
||||
isCompact: false,
|
||||
shouldShowFolders: true,
|
||||
isUploading: false,
|
||||
shouldCancelUpload: false,
|
||||
uploadPercentage: 0,
|
||||
@@ -314,8 +332,29 @@ const folderPath = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const filteredFiles = computed(() => {
|
||||
if (state.search) {
|
||||
const fuse = new Fuse(state.fileList, {
|
||||
keys: [
|
||||
'title',
|
||||
'fileName'
|
||||
]
|
||||
})
|
||||
return fuse.search(state.search).map(n => n.item)
|
||||
} else {
|
||||
return state.fileList
|
||||
}
|
||||
})
|
||||
|
||||
const files = computed(() => {
|
||||
return state.fileList.map(f => {
|
||||
return filteredFiles.value.filter(f => {
|
||||
console.info(f)
|
||||
// -> Show Folders Filter
|
||||
if (f.type === 'folder' && !state.shouldShowFolders) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}).map(f => {
|
||||
switch (f.type) {
|
||||
case 'folder': {
|
||||
f.icon = fileTypes.folder.icon
|
||||
@@ -413,10 +452,9 @@ async function treeLazyLoad (nodeId, { done, fail }) {
|
||||
done()
|
||||
}
|
||||
|
||||
async function loadTree (parentId, types, noCache = false) {
|
||||
async function loadTree (parentId, types) {
|
||||
if (!parentId) {
|
||||
parentId = null
|
||||
state.treeRoots = []
|
||||
}
|
||||
if (parentId === state.currentFolderId) {
|
||||
state.fileListLoading = true
|
||||
@@ -451,7 +489,7 @@ async function loadTree (parentId, types, noCache = false) {
|
||||
title
|
||||
createdAt
|
||||
updatedAt
|
||||
pageEditor
|
||||
editor
|
||||
}
|
||||
... on TreeItemAsset {
|
||||
id
|
||||
@@ -479,22 +517,21 @@ async function loadTree (parentId, types, noCache = false) {
|
||||
switch (item.__typename) {
|
||||
case 'TreeItemFolder': {
|
||||
// -> Tree Nodes
|
||||
if (!state.treeNodes[item.id] || (parentId && !treeComp.value.isLoaded(item.id))) {
|
||||
if (!state.treeNodes[item.id]) {
|
||||
state.treeNodes[item.id] = {
|
||||
folderPath: item.folderPath,
|
||||
fileName: item.fileName,
|
||||
title: item.title,
|
||||
children: []
|
||||
}
|
||||
if (item.folderPath) {
|
||||
if (!state.treeNodes[parentId].children.includes(item.id)) {
|
||||
state.treeNodes[parentId].children.push(item.id)
|
||||
}
|
||||
children: state.treeNodes[item.id]?.children ?? []
|
||||
}
|
||||
}
|
||||
|
||||
// -> Set Tree Roots
|
||||
if (!item.folderPath) {
|
||||
// -> Set Ancestors / Tree Roots
|
||||
if (item.folderPath) {
|
||||
if (!state.treeNodes[parentId].children.includes(item.id)) {
|
||||
state.treeNodes[parentId].children.push(item.id)
|
||||
}
|
||||
} else {
|
||||
newTreeRoots.push(item.id)
|
||||
}
|
||||
|
||||
@@ -504,6 +541,7 @@ async function loadTree (parentId, types, noCache = false) {
|
||||
id: item.id,
|
||||
type: 'folder',
|
||||
title: item.title,
|
||||
fileName: item.fileName,
|
||||
children: 0
|
||||
})
|
||||
}
|
||||
@@ -516,7 +554,9 @@ async function loadTree (parentId, types, noCache = false) {
|
||||
type: 'asset',
|
||||
title: item.title,
|
||||
fileType: 'pdf',
|
||||
fileSize: 19000
|
||||
fileSize: 19000,
|
||||
folderPath: item.folderPath,
|
||||
fileName: item.fileName
|
||||
})
|
||||
}
|
||||
break
|
||||
@@ -528,7 +568,9 @@ async function loadTree (parentId, types, noCache = false) {
|
||||
type: 'page',
|
||||
title: item.title,
|
||||
pageType: 'markdown',
|
||||
updatedAt: '2022-11-24T18:27:00Z'
|
||||
updatedAt: '2022-11-24T18:27:00Z',
|
||||
folderPath: item.folderPath,
|
||||
fileName: item.fileName
|
||||
})
|
||||
}
|
||||
break
|
||||
@@ -551,6 +593,9 @@ async function loadTree (parentId, types, noCache = false) {
|
||||
state.fileListLoading = false
|
||||
})
|
||||
}
|
||||
if (parentId) {
|
||||
treeComp.value.setLoaded(parentId)
|
||||
}
|
||||
}
|
||||
|
||||
function treeContextAction (nodeId, action) {
|
||||
@@ -566,6 +611,10 @@ function treeContextAction (nodeId, action) {
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
// FOLDER METHODS
|
||||
// --------------------------------------
|
||||
|
||||
function newFolder (parentId) {
|
||||
$q.dialog({
|
||||
component: FolderCreateDialog,
|
||||
@@ -577,7 +626,7 @@ function newFolder (parentId) {
|
||||
})
|
||||
}
|
||||
|
||||
function delFolder (folderId) {
|
||||
function delFolder (folderId, mustReload = false) {
|
||||
$q.dialog({
|
||||
component: FolderDeleteDialog,
|
||||
componentProps: {
|
||||
@@ -591,15 +640,23 @@ function delFolder (folderId) {
|
||||
}
|
||||
}
|
||||
delete state.treeNodes[folderId]
|
||||
if (state.treeRoots.includes(folderId)) {
|
||||
state.treeRoots = state.treeRoots.filter(n => n !== folderId)
|
||||
}
|
||||
if (mustReload) {
|
||||
loadTree(state.currentFolderId, null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function reloadFolder (folderId) {
|
||||
loadTree(folderId, null, true)
|
||||
loadTree(folderId, null)
|
||||
treeComp.value.resetLoaded()
|
||||
}
|
||||
|
||||
// -> Upload Methods
|
||||
// --------------------------------------
|
||||
// UPLOAD METHODS
|
||||
// --------------------------------------
|
||||
|
||||
function uploadFile () {
|
||||
fileIpt.value.click()
|
||||
@@ -678,6 +735,10 @@ function uploadCancel () {
|
||||
state.uploadPercentage = 0
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
// ITEM LIST ACTIONS
|
||||
// --------------------------------------
|
||||
|
||||
function selectItem (item) {
|
||||
if (item.type === 'folder') {
|
||||
state.currentFolderId = item.id
|
||||
@@ -688,7 +749,60 @@ function selectItem (item) {
|
||||
}
|
||||
|
||||
function openItem (item) {
|
||||
console.info(item.id)
|
||||
switch (item.type) {
|
||||
case 'folder': {
|
||||
return
|
||||
}
|
||||
case 'page': {
|
||||
const pagePath = item.folderPath ? `${item.folderPath}/${item.fileName}` : item.fileName
|
||||
router.push(`/${pagePath}`)
|
||||
close()
|
||||
break
|
||||
}
|
||||
case 'asset': {
|
||||
// TODO: Open asset
|
||||
close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function copyItemURL (item) {
|
||||
try {
|
||||
switch (item.type) {
|
||||
case 'page': {
|
||||
const pagePath = item.folderPath ? `${item.folderPath}/${item.fileName}` : item.fileName
|
||||
await navigator.clipboard.writeText(`${window.location.origin}/${pagePath}`)
|
||||
break
|
||||
}
|
||||
case 'asset': {
|
||||
// TODO: Copy asset URL to clibpard
|
||||
break
|
||||
}
|
||||
default: {
|
||||
throw new Error('Invalid Item Type')
|
||||
}
|
||||
}
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
message: t('fileman.copyURLSuccess')
|
||||
})
|
||||
} catch (err) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: 'Failed to copy URL to clipboard.',
|
||||
caption: err.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function delItem (item) {
|
||||
switch (item.type) {
|
||||
case 'folder': {
|
||||
delFolder(item.id, true)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MOUNTED
|
||||
|
||||
@@ -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,17 +38,20 @@ 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'
|
||||
clickable
|
||||
:to='{ params: { section: sc.key } }'
|
||||
active-class='bg-primary text-white'
|
||||
:disabled='sc.disabled'
|
||||
)
|
||||
q-item-section(side)
|
||||
q-icon(:name='sc.icon', color='white')
|
||||
q-item-section {{sc.text}}
|
||||
q-item(
|
||||
v-if='!sc.disabled || flagsStore.experimental'
|
||||
clickable
|
||||
:to='{ params: { section: sc.key } }'
|
||||
active-class='bg-primary text-white'
|
||||
:disabled='sc.disabled'
|
||||
)
|
||||
q-item-section(side)
|
||||
q-icon(:name='sc.icon', color='white')
|
||||
q-item-section {{sc.text}}
|
||||
q-page-container
|
||||
q-page(v-if='state.loading > 0')
|
||||
.flex.q-pa-lg.items-center
|
||||
@@ -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,26 +70,27 @@ 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') }}
|
||||
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')
|
||||
q-item-section {{ t('admin.analytics.title') }}
|
||||
q-item(:to='`/_admin/` + adminStore.currentSiteId + `/approvals`', v-ripple, active-class='bg-primary text-white', disabled)
|
||||
q-item-section(avatar)
|
||||
q-icon(name='img:/_assets/icons/fluent-inspection.svg')
|
||||
q-item-section {{ t('admin.approval.title') }}
|
||||
q-item(:to='`/_admin/` + adminStore.currentSiteId + `/comments`', v-ripple, active-class='bg-primary text-white', disabled)
|
||||
q-item-section(avatar)
|
||||
q-icon(name='img:/_assets/icons/fluent-comments.svg')
|
||||
q-item-section {{ t('admin.comments.title') }}
|
||||
q-item(:to='`/_admin/` + adminStore.currentSiteId + `/blocks`', v-ripple, active-class='bg-primary text-white', disabled)
|
||||
q-item-section(avatar)
|
||||
q-icon(name='img:/_assets/icons/fluent-rfid-tag.svg')
|
||||
q-item-section {{ t('admin.blocks.title') }}
|
||||
q-item(:to='`/_admin/` + adminStore.currentSiteId + `/editors`', v-ripple, active-class='bg-primary text-white', disabled)
|
||||
q-item-section(avatar)
|
||||
q-icon(name='img:/_assets/icons/fluent-cashbook.svg')
|
||||
q-item-section {{ t('admin.editors.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')
|
||||
q-item-section {{ t('admin.analytics.title') }}
|
||||
q-item(:to='`/_admin/` + adminStore.currentSiteId + `/approvals`', v-ripple, active-class='bg-primary text-white', disabled)
|
||||
q-item-section(avatar)
|
||||
q-icon(name='img:/_assets/icons/fluent-inspection.svg')
|
||||
q-item-section {{ t('admin.approval.title') }}
|
||||
q-item(:to='`/_admin/` + adminStore.currentSiteId + `/comments`', v-ripple, active-class='bg-primary text-white', disabled)
|
||||
q-item-section(avatar)
|
||||
q-icon(name='img:/_assets/icons/fluent-comments.svg')
|
||||
q-item-section {{ t('admin.comments.title') }}
|
||||
q-item(:to='`/_admin/` + adminStore.currentSiteId + `/blocks`', v-ripple, active-class='bg-primary text-white', disabled)
|
||||
q-item-section(avatar)
|
||||
q-icon(name='img:/_assets/icons/fluent-rfid-tag.svg')
|
||||
q-item-section {{ t('admin.blocks.title') }}
|
||||
q-item(:to='`/_admin/` + adminStore.currentSiteId + `/editors`', v-ripple, active-class='bg-primary text-white', disabled)
|
||||
q-item-section(avatar)
|
||||
q-icon(name='img:/_assets/icons/fluent-cashbook.svg')
|
||||
q-item-section {{ t('admin.editors.title') }}
|
||||
q-item(:to='`/_admin/` + adminStore.currentSiteId + `/locale`', v-ripple, active-class='bg-primary text-white')
|
||||
q-item-section(avatar)
|
||||
q-icon(name='img:/_assets/icons/fluent-language.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;
|
||||
|
||||
@@ -4127,6 +4127,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fuse.js@npm:6.6.2":
|
||||
version: 6.6.2
|
||||
resolution: "fuse.js@npm:6.6.2"
|
||||
checksum: 17ae758ce205276ebd88bd9c9f088a100be0b4896abac9f6b09847151269d1690f41d7f98ff5813d4a58973162dbd99d0072ce807020fee6f9de60170f6b08eb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"gauge@npm:^4.0.3":
|
||||
version: 4.0.4
|
||||
resolution: "gauge@npm:4.0.4"
|
||||
@@ -7179,6 +7186,7 @@ __metadata:
|
||||
eslint-plugin-vue: 9.7.0
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user