feat: admin storage actions + status light component

pull/5698/head
Nicolas Giard 2 years ago
parent c9fc4a1611
commit 468aebe2a8
No known key found for this signature in database
GPG Key ID: 85061B8F9D55B7C8

@ -92,8 +92,7 @@ module.exports = {
}
}
}, {}),
actions: {}
// actions: md.actions
actions: md.actions
}
}), ['title'])
}

@ -94,6 +94,9 @@ module.exports = {
latestVersionReleaseDate () {
return DateTime.fromISO(WIKI.system.updates.releaseDate).toJSDate()
},
mailConfigured () {
return false // TODO: return true if mail is setup
},
nodeVersion () {
return process.version.substr(1)
},

@ -68,6 +68,7 @@ type SystemInfo {
httpsPort: Int
latestVersion: String
latestVersionReleaseDate: Date
mailConfigured: Boolean
nodeVersion: String
operatingSystem: String
pagesTotal: Int

@ -50,7 +50,7 @@ props:
- hot|Hot
- cool|Cool
actions:
- handler: exportAll
exportAll:
label: Export All DB Assets to Azure
hint: Output all content from the DB to Azure Blog Storage, overwriting any existing data. If you enabled Azure Blog Storage after content was created or you temporarily disabled it, you'll want to execute this action to add the missing content.
icon: this-way-up

@ -18,7 +18,7 @@ versioning:
sync: false
props: {}
actions:
- handler: purge
purge:
label: Purge All Assets
hint: Delete all asset data from the database (not the metadata). Useful if you moved assets to another storage target and want to reduce the size of the database.
warn: This is a destructive action! Make sure all asset files are properly stored on another storage module! This action cannot be undone!

@ -32,15 +32,15 @@ props:
icon: archive-folder
order: 2
actions:
- handler: dump
dump:
label: Dump all content to disk
hint: Output all content from the DB to the local disk. If you enabled this module after content was created or you temporarily disabled this module, you'll want to execute this action to add the missing files.
icon: downloads
- handler: backup
backup:
label: Create Backup
hint: Will create a manual backup archive at this point in time, in a subfolder named _manual, from the contents currently on disk.
icon: archive-folder
- handler: importAll
importAll:
label: Import Everything
hint: Will import all content currently in the local disk folder.
icon: database-daily-import

@ -59,7 +59,7 @@ props:
default: storage.google.com
order: 5
actions:
- handler: exportAll
exportAll:
label: Export All DB Assets to GCS
hint: Output all content from the DB to Google Cloud Storage, overwriting any existing data. If you enabled Google Cloud Storage after content was created or you temporarily disabled it, you'll want to execute this action to add the missing content.
icon: this-way-up

@ -134,19 +134,19 @@ props:
icon: run-command
order: 50
actions:
- handler: syncUntracked
syncUntracked:
label: Add Untracked Changes
hint: Output all content from the DB to the local Git repository to ensure all untracked content is saved. If you enabled Git after content was created or you temporarily disabled Git, you'll want to execute this action to add the missing untracked changes.
icon: database-daily-export
- handler: sync
sync:
label: Force Sync
hint: Will trigger an immediate sync operation, regardless of the current sync schedule. The sync direction is respected.
icon: synchronize
- handler: importAll
importAll:
label: Import Everything
hint: Will import all content currently in the local Git repository, regardless of the latest commit state. Useful for importing content from the remote repository created before git was enabled.
icon: database-daily-import
- handler: purge
purge:
label: Purge Local Repository
hint: If you have unrelated merge histories, clearing the local repository can resolve this issue. This will not affect the remote repository or perform any commit.
icon: trash

@ -43,7 +43,7 @@ props:
hint: The repository default branch.
icon: code-fork
actions:
- handler: exportAll
exportAll:
label: Export All DB Assets to GitHub
hint: Output all content from the DB to GitHub, overwriting any existing data. If you enabled GitHub after content was created or you temporarily disabled it, you'll want to execute this action to add the missing content.
icon: this-way-up

@ -153,7 +153,7 @@ props:
if:
- { key: 'mode', eq: 'custom' }
actions:
- handler: exportAll
exportAll:
label: Export All DB Assets to S3
hint: Output all content from the DB to S3, overwriting any existing data. If you enabled S3 after content was created or you temporarily disabled it, you'll want to execute this action to add the missing content.
icon: this-way-up

@ -87,7 +87,7 @@ props:
hint: Base directory where files will be transferred to. The path must already exists and be writable by the user.
icon: symlink-directory
actions:
- handler: exportAll
exportAll:
label: Export All DB Assets to Remote
hint: Output all content from the DB to the remote SSH server, overwriting any existing data. If you enabled SFTP after content was created or you temporarily disabled it, you'll want to execute this action to add the missing content.
icon: this-way-up

@ -1,9 +1,11 @@
import { boot } from 'quasar/wrappers'
import BlueprintIcon from '../components/BlueprintIcon.vue'
import StatusLight from '../components/StatusLight.vue'
import VNetworkGraph from 'v-network-graph'
export default boot(({ app }) => {
app.component('BlueprintIcon', BlueprintIcon)
app.component('StatusLight', StatusLight)
app.use(VNetworkGraph)
})

@ -0,0 +1,65 @@
<template lang='pug'>
.status-light(:class='cssClasses')
</template>
<script setup>
import { computed } from 'vue'
// PROPS
const props = defineProps({
pulse: {
type: Boolean,
default: false
},
color: {
type: String,
default: ''
}
})
// COMPUTED
const cssClasses = computed(() => {
return `${props.color} ${props.pulse && 'pulsate'}`
})
</script>
<style lang="scss">
.status-light {
display: block;
width: 5px;
height: 100%;
min-height: 5px;
border-radius: 5px;
color: $grey-5;
background-color: currentColor;
background-image: linear-gradient(to bottom, transparent, rgba(255,255,255,.4));
&.negative {
color: $negative;
}
&.positive {
color: $positive;
}
&.warning {
color: $warning;
}
&.pulsate {
animation: status-light-pulsate 2s ease infinite;
}
}
@keyframes status-light-pulsate {
0% {
box-shadow: 0 0 5px 0 currentColor;
}
50% {
box-shadow: 0 0 5px 2px currentColor;
}
100% {
box-shadow: 0 0 5px 0 currentColor;
}
}
</style>

@ -1466,5 +1466,6 @@
"admin.api.key": "API Key",
"admin.api.createSuccess": "API Key created successfully.",
"admin.api.revoked": "Revoked",
"admin.api.revokedHint": "This key has been revoked and can no longer be used."
"admin.api.revokedHint": "This key has been revoked and can no longer be used.",
"admin.storage.setupRequired": "Setup required"
}

@ -132,6 +132,8 @@ q-layout.admin(view='hHh Lpr lff')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-rest-api.svg')
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-section(avatar)
q-icon(name='img:/_assets/icons/fluent-event-log.svg')
@ -144,6 +146,8 @@ q-layout.admin(view='hHh Lpr lff')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-message-settings.svg')
q-item-section {{ t('admin.mail.title') }}
q-item-section(side)
status-light(:color='adminStore.info.isMailConfigured ? `positive` : `warning`')
q-item(to='/_admin/security', v-ripple, active-class='bg-primary text-white')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-protect.svg')
@ -156,6 +160,8 @@ q-layout.admin(view='hHh Lpr lff')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-processor.svg')
q-item-section {{ t('admin.system.title') }}
q-item-section(side)
status-light(:color='adminStore.isVersionLatest ? `positive` : `warning`')
q-item(to='/_admin/utilities', v-ripple, active-class='bg-primary text-white')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-swiss-army-knife.svg')

@ -41,14 +41,12 @@ q-page.admin-mail
clickable
)
q-item-section(side)
q-icon(
:name='`img:` + str.strategy.icon'
)
q-icon(:name='`img:` + str.strategy.icon')
q-item-section
q-item-label {{str.displayName}}
q-item-label(caption) {{str.strategy.title}}
q-item-section(side)
q-spinner-rings(:color='str.isEnabled ? `positive` : `negative`', size='sm')
status-light(:color='str.isEnabled ? `positive` : `negative`', :pulse='str.isEnabled')
q-btn.q-mt-sm.full-width(
color='primary'
icon='las la-plus'
@ -499,7 +497,7 @@ function addStrategy (str) {
config: transform(str.props, (cfg, v, k) => {
cfg[k] = v.default
}, {}),
isEnabled: true,
isEnabled: false,
displayName: str.title,
selfRegistration: false,
domainWhitelist: [],

@ -89,11 +89,13 @@ q-page.admin-dashboard
)
.col-12
q-banner.bg-positive.text-white(
:class='adminStore.isVersionLatest ? `bg-positive` : `bg-warning`'
inline-actions
rounded
)
i.las.la-check.q-mr-sm
span.text-weight-medium Your Wiki.js server is running the latest version!
span.text-weight-medium(v-if='adminStore.isVersionLatest') Your Wiki.js server is running the latest version!
span.text-weight-medium(v-else) A new version of Wiki.js is available. Please update to the latest version.
template(#action)
q-btn(
flat

@ -49,7 +49,7 @@ q-page.admin-storage
.col-auto
q-card.rounded-borders.bg-dark
q-list(
style='min-width: 350px;'
style='min-width: 300px;'
padding
dark
)
@ -62,427 +62,430 @@ q-page.admin-storage
clickable
)
q-item-section(side)
q-icon(
:name='`img:` + tgt.icon'
)
q-icon(:name='`img:` + tgt.icon')
q-item-section
q-item-label {{tgt.title}}
q-item-label(caption, :class='getTargetSubtitleColor(tgt)') {{getTargetSubtitle(tgt)}}
q-item-section(side)
q-spinner-rings(:color='tgt.isEnabled ? `positive` : `negative`', size='sm')
status-light(:color='tgt.isEnabled ? `positive` : `negative`', :pulse='tgt.isEnabled')
.col(v-if='state.target')
//- -----------------------
//- Content Types
//- -----------------------
q-card.shadow-1.q-pb-sm
q-card-section
.text-subtitle1 {{t('admin.storage.contentTypes')}}
.text-body2.text-grey {{ t('admin.storage.contentTypesHint') }}
q-item(tag='label')
q-item-section(avatar)
q-checkbox(
v-model='state.target.contentTypes.activeTypes'
:color='state.target.module === `db` ? `grey` : `primary`'
val='pages'
:aria-label='t(`admin.storage.contentTypePages`)'
:disable='state.target.module === `db`'
)
q-item-section
q-item-label {{t(`admin.storage.contentTypePages`)}}
q-item-label(caption) {{t(`admin.storage.contentTypePagesHint`)}}
q-item(tag='label')
q-item-section(avatar)
q-checkbox(
v-model='state.target.contentTypes.activeTypes'
color='primary'
val='images'
:aria-label='t(`admin.storage.contentTypeImages`)'
)
q-item-section
q-item-label {{t(`admin.storage.contentTypeImages`)}}
q-item-label(caption) {{t(`admin.storage.contentTypeImagesHint`)}}
q-item(tag='label')
q-item-section(avatar)
q-checkbox(
v-model='state.target.contentTypes.activeTypes'
color='primary'
val='documents'
:aria-label='t(`admin.storage.contentTypeDocuments`)'
)
q-item-section
q-item-label {{t(`admin.storage.contentTypeDocuments`)}}
q-item-label(caption) {{t(`admin.storage.contentTypeDocumentsHint`)}}
q-item(tag='label')
q-item-section(avatar)
q-checkbox(
v-model='state.target.contentTypes.activeTypes'
color='primary'
val='others'
:aria-label='t(`admin.storage.contentTypeOthers`)'
)
q-item-section
q-item-label {{t(`admin.storage.contentTypeOthers`)}}
q-item-label(caption) {{t(`admin.storage.contentTypeOthersHint`)}}
q-item(tag='label')
q-item-section(avatar)
q-checkbox(
v-model='state.target.contentTypes.activeTypes'
color='primary'
val='large'
:aria-label='t(`admin.storage.contentTypeLargeFiles`)'
)
q-item-section
q-item-label {{t(`admin.storage.contentTypeLargeFiles`)}}
q-item-label(caption) {{t(`admin.storage.contentTypeLargeFilesHint`)}}
q-item-label.text-deep-orange(v-if='state.target.module === `db`', caption) {{t(`admin.storage.contentTypeLargeFilesDBWarn`)}}
q-item-section(side)
q-input(
outlined
:label='t(`admin.storage.contentTypeLargeFilesThreshold`)'
v-model='state.target.contentTypes.largeThreshold'
style='min-width: 150px;'
dense
)
//- -----------------------
//- Content Delivery
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section
.text-subtitle1 {{t('admin.storage.assetDelivery')}}
.text-body2.text-grey {{ t('admin.storage.assetDeliveryHint') }}
q-item(:tag='state.target.assetDelivery.isStreamingSupported ? `label` : null')
q-item-section(avatar)
q-checkbox(
v-model='state.target.assetDelivery.streaming'
:color='state.target.module === `db` || !state.target.assetDelivery.isStreamingSupported ? `grey` : `primary`'
:aria-label='t(`admin.storage.contentTypePages`)'
:disable='state.target.module === `db` || !state.target.assetDelivery.isStreamingSupported'
)
q-item-section
q-item-label {{t(`admin.storage.assetStreaming`)}}
q-item-label(caption) {{t(`admin.storage.assetStreamingHint`)}}
q-item-label.text-deep-orange(v-if='!state.target.assetDelivery.isStreamingSupported', caption) {{t(`admin.storage.assetStreamingNotSupported`)}}
q-item(:tag='state.target.assetDelivery.isDirectAccessSupported ? `label` : null')
q-item-section(avatar)
q-checkbox(
v-model='state.target.assetDelivery.directAccess'
:color='!state.target.assetDelivery.isDirectAccessSupported ? `grey` : `primary`'
:aria-label='t(`admin.storage.contentTypePages`)'
:disable='!state.target.assetDelivery.isDirectAccessSupported'
)
q-item-section
q-item-label {{t(`admin.storage.assetDirectAccess`)}}
q-item-label(caption) {{t(`admin.storage.assetDirectAccessHint`)}}
q-item-label.text-deep-orange(v-if='!state.target.assetDelivery.isDirectAccessSupported', caption) {{t(`admin.storage.assetDirectAccessNotSupported`)}}
//- -----------------------
//- Setup
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md(v-if='state.target.setup && state.target.setup.handler && state.target.setup.state !== `configured`')
q-card-section
.text-subtitle1 {{t('admin.storage.setup')}}
.text-body2.text-grey {{ t('admin.storage.setupHint') }}
template(v-if='state.target.setup.handler === `github` && state.target.setup.state === `notconfigured`')
q-item
blueprint-icon(icon='test-account')
q-item-section
q-item-label GitHub Account Type
q-item-label(caption) Whether to use an organization or personal GitHub account during setup.
q-item-section.col-auto
q-btn-toggle(
v-model='state.target.setup.values.accountType'
push
glossy
no-caps
toggle-color='primary'
:options=`[
{ label: t('admin.storage.githubAccTypeOrg'), value: 'org' },
{ label: t('admin.storage.githubAccTypePersonal'), value: 'personal' }
]`
)
q-separator.q-my-sm(inset)
template(v-if='state.target.setup.values.accountType === `org`')
.row.q-col-gutter-md
.col-12.col-lg
//- -----------------------
//- Setup
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mb-md(v-if='state.target.setup && state.target.setup.handler && state.target.setup.state !== `configured`')
q-card-section
.text-subtitle1 {{t('admin.storage.setup')}}
.text-body2.text-grey {{ t('admin.storage.setupHint') }}
template(v-if='state.target.setup.handler === `github` && state.target.setup.state === `notconfigured`')
q-item
blueprint-icon(icon='test-account')
q-item-section
q-item-label GitHub Account Type
q-item-label(caption) Whether to use an organization or personal GitHub account during setup.
q-item-section.col-auto
q-btn-toggle(
v-model='state.target.setup.values.accountType'
push
glossy
no-caps
toggle-color='primary'
:options=`[
{ label: t('admin.storage.githubAccTypeOrg'), value: 'org' },
{ label: t('admin.storage.githubAccTypePersonal'), value: 'personal' }
]`
)
q-separator.q-my-sm(inset)
template(v-if='state.target.setup.values.accountType === `org`')
q-item
blueprint-icon(icon='github')
q-item-section
q-item-label {{ t('admin.storage.githubOrg') }}
q-item-label(caption) {{ t('admin.storage.githubOrgHint') }}
q-item-section
q-input(
outlined
v-model='state.target.setup.values.org'
dense
:aria-label='t(`admin.storage.githubOrg`)'
)
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='dns')
q-item-section
q-item-label {{ t('admin.storage.githubPublicUrl') }}
q-item-label(caption) {{ t('admin.storage.githubPublicUrlHint') }}
q-item-section
q-input(
outlined
v-model='state.target.setup.values.publicUrl'
dense
:aria-label='t(`admin.storage.githubPublicUrl`)'
)
q-card-section.q-pt-sm.text-right
form(
ref='githubSetupForm'
method='POST'
:action='state.setupCfg.action'
)
input(
type='hidden'
name='manifest'
:value='state.setupCfg.manifest'
)
q-btn(
unelevated
icon='las la-angle-double-right'
:label='t(`admin.storage.startSetup`)'
color='secondary'
@click='setupGitHub'
:loading='state.setupCfg.loading'
)
template(v-else-if='state.target.setup.handler === `github` && state.target.setup.state === `pendinginstall`')
q-card-section.q-py-none
q-banner(
rounded
:class='$q.dark.isActive ? `bg-teal-9 text-white` : `bg-teal-1 text-teal-9`'
) {{t('admin.storage.githubFinish')}}
q-card-section.q-pt-sm.text-right
q-btn.q-mr-sm(
unelevated
icon='las la-times-circle'
:label='t(`admin.storage.cancelSetup`)'
color='negative'
@click='setupDestroy'
)
q-btn(
unelevated
icon='las la-angle-double-right'
:label='t(`admin.storage.finishSetup`)'
color='secondary'
@click='setupGitHubStep(`verify`)'
:loading='state.setupCfg.loading'
)
q-card.shadow-1.q-pb-sm.q-mt-md(v-if='state.target.setup && state.target.setup.handler && state.target.setup.state === `configured`')
q-card-section
.text-subtitle1 {{t('admin.storage.setup')}}
.text-body2.text-grey {{ t('admin.storage.setupConfiguredHint') }}
q-item
blueprint-icon(icon='github')
blueprint-icon.self-start(icon='matches', :hue-rotate='140')
q-item-section
q-item-label {{ t('admin.storage.githubOrg') }}
q-item-label(caption) {{ t('admin.storage.githubOrgHint') }}
q-item-label Uninstall
q-item-label(caption) Delete the active configuration and start over the setup process.
q-item-label.text-red(caption): strong This action cannot be undone!
q-item-section(side)
q-btn.acrylic-btn(
flat
icon='las la-arrow-circle-right'
color='negative'
@click='setupDestroy'
:label='t(`admin.storage.uninstall`)'
)
//- -----------------------
//- Content Types
//- -----------------------
q-card.shadow-1.q-pb-sm
q-card-section
.text-subtitle1 {{t('admin.storage.contentTypes')}}
.text-body2.text-grey {{ t('admin.storage.contentTypesHint') }}
q-item(tag='label')
q-item-section(avatar)
q-checkbox(
v-model='state.target.contentTypes.activeTypes'
:color='state.target.module === `db` ? `grey` : `primary`'
val='pages'
:aria-label='t(`admin.storage.contentTypePages`)'
:disable='state.target.module === `db`'
)
q-item-section
q-item-label {{t(`admin.storage.contentTypePages`)}}
q-item-label(caption) {{t(`admin.storage.contentTypePagesHint`)}}
q-item(tag='label')
q-item-section(avatar)
q-checkbox(
v-model='state.target.contentTypes.activeTypes'
color='primary'
val='images'
:aria-label='t(`admin.storage.contentTypeImages`)'
)
q-item-section
q-item-label {{t(`admin.storage.contentTypeImages`)}}
q-item-label(caption) {{t(`admin.storage.contentTypeImagesHint`)}}
q-item(tag='label')
q-item-section(avatar)
q-checkbox(
v-model='state.target.contentTypes.activeTypes'
color='primary'
val='documents'
:aria-label='t(`admin.storage.contentTypeDocuments`)'
)
q-item-section
q-item-label {{t(`admin.storage.contentTypeDocuments`)}}
q-item-label(caption) {{t(`admin.storage.contentTypeDocumentsHint`)}}
q-item(tag='label')
q-item-section(avatar)
q-checkbox(
v-model='state.target.contentTypes.activeTypes'
color='primary'
val='others'
:aria-label='t(`admin.storage.contentTypeOthers`)'
)
q-item-section
q-item-label {{t(`admin.storage.contentTypeOthers`)}}
q-item-label(caption) {{t(`admin.storage.contentTypeOthersHint`)}}
q-item(tag='label')
q-item-section(avatar)
q-checkbox(
v-model='state.target.contentTypes.activeTypes'
color='primary'
val='large'
:aria-label='t(`admin.storage.contentTypeLargeFiles`)'
)
q-item-section
q-item-label {{t(`admin.storage.contentTypeLargeFiles`)}}
q-item-label(caption) {{t(`admin.storage.contentTypeLargeFilesHint`)}}
q-item-label.text-deep-orange(v-if='state.target.module === `db`', caption) {{t(`admin.storage.contentTypeLargeFilesDBWarn`)}}
q-item-section(side)
q-input(
outlined
v-model='state.target.setup.values.org'
:label='t(`admin.storage.contentTypeLargeFilesThreshold`)'
v-model='state.target.contentTypes.largeThreshold'
style='min-width: 150px;'
dense
:aria-label='t(`admin.storage.githubOrg`)'
)
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='dns')
q-item-section
q-item-label {{ t('admin.storage.githubPublicUrl') }}
q-item-label(caption) {{ t('admin.storage.githubPublicUrlHint') }}
q-item-section
q-input(
outlined
v-model='state.target.setup.values.publicUrl'
dense
:aria-label='t(`admin.storage.githubPublicUrl`)'
)
q-card-section.q-pt-sm.text-right
form(
ref='githubSetupForm'
method='POST'
:action='setupCfg.action'
)
input(
type='hidden'
name='manifest'
:value='setupCfg.manifest'
//- -----------------------
//- Content Delivery
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section
.text-subtitle1 {{t('admin.storage.assetDelivery')}}
.text-body2.text-grey {{ t('admin.storage.assetDeliveryHint') }}
q-item(:tag='state.target.assetDelivery.isStreamingSupported ? `label` : null')
q-item-section(avatar)
q-checkbox(
v-model='state.target.assetDelivery.streaming'
:color='state.target.module === `db` || !state.target.assetDelivery.isStreamingSupported ? `grey` : `primary`'
:aria-label='t(`admin.storage.contentTypePages`)'
:disable='state.target.module === `db` || !state.target.assetDelivery.isStreamingSupported'
)
q-item-section
q-item-label {{t(`admin.storage.assetStreaming`)}}
q-item-label(caption) {{t(`admin.storage.assetStreamingHint`)}}
q-item-label.text-deep-orange(v-if='!state.target.assetDelivery.isStreamingSupported', caption) {{t(`admin.storage.assetStreamingNotSupported`)}}
q-item(:tag='state.target.assetDelivery.isDirectAccessSupported ? `label` : null')
q-item-section(avatar)
q-checkbox(
v-model='state.target.assetDelivery.directAccess'
:color='!state.target.assetDelivery.isDirectAccessSupported ? `grey` : `primary`'
:aria-label='t(`admin.storage.contentTypePages`)'
:disable='!state.target.assetDelivery.isDirectAccessSupported'
)
q-item-section
q-item-label {{t(`admin.storage.assetDirectAccess`)}}
q-item-label(caption) {{t(`admin.storage.assetDirectAccessHint`)}}
q-item-label.text-deep-orange(v-if='!state.target.assetDelivery.isDirectAccessSupported', caption) {{t(`admin.storage.assetDirectAccessNotSupported`)}}
//- -----------------------
//- Configuration
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section
.text-subtitle1 {{t('admin.storage.config')}}
q-banner.q-mt-md(
v-if='!state.target.config || Object.keys(state.target.config).length < 1'
rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
) {{t('admin.storage.noConfigOption')}}
template(
v-for='(cfg, cfgKey, idx) in state.target.config'
)
q-btn(
unelevated
icon='las la-angle-double-right'
:label='t(`admin.storage.startSetup`)'
color='secondary'
@click='setupGitHub'
:loading='setupCfg.loading'
template(
v-if='configIfCheck(cfg.if)'
)
q-separator.q-my-sm(inset, v-if='idx > 0')
q-item(v-if='cfg.type === `Boolean`', tag='label')
blueprint-icon(:icon='cfg.icon', :hue-rotate='cfg.readOnly ? -45 : 0')
q-item-section
q-item-label {{cfg.title}}
q-item-label(caption) {{cfg.hint}}
q-item-section(avatar)
q-toggle(
v-model='cfg.value'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='t(`admin.general.allowComments`)'
:disable='cfg.readOnly'
)
q-item(v-else)
blueprint-icon(:icon='cfg.icon', :hue-rotate='cfg.readOnly ? -45 : 0')
q-item-section
q-item-label {{cfg.title}}
q-item-label(caption) {{cfg.hint}}
q-item-section(
:style='cfg.type === `Number` ? `flex: 0 0 150px;` : ``'
:class='{ "col-auto": cfg.enum && cfg.enumDisplay === `buttons` }'
)
q-btn-toggle(
v-if='cfg.enum && cfg.enumDisplay === `buttons`'
v-model='cfg.value'
push
glossy
no-caps
toggle-color='primary'
:options=`cfg.enum`
:disable='cfg.readOnly'
)
q-select(
v-else-if='cfg.enum'
outlined
v-model='cfg.value'
:options='cfg.enum'
emit-value
map-options
dense
options-dense
:aria-label='cfg.title'
:disable='cfg.readOnly'
)
q-input(
v-else
outlined
v-model='cfg.value'
dense
:type='cfg.multiline ? `textarea` : `input`'
:aria-label='cfg.title'
:disable='cfg.readOnly'
)
//- -----------------------
//- Sync
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md(v-if='state.target.sync && Object.keys(state.target.sync).length > 0')
q-card-section
.text-subtitle1 {{t('admin.storage.sync')}}
q-banner.q-mt-md(
rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
) {{t('admin.storage.noSyncModes')}}
//- -----------------------
//- Actions
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section
.text-subtitle1 {{t('admin.storage.actions')}}
q-banner.q-mt-md(
v-if='!state.target.actions || state.target.actions.length < 1'
rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
) {{t('admin.storage.noActions')}}
q-banner.q-mt-md(
v-else-if='!state.target.isEnabled'
rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
) {{t('admin.storage.actionsInactiveWarn')}}
template(
v-if='state.target.isEnabled'
v-for='(act, idx) in state.target.actions'
)
template(v-else-if='state.target.setup.handler === `github` && state.target.setup.state === `pendinginstall`')
q-card-section.q-py-none
q-banner(
rounded
:class='$q.dark.isActive ? `bg-teal-9 text-white` : `bg-teal-1 text-teal-9`'
) {{t('admin.storage.githubFinish')}}
q-card-section.q-pt-sm.text-right
q-btn.q-mr-sm(
unelevated
icon='las la-times-circle'
:label='t(`admin.storage.cancelSetup`)'
color='negative'
@click='setupDestroy'
)
q-btn(
unelevated
icon='las la-angle-double-right'
:label='t(`admin.storage.finishSetup`)'
color='secondary'
@click='setupGitHubStep(`verify`)'
:loading='setupCfg.loading'
)
q-card.shadow-1.q-pb-sm.q-mt-md(v-if='state.target.setup && state.target.setup.handler && state.target.setup.state === `configured`')
q-card-section
.text-subtitle1 {{t('admin.storage.setup')}}
.text-body2.text-grey {{ t('admin.storage.setupConfiguredHint') }}
q-item
blueprint-icon.self-start(icon='matches', :hue-rotate='140')
q-item-section
q-item-label Uninstall
q-item-label(caption) Delete the active configuration and start over the setup process.
q-item-label.text-red(caption): strong This action cannot be undone!
q-item-section(side)
q-btn.acrylic-btn(
flat
icon='las la-arrow-circle-right'
color='negative'
@click='setupDestroy'
:label='t(`admin.storage.uninstall`)'
)
q-separator.q-my-sm(inset, v-if='idx > 0')
q-item
blueprint-icon.self-start(:icon='act.icon', :hue-rotate='45')
q-item-section
q-item-label {{act.label}}
q-item-label(caption) {{act.hint}}
q-item-label.text-red(v-if='act.warn', caption): strong {{act.warn}}
q-item-section(side)
q-btn.acrylic-btn(
flat
icon='las la-arrow-circle-right'
color='primary'
@click=''
:label='t(`common.actions.proceed`)'
)
//- -----------------------
//- Configuration
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section
.text-subtitle1 {{t('admin.storage.config')}}
q-banner.q-mt-md(
v-if='!state.target.config || Object.keys(state.target.config).length < 1'
rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
) {{t('admin.storage.noConfigOption')}}
template(
v-for='(cfg, cfgKey, idx) in state.target.config'
)
template(
v-if='configIfCheck(cfg.if)'
)
q-separator.q-my-sm(inset, v-if='idx > 0')
q-item(v-if='cfg.type === `Boolean`', tag='label')
blueprint-icon(:icon='cfg.icon', :hue-rotate='cfg.readOnly ? -45 : 0')
.col-12.col-lg-auto
//- -----------------------
//- Infobox
//- -----------------------
q-card.rounded-borders.q-pb-md(style='width: 300px;')
q-card-section
.text-subtitle1 {{state.target.title}}
q-img.q-mt-sm.rounded-borders(
:src='state.target.banner'
fit='cover'
no-spinner
)
.text-body2.q-mt-md {{state.target.description}}
q-separator.q-mb-sm(inset)
q-item
q-item-section
q-item-label.text-grey {{t(`admin.storage.vendor`)}}
q-item-label {{state.target.vendor}}
q-separator.q-my-sm(inset)
q-item
q-item-section
q-item-label {{cfg.title}}
q-item-label(caption) {{cfg.hint}}
q-item-label.text-grey {{t(`admin.storage.vendorWebsite`)}}
q-item-label: a(:href='state.target.website', target='_blank', rel='noreferrer') {{state.target.website}}
//- -----------------------
//- Status
//- -----------------------
q-card.rounded-borders.q-pb-md.q-mt-md(style='width: 300px;')
q-card-section
.text-subtitle1 {{ t('admin.storage.status') }}
template(v-if='state.target.module !== `db`')
q-item(tag='label')
q-item-section
q-item-label {{t(`admin.storage.enabled`)}}
q-item-label(caption) {{t(`admin.storage.enabledHint`)}}
q-item-label.text-deep-orange(v-if='state.target.module === `db`', caption) {{t(`admin.storage.enabledForced`)}}
q-item-section(avatar)
q-toggle(
v-model='state.target.isEnabled'
:disable='state.target.module === `db` || isSetupNeeded'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='t(`admin.storage.enabled`)'
)
q-inner-loading(:showing='isSetupNeeded')
q-icon(name='las la-exclamation-triangle', size='sm', color='negative')
.text-body2.text-negative {{ t('admin.storage.setupRequired') }}
q-separator.q-my-sm(inset)
q-item
q-item-section
q-item-label.text-grey {{t(`admin.storage.currentState`)}}
q-item-label.text-positive No issues detected.
//- -----------------------
//- Versioning
//- -----------------------
q-card.rounded-borders.q-pb-md.q-mt-md(style='width: 300px;')
q-card-section
.text-subtitle1 {{t(`admin.storage.versioning`)}}
.text-body2.text-grey {{t(`admin.storage.versioningHint`)}}
q-item(:tag='state.target.versioning.isSupported ? `label` : null')
q-item-section
q-item-label {{t(`admin.storage.useVersioning`)}}
q-item-label(caption) {{t(`admin.storage.useVersioningHint`)}}
q-item-label.text-deep-orange(v-if='!state.target.versioning.isSupported', caption) {{t(`admin.storage.versioningNotSupported`)}}
q-item-label.text-deep-orange(v-if='state.target.versioning.isForceEnabled', caption) {{t(`admin.storage.versioningForceEnabled`)}}
q-item-section(avatar)
q-toggle(
v-model='cfg.value'
v-model='state.target.versioning.enabled'
:disable='!state.target.versioning.isSupported || state.target.versioning.isForceEnabled'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='t(`admin.general.allowComments`)'
:disable='cfg.readOnly'
)
q-item(v-else)
blueprint-icon(:icon='cfg.icon', :hue-rotate='cfg.readOnly ? -45 : 0')
q-item-section
q-item-label {{cfg.title}}
q-item-label(caption) {{cfg.hint}}
q-item-section(
:style='cfg.type === `Number` ? `flex: 0 0 150px;` : ``'
:class='{ "col-auto": cfg.enum && cfg.enumDisplay === `buttons` }'
)
q-btn-toggle(
v-if='cfg.enum && cfg.enumDisplay === `buttons`'
v-model='cfg.value'
push
glossy
no-caps
toggle-color='primary'
:options=`cfg.enum`
:disable='cfg.readOnly'
)
q-select(
v-else-if='cfg.enum'
outlined
v-model='cfg.value'
:options='cfg.enum'
emit-value
map-options
dense
options-dense
:aria-label='cfg.title'
:disable='cfg.readOnly'
)
q-input(
v-else
outlined
v-model='cfg.value'
dense
:type='cfg.multiline ? `textarea` : `input`'
:aria-label='cfg.title'
:disable='cfg.readOnly'
:aria-label='t(`admin.storage.useVersioning`)'
)
//- -----------------------
//- Sync
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md(v-if='state.target.sync && Object.keys(state.target.sync).length > 0')
q-card-section
.text-subtitle1 {{t('admin.storage.sync')}}
q-banner.q-mt-md(
rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
) {{t('admin.storage.noSyncModes')}}
//- -----------------------
//- Actions
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section
.text-subtitle1 {{t('admin.storage.actions')}}
q-banner.q-mt-md(
v-if='!state.target.actions || state.target.actions.length < 1'
rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
) {{t('admin.storage.noActions')}}
q-banner.q-mt-md(
v-else-if='!state.target.isEnabled'
rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
) {{t('admin.storage.actionsInactiveWarn')}}
template(
v-if='state.target.isEnabled'
v-for='(act, idx) in state.target.actions'
)
q-separator.q-my-sm(inset, v-if='idx > 0')
q-item
blueprint-icon.self-start(:icon='act.icon', :hue-rotate='45')
q-item-section
q-item-label {{act.label}}
q-item-label(caption) {{act.hint}}
q-item-label.text-red(v-if='act.warn', caption): strong {{act.warn}}
q-item-section(side)
q-btn.acrylic-btn(
flat
icon='las la-arrow-circle-right'
color='primary'
@click=''
:label='t(`common.actions.proceed`)'
)
.col-auto(v-if='state.target')
//- -----------------------
//- Infobox
//- -----------------------
q-card.rounded-borders.q-pb-md(style='width: 350px;')
q-card-section
.text-subtitle1 {{state.target.title}}
q-img.q-mt-sm.rounded-borders(
:src='state.target.banner'
fit='cover'
no-spinner
)
.text-body2.q-mt-md {{state.target.description}}
q-separator.q-mb-sm(inset)
q-item
q-item-section
q-item-label.text-grey {{t(`admin.storage.vendor`)}}
q-item-label {{state.target.vendor}}
q-separator.q-my-sm(inset)
q-item
q-item-section
q-item-label.text-grey {{t(`admin.storage.vendorWebsite`)}}
q-item-label: a(:href='state.target.website', target='_blank', rel='noreferrer') {{state.target.website}}
//- -----------------------
//- Status
//- -----------------------
q-card.rounded-borders.q-pb-md.q-mt-md(style='width: 350px;')
q-card-section
.text-subtitle1 {{ t('admin.storage.status') }}
template(v-if='state.target.module !== `db` && !(state.target.setup && state.target.setup.handler && state.target.setup.state !== `configured`)')
q-item(tag='label')
q-item-section
q-item-label {{t(`admin.storage.enabled`)}}
q-item-label(caption) {{t(`admin.storage.enabledHint`)}}
q-item-label.text-deep-orange(v-if='state.target.module === `db`', caption) {{t(`admin.storage.enabledForced`)}}
q-item-section(avatar)
q-toggle(
v-model='state.target.isEnabled'
:disable='state.target.module === `db` || (state.target.setup && state.target.setup.handler && state.target.setup.state !== `configured`)'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='t(`admin.storage.enabled`)'
)
q-separator.q-my-sm(inset)
q-item
q-item-section
q-item-label.text-grey {{t(`admin.storage.currentState`)}}
q-item-label.text-positive No issues detected.
//- -----------------------
//- Versioning
//- -----------------------
q-card.rounded-borders.q-pb-md.q-mt-md(style='width: 350px;')
q-card-section
.text-subtitle1 {{t(`admin.storage.versioning`)}}
.text-body2.text-grey {{t(`admin.storage.versioningHint`)}}
q-item(:tag='state.target.versioning.isSupported ? `label` : null')
q-item-section
q-item-label {{t(`admin.storage.useVersioning`)}}
q-item-label(caption) {{t(`admin.storage.useVersioningHint`)}}
q-item-label.text-deep-orange(v-if='!state.target.versioning.isSupported', caption) {{t(`admin.storage.versioningNotSupported`)}}
q-item-label.text-deep-orange(v-if='state.target.versioning.isForceEnabled', caption) {{t(`admin.storage.versioningForceEnabled`)}}
q-item-section(avatar)
q-toggle(
v-model='state.target.versioning.enabled'
:disable='!state.target.versioning.isSupported || state.target.versioning.isForceEnabled'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='t(`admin.storage.useVersioning`)'
)
//- ==========================================
//- DELIVERY PATHS
//- ==========================================
@ -705,6 +708,15 @@ const state = reactive({
const githubSetupForm = ref(null)
// COMPUTED
const isSetupNeeded = computed(() => {
return state.target?.setup?.handler && state.target.setup.state !== 'configured'
})
const isSetupCompleted = computed(() => {
return state.target?.setup?.handler && state.target.setup.state !== 'configured'
})
// WATCHERS
watch(() => adminStore.currentSiteId, async (newValue) => {

@ -1,6 +1,7 @@
import { defineStore } from 'pinia'
import gql from 'graphql-tag'
import { cloneDeep } from 'lodash-es'
import { clone, cloneDeep } from 'lodash-es'
import semverGte from 'semver/functions/gte'
/* global APOLLO_CLIENT */
@ -13,7 +14,9 @@ export const useAdminStore = defineStore('admin', {
groupsTotal: 0,
pagesTotal: 0,
usersTotal: 0,
loginsPastDay: 0
loginsPastDay: 0,
isApiEnabled: false,
isMailConfigured: false
},
overlay: null,
overlayOpts: {},
@ -22,7 +25,14 @@ export const useAdminStore = defineStore('admin', {
{ code: 'en', name: 'English' }
]
}),
getters: {},
getters: {
isVersionLatest: (state) => {
if (!state.info.currentVersion || !state.info.latestVersion || state.info.currentVersion === 'n/a' || state.info.latestVersion === 'n/a') {
return false
}
return semverGte(state.info.currentVersion, state.info.latestVersion)
}
},
actions: {
async fetchSites () {
const resp = await APOLLO_CLIENT.query({
@ -47,16 +57,24 @@ export const useAdminStore = defineStore('admin', {
const resp = await APOLLO_CLIENT.query({
query: gql`
query getAdminInfo {
apiState
systemInfo {
groupsTotal
usersTotal
currentVersion
latestVersion
mailConfigured
}
}
`,
fetchPolicy: 'network-only'
})
this.info.groupsTotal = cloneDeep(resp?.data?.systemInfo.groupsTotal ?? 0)
this.info.usersTotal = cloneDeep(resp?.data?.systemInfo.usersTotal ?? 0)
this.info.groupsTotal = clone(resp?.data?.systemInfo?.groupsTotal ?? 0)
this.info.usersTotal = clone(resp?.data?.systemInfo?.usersTotal ?? 0)
this.info.currentVersion = clone(resp?.data?.systemInfo?.currentVersion ?? 'n/a')
this.info.latestVersion = clone(resp?.data?.systemInfo?.latestVersion ?? 'n/a')
this.info.isApiEnabled = clone(resp?.data?.apiState ?? false)
this.info.isMailConfigured = clone(resp?.data?.systemInfo?.mailConfigured ?? false)
}
}
})

Loading…
Cancel
Save