Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wiki-js
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
wiki-js
Commits
36ba1eb1
Unverified
Commit
36ba1eb1
authored
May 23, 2022
by
Nicolas Giard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(admin): migrate locale + login + storage views to vue 3 composable
parent
0c522c17
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
1316 additions
and
1244 deletions
+1316
-1244
Dockerfile
.devcontainer/Dockerfile
+51
-13
app-init.sh
.devcontainer/app-init.sh
+4
-0
devcontainer.json
.devcontainer/devcontainer.json
+2
-2
docker-compose.yml
.devcontainer/docker-compose.yml
+2
-2
system.js
server/core/system.js
+2
-63
index.js
server/index.js
+2
-2
package.json
ux/package.json
+22
-22
LocaleInstallDialog.vue
ux/src/components/LocaleInstallDialog.vue
+90
-79
AdminEditors.vue
ux/src/pages/AdminEditors.vue
+1
-1
AdminFlags.vue
ux/src/pages/AdminFlags.vue
+1
-1
AdminGeneral.vue
ux/src/pages/AdminGeneral.vue
+1
-7
AdminLocale.vue
ux/src/pages/AdminLocale.vue
+226
-202
AdminLogin.vue
ux/src/pages/AdminLogin.vue
+150
-130
AdminSites.vue
ux/src/pages/AdminSites.vue
+2
-2
AdminStorage.vue
ux/src/pages/AdminStorage.vue
+753
-695
AdminSystem.vue
ux/src/pages/AdminSystem.vue
+1
-17
routes.js
ux/src/router/routes.js
+6
-6
yarn.lock
ux/yarn.lock
+0
-0
No files found.
.devcontainer/Dockerfile
View file @
36ba1eb1
# Based of https://github.com/microsoft/vscode-dev-containers/blob/main/containers/javascript-node/.devcontainer/base.Dockerfile
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 16, 14, 12, 16-bullseye, 14-bullseye, 12-bullseye, 16-buster, 14-buster, 12-buster
ARG
VARIANT=1
6
-bullseye
FROM
mcr.microsoft.com/vscode/devcontainers/javascript-node:0-
${VARIANT}
ARG
VARIANT=1
8
-bullseye
FROM
node:
${VARIANT}
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
ENV
DEBIAN_FRONTEND=noninteractive
# [Optional] Uncomment if you want to install an additional version of node using nvm
# ARG EXTRA_NODE_VERSION=10
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
# Copy library scripts to execute
ADD
https://raw.githubusercontent.com/microsoft/vscode-dev-containers/main/containers/javascript-node/.devcontainer/library-scripts/common-debian.sh /tmp/library-scripts/
ADD
https://raw.githubusercontent.com/microsoft/vscode-dev-containers/main/containers/javascript-node/.devcontainer/library-scripts/node-debian.sh /tmp/library-scripts/
ADD
https://raw.githubusercontent.com/microsoft/vscode-dev-containers/main/containers/javascript-node/.devcontainer/library-scripts/meta.env /tmp/library-scripts/
# [Optional] Uncomment if you want to install more global node modules
# RUN su node -c "npm install -g <your-package-list-here>"
# [Option] Install zsh
ARG
INSTALL_ZSH="true"
# [Option] Upgrade OS packages to their latest versions
ARG
UPGRADE_PACKAGES="true"
EXPOSE
3000
# Install needed packages, yarn, nvm and setup non-root user. Use a separate RUN statement to add your own dependencies.
ARG
USERNAME=node
ARG
USER_UID=1000
ARG
USER_GID=$USER_UID
ARG
NPM_GLOBAL=/usr/local/share/npm-global
ENV
NVM_DIR=/usr/local/share/nvm
ENV
NVM_SYMLINK_CURRENT=true \
PATH=${NPM_GLOBAL}/bin:${NVM_DIR}/current/bin:${PATH}
RUN
apt-get update
\
# Remove imagemagick due to https://security-tracker.debian.org/tracker/CVE-2019-10131
&& apt-get purge -y imagemagick imagemagick-6-common \
# Install common packages, non-root user, update yarn and install nvm
&& bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" "true" "true" \
# Install yarn, nvm
&& rm -rf /opt/yarn-* /usr/local/bin/yarn /usr/local/bin/yarnpkg \
&& bash /tmp/library-scripts/node-debian.sh "${NVM_DIR}" "none" "${USERNAME}" \
# Configure global npm install location, use group to adapt to UID/GID changes
&& if ! cat /etc/group | grep -e "^npm:" > /dev/null 2>&1; then groupadd -r npm; fi \
&& usermod -a -G npm ${USERNAME} \
&& umask 0002 \
&& mkdir -p ${NPM_GLOBAL} \
&& touch /usr/local/etc/npmrc \
&& chown ${USERNAME}:npm ${NPM_GLOBAL} /usr/local/etc/npmrc \
&& chmod g+s ${NPM_GLOBAL} \
&& npm config -g set prefix ${NPM_GLOBAL} \
&& sudo -u ${USERNAME} npm config -g set prefix ${NPM_GLOBAL} \
# Install eslint
&& su ${USERNAME} -c "umask 0002 && npm install -g eslint" \
&& npm cache clean --force > /dev/null 2>&1 \
# Install python-is-python3 on bullseye to prevent node-gyp regressions
&& . /etc/os-release \
&& if [ "${VERSION_CODENAME}" = "bullseye" ]; then apt-get -y install --no-install-recommends python-is-python3; fi \
# Clean up
&& apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* /root/.gnupg /tmp/library-scripts
ENV
DEBIAN_FRONTEND=noninteractive
EXPOSE
3000
# Add Docker Source
RUN
curl
-fsSL
https://download.docker.com/linux/debian/gpg | gpg
--dearmor
-o
/usr/share/keyrings/docker-archive-keyring.gpg
...
...
@@ -33,6 +70,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && apt-get install -
git
\
gnupg2
\
nano
\
netcat
\
pandoc
\
unzip
\
wget
...
...
@@ -48,7 +86,7 @@ ENV npm_config_fund false
RUN
sed
-i
's/#force_color_prompt=/force_color_prompt=/'
/root/.bashrc
# Fetch wait-for utility
ADD
https://raw.githubusercontent.com/eficode/wait-for/v2.
1
.3/wait-for /usr/local/bin/
ADD
https://raw.githubusercontent.com/eficode/wait-for/v2.
2
.3/wait-for /usr/local/bin/
RUN
chmod
+rx /usr/local/bin/wait-for
# Copy the startup file
...
...
.devcontainer/app-init.sh
View file @
36ba1eb1
...
...
@@ -2,7 +2,11 @@
cd
/workspace
echo
"Disabling git info in terminal..."
git config codespaces-theme.hide-status 1
git config oh-my-zsh.hide-info 1
echo
"Waiting for DB container to come online..."
/usr/local/bin/wait-for localhost:5432
--
echo
"DB ready"
echo
"Ready!"
.devcontainer/devcontainer.json
View file @
36ba1eb1
...
...
@@ -32,10 +32,10 @@
"arcanis.vscode-zipfs"
,
"dbaeumer.vscode-eslint"
,
"eamodio.gitlens"
,
"
johnsoncodehk
.volar"
,
"
Vue
.volar"
,
"oderwat.indent-rainbow"
,
"redhat.vscode-yaml"
,
"
visualstudioexptt
eam.vscodeintellicode"
,
"
VisualStudioExptT
eam.vscodeintellicode"
,
"editorconfig.editorconfig"
,
"lokalise.i18n-ally"
,
"mrmlnc.vscode-duplicate"
,
...
...
.devcontainer/docker-compose.yml
View file @
36ba1eb1
...
...
@@ -6,10 +6,10 @@ services:
context
:
.
dockerfile
:
Dockerfile
args
:
# Update 'VARIANT' to pick an LTS version of Node.js: 16, 14, 12.
# Update 'VARIANT' to pick an LTS version of Node.js: 1
8, 1
6, 14, 12.
# Append -bullseye or -buster to pin to an OS version.
# Use -bullseye variants on local arm64/Apple Silicon.
VARIANT
:
1
6
-bullseye
VARIANT
:
1
8
-bullseye
volumes
:
-
..:/workspace
...
...
server/core/system.js
View file @
36ba1eb1
const
_
=
require
(
'lodash'
)
const
cfgHelper
=
require
(
'../helpers/config'
)
const
Promise
=
require
(
'bluebird'
)
const
fs
=
require
(
'fs-extra'
)
const
path
=
require
(
'path'
)
...
...
@@ -11,71 +8,13 @@ module.exports = {
channel
:
'BETA'
,
version
:
WIKI
.
version
,
releaseDate
:
WIKI
.
releaseDate
,
minimumVersionRequired
:
'
2
.0.0-beta.0'
,
minimumNodeRequired
:
'1
0.12
.0'
minimumVersionRequired
:
'
3
.0.0-beta.0'
,
minimumNodeRequired
:
'1
8.0
.0'
},
init
()
{
// Clear content cache
fs
.
emptyDir
(
path
.
resolve
(
WIKI
.
ROOTPATH
,
WIKI
.
config
.
dataPath
,
'cache'
))
return
this
},
/**
* Upgrade from WIKI.js 1.x - MongoDB database
*
* @param {Object} opts Options object
*/
async
upgradeFromMongo
(
opts
)
{
WIKI
.
logger
.
info
(
'Upgrading from MongoDB...'
)
let
mongo
=
require
(
'mongodb'
).
MongoClient
let
parsedMongoConStr
=
cfgHelper
.
parseConfigValue
(
opts
.
mongoCnStr
)
return
new
Promise
((
resolve
,
reject
)
=>
{
// Connect to MongoDB
mongo
.
connect
(
parsedMongoConStr
,
{
autoReconnect
:
false
,
reconnectTries
:
2
,
reconnectInterval
:
1000
,
connectTimeoutMS
:
5000
,
socketTimeoutMS
:
5000
},
async
(
err
,
db
)
=>
{
try
{
if
(
err
!==
null
)
{
throw
err
}
let
users
=
db
.
collection
(
'users'
)
// Check if users table is populated
let
userCount
=
await
users
.
count
()
if
(
userCount
<
2
)
{
throw
new
Error
(
'MongoDB Upgrade: Users table is empty!'
)
}
// Import all users
let
userData
=
await
users
.
find
({
email
:
{
$not
:
'guest'
}
}).
toArray
()
await
WIKI
.
models
.
User
.
bulkCreate
(
_
.
map
(
userData
,
usr
=>
{
return
{
email
:
usr
.
email
,
name
:
usr
.
name
||
'Imported User'
,
password
:
usr
.
password
||
''
,
provider
:
usr
.
provider
||
'local'
,
providerId
:
usr
.
providerId
||
''
,
role
:
'user'
,
createdAt
:
usr
.
createdAt
}
}))
resolve
(
true
)
}
catch
(
errc
)
{
reject
(
errc
)
}
db
.
close
()
})
})
}
}
server/index.js
View file @
36ba1eb1
...
...
@@ -8,8 +8,8 @@ const { nanoid } = require('nanoid')
const
{
DateTime
}
=
require
(
'luxon'
)
const
semver
=
require
(
'semver'
)
if
(
!
semver
.
satisfies
(
process
.
version
,
'>=1
6
'
))
{
console
.
error
(
'ERROR: Node.js 1
6
.x or later required!'
)
if
(
!
semver
.
satisfies
(
process
.
version
,
'>=1
8
'
))
{
console
.
error
(
'ERROR: Node.js 1
8
.x or later required!'
)
process
.
exit
(
1
)
}
...
...
ux/package.json
View file @
36ba1eb1
...
...
@@ -11,8 +11,8 @@
"lint"
:
"eslint --ext .js,.vue ./"
},
"dependencies"
:
{
"@apollo/client"
:
"3.6.
1
"
,
"@codemirror/autocomplete"
:
"0.20.
0
"
,
"@apollo/client"
:
"3.6.
4
"
,
"@codemirror/autocomplete"
:
"0.20.
1
"
,
"@codemirror/basic-setup"
:
"0.20.0"
,
"@codemirror/closebrackets"
:
"0.19.2"
,
"@codemirror/commands"
:
"0.20.0"
,
...
...
@@ -25,15 +25,15 @@
"@codemirror/lang-html"
:
"0.20.0"
,
"@codemirror/lang-javascript"
:
"0.20.0"
,
"@codemirror/lang-json"
:
"0.20.0"
,
"@codemirror/lang-markdown"
:
"0.20.
0
"
,
"@codemirror/lang-markdown"
:
"0.20.
1
"
,
"@codemirror/matchbrackets"
:
"0.19.4"
,
"@codemirror/search"
:
"0.20.1"
,
"@codemirror/state"
:
"0.20.0"
,
"@codemirror/tooltip"
:
"0.19.16"
,
"@codemirror/view"
:
"0.20.
3
"
,
"@codemirror/view"
:
"0.20.
6
"
,
"@lezer/common"
:
"0.16.0"
,
"@quasar/extras"
:
"1.1
3.6
"
,
"@tiptap/core"
:
"2.0.0-beta.17
5
"
,
"@quasar/extras"
:
"1.1
4.0
"
,
"@tiptap/core"
:
"2.0.0-beta.17
6
"
,
"@tiptap/extension-code-block"
:
"2.0.0-beta.37"
,
"@tiptap/extension-code-block-lowlight"
:
"2.0.0-beta.68"
,
"@tiptap/extension-color"
:
"2.0.0-beta.9"
,
...
...
@@ -44,9 +44,9 @@
"@tiptap/extension-highlight"
:
"2.0.0-beta.33"
,
"@tiptap/extension-history"
:
"2.0.0-beta.21"
,
"@tiptap/extension-image"
:
"2.0.0-beta.27"
,
"@tiptap/extension-mention"
:
"2.0.0-beta.9
6
"
,
"@tiptap/extension-mention"
:
"2.0.0-beta.9
7
"
,
"@tiptap/extension-placeholder"
:
"2.0.0-beta.48"
,
"@tiptap/extension-table"
:
"2.0.0-beta.4
8
"
,
"@tiptap/extension-table"
:
"2.0.0-beta.4
9
"
,
"@tiptap/extension-table-cell"
:
"2.0.0-beta.20"
,
"@tiptap/extension-table-header"
:
"2.0.0-beta.22"
,
"@tiptap/extension-table-row"
:
"2.0.0-beta.19"
,
...
...
@@ -55,38 +55,38 @@
"@tiptap/extension-text-align"
:
"2.0.0-beta.29"
,
"@tiptap/extension-text-style"
:
"2.0.0-beta.23"
,
"@tiptap/extension-typography"
:
"2.0.0-beta.20"
,
"@tiptap/starter-kit"
:
"2.0.0-beta.18
4
"
,
"@tiptap/starter-kit"
:
"2.0.0-beta.18
5
"
,
"@tiptap/vue-3"
:
"2.0.0-beta.91"
,
"@vue/apollo-option"
:
"4.0.0-alpha.1
6
"
,
"@vue/apollo-option"
:
"4.0.0-alpha.1
7
"
,
"apollo-upload-client"
:
"17.0.0"
,
"browser-fs-access"
:
"0.29.
4
"
,
"clipboard"
:
"2.0.1
0
"
,
"browser-fs-access"
:
"0.29.
5
"
,
"clipboard"
:
"2.0.1
1
"
,
"filesize"
:
"8.0.7"
,
"filesize-parser"
:
"1.5.0"
,
"graphql"
:
"16.
4
.0"
,
"graphql"
:
"16.
5
.0"
,
"graphql-tag"
:
"2.12.6"
,
"js-cookie"
:
"3.0.1"
,
"jwt-decode"
:
"3.1.2"
,
"lodash"
:
"4.17.21"
,
"luxon"
:
"2.
3.2
"
,
"pinia"
:
"2.0.1
3
"
,
"luxon"
:
"2.
4.0
"
,
"pinia"
:
"2.0.1
4
"
,
"pug"
:
"3.0.2"
,
"quasar"
:
"2.
6.6
"
,
"quasar"
:
"2.
7.0
"
,
"tippy.js"
:
"6.3.7"
,
"uuid"
:
"8.3.2"
,
"v-network-graph"
:
"0.5.1
3
"
,
"v-network-graph"
:
"0.5.1
6
"
,
"vue"
:
"3.2.31"
,
"vue-i18n"
:
"9.1.
9
"
,
"vue-router"
:
"4.0.1
4
"
,
"vue-i18n"
:
"9.1.
10
"
,
"vue-router"
:
"4.0.1
5
"
,
"vuedraggable"
:
"4.1.0"
,
"zxcvbn"
:
"4.4.2"
},
"devDependencies"
:
{
"@intlify/vite-plugin-vue-i18n"
:
"3.4.0"
,
"@quasar/app-vite"
:
"1.0.0
-beta.14
"
,
"@quasar/app-vite"
:
"1.0.0"
,
"@types/lodash"
:
"4.14.182"
,
"autoprefixer"
:
"10.4.
5
"
,
"eslint"
:
"8.1
4
.0"
,
"autoprefixer"
:
"10.4.
7
"
,
"eslint"
:
"8.1
6
.0"
,
"eslint-config-standard"
:
"17.0.0"
,
"eslint-plugin-import"
:
"2.26.0"
,
"eslint-plugin-n"
:
"15.2.0"
,
...
...
ux/src/components/LocaleInstallDialog.vue
View file @
36ba1eb1
<
template
lang=
"pug"
>
q-dialog(ref='dialog', @hide='onDialogHide')
q-dialog(ref='dialog
Ref
', @hide='onDialogHide')
q-card(style='min-width: 850px;')
q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-down.svg', left, size='sm')
span
{{
$
t
(
`admin.locale.downloadTitle`
)
}}
span
{{
t
(
`admin.locale.downloadTitle`
)
}}
q-card-section.q-pa-none
q-table.no-border-radius(
:data='locales'
:data='
state.
locales'
:columns='headers'
row-name='code'
flat
hide-bottom
:rows-per-page-options='[0]'
:loading='loading > 0'
:loading='
state.
loading > 0'
)
template(v-slot:body-cell-code='props')
q-td(:props='props')
...
...
@@ -81,90 +81,101 @@ q-dialog(ref='dialog', @hide='onDialogHide')
q-space
q-btn.acrylic-btn(
flat
:label='
$
t(`common.actions.close`)'
:label='t(`common.actions.close`)'
color='grey'
padding='xs md'
@click='
hide
'
@click='
onDialogCancel
'
)
q-inner-loading(:showing='
loading
')
q-inner-loading(:showing='
state.loading > 0
')
q-spinner(color='accent', size='lg')
</
template
>
<
script
>
// import gql from 'graphql-tag'
// import cloneDeep from 'lodash/cloneDeep'
export
default
{
emits
:
[
'ok'
,
'hide'
],
data
()
{
return
{
locales
:
[],
loading
:
0
}
<
script
setup
>
import
{
useI18n
}
from
'vue-i18n'
import
{
useDialogPluginComponent
,
useQuasar
}
from
'quasar'
import
{
reactive
,
ref
}
from
'vue'
import
{
useAdminStore
}
from
'../stores/admin'
// EMITS
defineEmits
([
...
useDialogPluginComponent
.
emits
])
// QUASAR
const
{
dialogRef
,
onDialogHide
,
onDialogOK
,
onDialogCancel
}
=
useDialogPluginComponent
()
const
$q
=
useQuasar
()
// STORES
const
adminStore
=
useAdminStore
()
// I18N
const
{
t
}
=
useI18n
()
// DATA
const
state
=
reactive
({
locales
:
[],
loading
:
0
})
const
headers
=
[
{
label
:
t
(
'admin.locale.code'
),
align
:
'left'
,
field
:
'code'
,
name
:
'code'
,
sortable
:
true
,
style
:
'width: 90px'
},
{
label
:
t
(
'admin.locale.name'
),
align
:
'left'
,
field
:
'name'
,
name
:
'name'
,
sortable
:
true
},
{
label
:
t
(
'admin.locale.nativeName'
),
align
:
'left'
,
field
:
'nativeName'
,
name
:
'nativeName'
,
sortable
:
true
},
computed
:
{
headers
()
{
return
[
{
label
:
this
.
$t
(
'admin.locale.code'
),
align
:
'left'
,
field
:
'code'
,
name
:
'code'
,
sortable
:
true
,
style
:
'width: 90px'
},
{
label
:
this
.
$t
(
'admin.locale.name'
),
align
:
'left'
,
field
:
'name'
,
name
:
'name'
,
sortable
:
true
},
{
label
:
this
.
$t
(
'admin.locale.nativeName'
),
align
:
'left'
,
field
:
'nativeName'
,
name
:
'nativeName'
,
sortable
:
true
},
{
label
:
this
.
$t
(
'admin.locale.rtl'
),
align
:
'center'
,
field
:
'isRTL'
,
name
:
'isRTL'
,
sortable
:
false
,
style
:
'width: 10px'
},
{
label
:
this
.
$t
(
'admin.locale.availability'
),
align
:
'center'
,
field
:
'availability'
,
name
:
'availability'
,
sortable
:
false
,
style
:
'width: 120px'
},
{
label
:
this
.
$t
(
'admin.locale.download'
),
align
:
'center'
,
field
:
'isInstalled'
,
name
:
'isInstalled'
,
sortable
:
false
,
style
:
'width: 100px'
}
]
}
{
label
:
t
(
'admin.locale.rtl'
),
align
:
'center'
,
field
:
'isRTL'
,
name
:
'isRTL'
,
sortable
:
false
,
style
:
'width: 10px'
},
methods
:
{
show
()
{
this
.
$refs
.
dialog
.
show
()
},
hide
()
{
this
.
$refs
.
dialog
.
hide
()
},
onDialogHide
()
{
this
.
$emit
(
'hide'
)
}
{
label
:
t
(
'admin.locale.availability'
),
align
:
'center'
,
field
:
'availability'
,
name
:
'availability'
,
sortable
:
false
,
style
:
'width: 120px'
},
{
label
:
t
(
'admin.locale.download'
),
align
:
'center'
,
field
:
'isInstalled'
,
name
:
'isInstalled'
,
sortable
:
false
,
style
:
'width: 100px'
}
]
// METHODS
async
function
download
(
lc
)
{
}
</
script
>
ux/src/pages/AdminEditors.vue
View file @
36ba1eb1
...
...
@@ -16,7 +16,7 @@ q-page.admin-flags
type='a'
)
q-btn.q-mr-sm.acrylic-btn(
icon='
fa-solid fa-rotate
'
icon='
las la-redo-alt
'
flat
color='secondary'
:loading='loading > 0'
...
...
ux/src/pages/AdminFlags.vue
View file @
36ba1eb1
...
...
@@ -11,7 +11,7 @@ q-page.admin-flags
icon='las la-question-circle'
flat
color='grey'
href='https://docs.
requarks.io
/admin/flags'
href='https://docs.
js.wiki
/admin/flags'
target='_blank'
type='a'
)
...
...
ux/src/pages/AdminGeneral.vue
View file @
36ba1eb1
...
...
@@ -16,7 +16,7 @@ q-page.admin-general
type='a'
)
q-btn.q-mr-sm.acrylic-btn(
icon='
fa-solid fa-rotate
'
icon='
las la-redo-alt
'
flat
color='secondary'
:loading='state.loading > 0'
...
...
@@ -385,7 +385,6 @@ import { onMounted, reactive, watch } from 'vue'
import
{
useAdminStore
}
from
'src/stores/admin'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
useDataStore
}
from
'src/stores/data'
import
{
useRoute
,
useRouter
}
from
'vue-router'
// QUASAR
...
...
@@ -397,11 +396,6 @@ const adminStore = useAdminStore()
const
siteStore
=
useSiteStore
()
const
dataStore
=
useDataStore
()
// ROUTER
const
router
=
useRouter
()
const
route
=
useRoute
()
// I18N
const
{
t
}
=
useI18n
()
...
...
ux/src/pages/AdminLocale.vue
View file @
36ba1eb1
...
...
@@ -4,15 +4,15 @@ q-page.admin-locale
.col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-language.svg')
.col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft
{{
$
t
(
'admin.locale.title'
)
}}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s
{{
$
t
(
'admin.locale.subtitle'
)
}}
.text-h5.text-primary.animated.fadeInLeft
{{
t
(
'admin.locale.title'
)
}}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s
{{
t
(
'admin.locale.subtitle'
)
}}
.col-auto.flex
q-btn.q-mr-md(
icon='las la-download'
:label='
$
t(`admin.locale.downloadNew`)'
:label='t(`admin.locale.downloadNew`)'
unelevated
color='primary'
:disabled='loading > 0'
:disabled='
state.
loading > 0'
@click='installNewLocale'
)
q-separator.q-mr-md(vertical)
...
...
@@ -20,7 +20,7 @@ q-page.admin-locale
icon='las la-question-circle'
flat
color='grey'
href='https://docs.
requarks.io
/admin/locale'
href='https://docs.
js.wiki
/admin/locale'
target='_blank'
type='a'
)
...
...
@@ -28,16 +28,16 @@ q-page.admin-locale
icon='las la-redo-alt'
flat
color='secondary'
:loading='loading > 0'
:loading='
state.
loading > 0'
@click='load'
)
q-btn(
unelevated
icon='
mdi
-check'
:label='
$
t(`common.actions.apply`)'
icon='
fa-solid fa
-check'
:label='t(`common.actions.apply`)'
color='secondary'
@click='save'
:disabled='loading > 0'
:disabled='
state.
loading > 0'
)
q-separator(inset)
.row.q-pa-md.q-col-gutter-md
...
...
@@ -47,37 +47,37 @@ q-page.admin-locale
//- -----------------------
q-card.shadow-1.q-pb-sm
q-card-section
.text-subtitle1
{{
$
t
(
'admin.locale.settings'
)
}}
.text-subtitle1
{{
t
(
'admin.locale.settings'
)
}}
q-item
blueprint-icon(icon='translation')
q-item-section
q-item-label
{{
namespacing
?
$t
(
`admin.locale.base.labelWithNS`
)
:
$
t
(
`admin.locale.base.label`
)
}}
q-item-label(caption)
{{
$
t
(
`admin.locale.base.hint`
)
}}
q-item-label
{{
state
.
namespacing
?
t
(
`admin.locale.base.labelWithNS`
)
:
t
(
`admin.locale.base.label`
)
}}
q-item-label(caption)
{{
t
(
`admin.locale.base.hint`
)
}}
q-item-section
q-select(
outlined
v-model='selectedLocale'
v-model='s
tate.s
electedLocale'
:options='installedLocales'
option-value='code'
option-label='name'
emit-value
map-options
dense
:aria-label='
$
t(`admin.locale.base.label`)'
:aria-label='t(`admin.locale.base.label`)'
)
q-separator.q-my-sm(inset)
q-item(tag='label', v-ripple)
blueprint-icon(icon='unit')
q-item-section
q-item-label
{{
$
t
(
`admin.locale.namespaces.label`
)
}}
q-item-label(caption)
{{
$
t
(
`admin.locale.namespaces.hint`
)
}}
q-item-label
{{
t
(
`admin.locale.namespaces.label`
)
}}
q-item-label(caption)
{{
t
(
`admin.locale.namespaces.hint`
)
}}
q-item-section(avatar)
q-toggle(
v-model='namespacing'
v-model='
state.
namespacing'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='
$
t(`admin.locale.namespaces.label`)'
:aria-label='t(`admin.locale.namespaces.label`)'
)
q-item
q-item-section
...
...
@@ -86,21 +86,21 @@ q-page.admin-locale
q-card-section.col-auto.q-pr-none
q-icon(name='las la-info-circle', size='sm')
q-card-section
span
{{
$t
(
'admin.locale.namespacingPrefixWarning.title'
,
{
langCode
:
selectedLocale
}
)
}}
.
text
-
caption
.
text
-
yellow
-
1
{{
$
t
(
'admin.locale.namespacingPrefixWarning.subtitle'
)
}}
span
{{
t
(
'admin.locale.namespacingPrefixWarning.title'
,
{
langCode
:
state
.
selectedLocale
}
)
}}
.
text
-
caption
.
text
-
yellow
-
1
{{
t
(
'admin.locale.namespacingPrefixWarning.subtitle'
)
}}
.
col
-
5
//- -----------------------
//- Namespacing
//- -----------------------
q
-
card
.
shadow
-
1
.
q
-
pb
-
sm
(
v
-
if
=
'namespacing'
)
q
-
card
.
shadow
-
1
.
q
-
pb
-
sm
(
v
-
if
=
'
state.
namespacing'
)
q
-
card
-
section
.
text
-
subtitle1
{{
$
t
(
'admin.locale.activeNamespaces'
)
}}
.
text
-
subtitle1
{{
t
(
'admin.locale.activeNamespaces'
)
}}
q
-
item
(
v
-
for
=
'(lc, idx) of installedLocales'
:
key
=
'lc.code'
:
tag
=
'lc.code !== selectedLocale ? `label` : null'
:
tag
=
'lc.code !== s
tate.s
electedLocale ? `label` : null'
)
blueprint
-
icon
(:
text
=
'lc.code'
)
q
-
item
-
section
...
...
@@ -108,8 +108,8 @@ q-page.admin-locale
q
-
item
-
label
(
caption
)
{{
lc
.
nativeName
}}
q
-
item
-
section
(
avatar
)
q
-
toggle
(
:
disable
=
'lc.code === selectedLocale'
v
-
model
=
'namespaces'
:
disable
=
'lc.code === s
tate.s
electedLocale'
v
-
model
=
'
state.
namespaces'
:
val
=
'lc.code'
color
=
'primary'
checked
-
icon
=
'las la-check'
...
...
@@ -121,8 +121,8 @@ q-page.admin-locale
//- q-item
//- blueprint-icon(icon='test-passed')
//- q-item-section
//- q-item-label
{{
$
t
(
`admin.locale.activeNamespaces.label`
)
}}
//- q-item-label(caption)
{{
$
t
(
`admin.locale.activeNamespaces.hint`
)
}}
//- q-item-label
{{
t
(
`admin.locale.activeNamespaces.label`
)
}}
//- q-item-label(caption)
{{
t
(
`admin.locale.activeNamespaces.hint`
)
}}
//- q-item-section
//- q-select(
//- outlined
...
...
@@ -136,204 +136,228 @@ q-page.admin-locale
//- emit-value
//- map-options
//- dense
//- :aria-label='
$
t(`admin.locale.activeNamespaces.label`)'
//- :aria-label='t(`admin.locale.activeNamespaces.label`)'
//- )
<
/template
>
<
script
>
import
{
get
}
from
'vuex-pathify'
<
script
setup
>
import
gql
from
'graphql-tag'
import
filter
from
'lodash/filter'
import
_get
from
'lodash/get'
import
cloneDeep
from
'lodash/cloneDeep'
import
{
createMetaMixin
}
from
'quasar'
import
LocaleInstallDialog
from
'../components/LocaleInstallDialog.vue'
export
default
{
mixins
:
[
createMetaMixin
(
function
()
{
return
{
title
:
this
.
$t
(
'admin.locale.title'
)
}
}
)
],
data
()
{
return
{
loading
:
0
,
locales
:
[],
selectedLocale
:
'en'
,
namespacing
:
false
,
namespaces
:
[]
}
}
,
computed
:
{
currentSiteId
:
get
(
'admin/currentSiteId'
,
false
),
installedLocales
()
{
return
filter
(
this
.
locales
,
[
'isInstalled'
,
true
])
}
}
,
watch
:
{
currentSiteId
(
newValue
)
{
this
.
load
()
}
,
selectedLocale
(
newValue
)
{
if
(
!
this
.
namespaces
.
includes
(
newValue
))
{
this
.
namespaces
.
push
(
newValue
)
}
}
}
,
mounted
()
{
if
(
this
.
currentSiteId
)
{
this
.
load
()
}
}
,
methods
:
{
installNewLocale
()
{
this
.
$q
.
dialog
({
component
:
LocaleInstallDialog
}
).
onOk
(()
=>
{
this
.
load
()
}
)
}
,
async
load
()
{
this
.
loading
++
this
.
$q
.
loading
.
show
()
const
resp
=
await
this
.
$apollo
.
query
({
query
:
gql
`
query getLocales ($siteId: UUID!) {
locales {
availability
code
createdAt
isInstalled
installDate
isRTL
name
nativeName
updatedAt
}
siteById(
id: $siteId
) {
id
locale
localeNamespacing
localeNamespaces
}
}
`
,
variables
:
{
siteId
:
this
.
currentSiteId
}
,
fetchPolicy
:
'network-only'
}
)
this
.
locales
=
cloneDeep
(
resp
?.
data
?.
locales
)
this
.
selectedLocale
=
cloneDeep
(
resp
?.
data
?.
siteById
?.
locale
)
this
.
namespacing
=
cloneDeep
(
resp
?.
data
?.
siteById
?.
localeNamespacing
)
this
.
namespaces
=
cloneDeep
(
resp
?.
data
?.
siteById
?.
localeNamespaces
)
if
(
!
this
.
namespaces
.
includes
(
this
.
selectedLocale
))
{
this
.
namespaces
.
push
(
this
.
selectedLocale
)
import
{
useI18n
}
from
'vue-i18n'
import
{
useMeta
,
useQuasar
}
from
'quasar'
import
{
computed
,
onMounted
,
reactive
,
watch
}
from
'vue'
import
{
useAdminStore
}
from
'src/stores/admin'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
useDataStore
}
from
'src/stores/data'
// QUASAR
const
$q
=
useQuasar
()
// STORES
const
adminStore
=
useAdminStore
()
const
siteStore
=
useSiteStore
()
const
dataStore
=
useDataStore
()
// I18N
const
{
t
}
=
useI18n
()
// META
useMeta
({
title
:
t
(
'admin.locale.title'
)
}
)
// DATA
const
state
=
reactive
({
loading
:
0
,
locales
:
[],
selectedLocale
:
'en'
,
namespacing
:
false
,
namespaces
:
[]
}
)
// COMPUTED
const
installedLocales
=
computed
(()
=>
{
return
filter
(
state
.
locales
,
[
'isInstalled'
,
true
])
}
)
// WATCHERS
watch
(()
=>
adminStore
.
currentSiteId
,
(
newValue
)
=>
{
load
()
}
)
watch
(()
=>
state
.
selectedLocale
,
(
newValue
)
=>
{
if
(
!
state
.
namespaces
.
includes
(
newValue
))
{
state
.
namespaces
.
push
(
newValue
)
}
}
)
// METHODS
function
installNewLocale
()
{
$q
.
dialog
({
component
:
LocaleInstallDialog
}
).
onOk
(()
=>
{
this
.
load
()
}
)
}
async
function
load
()
{
state
.
loading
++
$q
.
loading
.
show
()
const
resp
=
await
APOLLO_CLIENT
.
query
({
query
:
gql
`
query getLocales ($siteId: UUID!) {
locales {
availability
code
createdAt
isInstalled
installDate
isRTL
name
nativeName
updatedAt
}
siteById(
id: $siteId
) {
id
locale
localeNamespacing
localeNamespaces
}
}
this
.
$q
.
loading
.
hide
()
this
.
loading
--
`
,
variables
:
{
siteId
:
adminStore
.
currentSiteId
}
,
async
download
(
lc
)
{
lc
.
isDownloading
=
true
const
respRaw
=
await
this
.
$apollo
.
mutate
({
mutation
:
gql
`
mutation downloadLocale ($locale: String!) {
localization {
downloadLocale (locale: $locale) {
responseResult {
succeeded
errorCode
slug
message
}
}
fetchPolicy
:
'network-only'
}
)
state
.
locales
=
cloneDeep
(
resp
?.
data
?.
locales
)
state
.
selectedLocale
=
cloneDeep
(
resp
?.
data
?.
siteById
?.
locale
)
state
.
namespacing
=
cloneDeep
(
resp
?.
data
?.
siteById
?.
localeNamespacing
)
state
.
namespaces
=
cloneDeep
(
resp
?.
data
?.
siteById
?.
localeNamespaces
)
if
(
!
state
.
namespaces
.
includes
(
state
.
selectedLocale
))
{
state
.
namespaces
.
push
(
state
.
selectedLocale
)
}
$q
.
loading
.
hide
()
state
.
loading
--
}
async
function
download
(
lc
)
{
lc
.
isDownloading
=
true
const
respRaw
=
await
APOLLO_CLIENT
.
mutate
({
mutation
:
gql
`
mutation downloadLocale ($locale: String!) {
localization {
downloadLocale (locale: $locale) {
responseResult {
succeeded
errorCode
slug
message
}
}
`
,
variables
:
{
locale
:
lc
.
code
}
}
)
const
resp
=
_get
(
respRaw
,
'data.localization.downloadLocale.responseResult'
,
{
}
)
if
(
resp
.
succeeded
)
{
lc
.
isDownloading
=
false
lc
.
isInstalled
=
true
lc
.
updatedAt
=
new
Date
().
toISOString
()
lc
.
installDate
=
lc
.
updatedAt
this
.
$store
.
commit
(
'showNotification'
,
{
message
:
`Locale ${lc.name
}
has been installed successfully.`
,
style
:
'success'
,
icon
:
'get_app'
}
)
}
else
{
this
.
$q
.
notify
({
type
:
'negative'
,
message
:
resp
.
message
}
)
}
this
.
isDownloading
=
false
}
,
async
save
()
{
this
.
loading
=
true
const
respRaw
=
await
this
.
$apollo
.
mutate
({
mutation
:
gql
`
mutation saveLocaleSettings (
$locale: String!
$autoUpdate: Boolean!
$namespacing: Boolean!
$namespaces: [String]!
`
,
variables
:
{
locale
:
lc
.
code
}
}
)
const
resp
=
_get
(
respRaw
,
'data.localization.downloadLocale.responseResult'
,
{
}
)
if
(
resp
.
succeeded
)
{
lc
.
isDownloading
=
false
lc
.
isInstalled
=
true
lc
.
updatedAt
=
new
Date
().
toISOString
()
lc
.
installDate
=
lc
.
updatedAt
$q
.
notify
({
message
:
`Locale ${lc.name
}
has been installed successfully.`
,
type
:
'positive'
}
)
}
else
{
$q
.
notify
({
type
:
'negative'
,
message
:
resp
.
message
}
)
}
state
.
isDownloading
=
false
}
async
function
save
()
{
state
.
loading
=
true
const
respRaw
=
await
APOLLO_CLIENT
.
mutate
({
mutation
:
gql
`
mutation saveLocaleSettings (
$locale: String!
$autoUpdate: Boolean!
$namespacing: Boolean!
$namespaces: [String]!
) {
localization {
updateLocale(
locale: $locale
autoUpdate: $autoUpdate
namespacing: $namespacing
namespaces: $namespaces
) {
localization {
updateLocale(
locale: $locale
autoUpdate: $autoUpdate
namespacing: $namespacing
namespaces: $namespaces
) {
responseResult {
succeeded
errorCode
slug
message
}
}
responseResult {
succeeded
errorCode
slug
message
}
}
`
,
variables
:
{
locale
:
this
.
selectedLocale
,
autoUpdate
:
this
.
autoUpdate
,
namespacing
:
this
.
namespacing
,
namespaces
:
this
.
namespaces
}
}
)
const
resp
=
_get
(
respRaw
,
'data.localization.updateLocale.responseResult'
,
{
}
)
if
(
resp
.
succeeded
)
{
// Change UI language
this
.
$i18n
.
locale
=
this
.
selectedLocale
this
.
$q
.
notify
({
type
:
'positive'
,
message
:
'Locale settings updated successfully.'
}
)
setTimeout
(()
=>
{
window
.
location
.
reload
(
true
)
}
,
1000
)
}
else
{
this
.
$q
.
notify
({
type
:
'negative'
,
message
:
resp
.
message
}
)
}
this
.
loading
=
false
`
,
variables
:
{
locale
:
state
.
selectedLocale
,
autoUpdate
:
state
.
autoUpdate
,
namespacing
:
state
.
namespacing
,
namespaces
:
state
.
namespaces
}
}
)
const
resp
=
_get
(
respRaw
,
'data.localization.updateLocale.responseResult'
,
{
}
)
if
(
resp
.
succeeded
)
{
// Change UI language
this
.
$i18n
.
locale
=
state
.
selectedLocale
$q
.
notify
({
type
:
'positive'
,
message
:
'Locale settings updated successfully.'
}
)
setTimeout
(()
=>
{
window
.
location
.
reload
(
true
)
}
,
1000
)
}
else
{
$q
.
notify
({
type
:
'negative'
,
message
:
resp
.
message
}
)
}
state
.
loading
=
false
}
// MOUNTED
onMounted
(()
=>
{
if
(
adminStore
.
currentSiteId
)
{
load
()
}
}
)
<
/script
>
ux/src/pages/AdminLogin.vue
View file @
36ba1eb1
...
...
@@ -4,8 +4,8 @@ q-page.admin-login
.col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-bunch-of-keys.svg')
.col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft
{{
$
t
(
'admin.login.title'
)
}}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s
{{
$
t
(
'admin.login.subtitle'
)
}}
.text-h5.text-primary.animated.fadeInLeft
{{
t
(
'admin.login.title'
)
}}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s
{{
t
(
'admin.login.subtitle'
)
}}
.col-auto
q-btn.q-mr-sm.acrylic-btn(
icon='las la-question-circle'
...
...
@@ -19,16 +19,16 @@ q-page.admin-login
icon='las la-redo-alt'
flat
color='secondary'
:loading='loading > 0'
:loading='
state.
loading > 0'
@click='load'
)
q-btn(
unelevated
icon='
mdi
-check'
:label='
$
t(`common.actions.apply`)'
icon='
fa-solid fa
-check'
:label='t(`common.actions.apply`)'
color='secondary'
@click='save'
:disabled='loading > 0'
:disabled='
state.
loading > 0'
)
q-separator(inset)
.row.q-pa-md.q-col-gutter-md
...
...
@@ -38,12 +38,12 @@ q-page.admin-login
//- -----------------------
q-card.shadow-1.q-pb-sm
q-card-section
.text-subtitle1
{{
$
t
(
'admin.login.experience'
)
}}
.text-subtitle1
{{
t
(
'admin.login.experience'
)
}}
q-item(tag='label', v-ripple)
blueprint-icon(icon='full-image', indicator, :indicator-text='
$
t(`admin.extensions.requiresSharp`)')
blueprint-icon(icon='full-image', indicator, :indicator-text='t(`admin.extensions.requiresSharp`)')
q-item-section
q-item-label
{{
$
t
(
`admin.login.background`
)
}}
q-item-label(caption)
{{
$
t
(
`admin.login.backgroundHint`
)
}}
q-item-label
{{
t
(
`admin.login.background`
)
}}
q-item-label(caption)
{{
t
(
`admin.login.backgroundHint`
)
}}
q-item-section.col-auto
q-btn(
label='Upload'
...
...
@@ -56,80 +56,80 @@ q-page.admin-login
q-item(tag='label', v-ripple)
blueprint-icon(icon='close-pane')
q-item-section
q-item-label
{{
$
t
(
`admin.login.bypassScreen`
)
}}
q-item-label(caption)
{{
$
t
(
`admin.login.bypassScreenHint`
)
}}
q-item-label
{{
t
(
`admin.login.bypassScreen`
)
}}
q-item-label(caption)
{{
t
(
`admin.login.bypassScreenHint`
)
}}
q-item-section(avatar)
q-toggle(
v-model='config.authAutoLogin'
v-model='
state.
config.authAutoLogin'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='
$
t(`admin.login.bypassScreen`)'
:aria-label='t(`admin.login.bypassScreen`)'
)
q-separator.q-my-sm(inset)
q-item(tag='label', v-ripple)
blueprint-icon(icon='no-access')
q-item-section
q-item-label
{{
$
t
(
`admin.login.bypassUnauthorized`
)
}}
q-item-label(caption)
{{
$
t
(
`admin.login.bypassUnauthorizedHint`
)
}}
q-item-label
{{
t
(
`admin.login.bypassUnauthorized`
)
}}
q-item-label(caption)
{{
t
(
`admin.login.bypassUnauthorizedHint`
)
}}
q-item-section(avatar)
q-toggle(
v-model='config.authBypassUnauthorized'
v-model='
state.
config.authBypassUnauthorized'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='
$
t(`admin.login.bypassUnauthorized`)'
:aria-label='t(`admin.login.bypassUnauthorized`)'
)
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='double-right')
q-item-section
q-item-label
{{
$
t
(
`admin.login.loginRedirect`
)
}}
q-item-label(caption)
{{
$
t
(
`admin.login.loginRedirectHint`
)
}}
q-item-label
{{
t
(
`admin.login.loginRedirect`
)
}}
q-item-label(caption)
{{
t
(
`admin.login.loginRedirectHint`
)
}}
q-item-section
q-input(
outlined
v-model='config.loginRedirect'
v-model='
state.
config.loginRedirect'
dense
:rules=`[
val =>
invalidCharsRegex.test(val) || $
t('admin.login.loginRedirectInvalidChars')
val =>
state.invalidCharsRegex.test(val) ||
t('admin.login.loginRedirectInvalidChars')
]`
hide-bottom-space
:aria-label='
$
t(`admin.login.loginRedirect`)'
:aria-label='t(`admin.login.loginRedirect`)'
)
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='chevron-right')
q-item-section
q-item-label
{{
$
t
(
`admin.login.welcomeRedirect`
)
}}
q-item-label(caption)
{{
$
t
(
`admin.login.welcomeRedirectHint`
)
}}
q-item-label
{{
t
(
`admin.login.welcomeRedirect`
)
}}
q-item-label(caption)
{{
t
(
`admin.login.welcomeRedirectHint`
)
}}
q-item-section
q-input(
outlined
v-model='config.welcomeRedirect'
v-model='
state.
config.welcomeRedirect'
dense
:rules=`[
val =>
invalidCharsRegex.test(val) || $
t('admin.login.welcomeRedirectInvalidChars')
val =>
state.invalidCharsRegex.test(val) ||
t('admin.login.welcomeRedirectInvalidChars')
]`
hide-bottom-space
:aria-label='
$
t(`admin.login.welcomeRedirect`)'
:aria-label='t(`admin.login.welcomeRedirect`)'
)
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='exit')
q-item-section
q-item-label
{{
$
t
(
`admin.login.logoutRedirect`
)
}}
q-item-label(caption)
{{
$
t
(
`admin.login.logoutRedirectHint`
)
}}
q-item-label
{{
t
(
`admin.login.logoutRedirect`
)
}}
q-item-label(caption)
{{
t
(
`admin.login.logoutRedirectHint`
)
}}
q-item-section
q-input(
outlined
v-model='config.logoutRedirect'
v-model='
state.
config.logoutRedirect'
dense
:rules=`[
val =>
invalidCharsRegex.test(val) || $
t('admin.login.logoutRedirectInvalidChars')
val =>
state.invalidCharsRegex.test(val) ||
t('admin.login.logoutRedirectInvalidChars')
]`
hide-bottom-space
:aria-label='
$
t(`admin.login.logoutRedirect`)'
:aria-label='t(`admin.login.logoutRedirect`)'
)
.col-12.col-lg-6
...
...
@@ -138,11 +138,11 @@ q-page.admin-login
//- -----------------------
q-card.shadow-1.q-pb-sm
q-card-section
.text-subtitle1
{{
$
t
(
'admin.login.providers'
)
}}
.text-subtitle1
{{
t
(
'admin.login.providers'
)
}}
q-card-section.admin-login-providers.q-pt-none
draggable(
class='q-list rounded-borders'
:list='providers'
:list='
state.
providers'
:animation='150'
handle='.handle'
@end='dragStarted = false'
...
...
@@ -171,117 +171,137 @@ q-page.admin-login
q-card-section.items-center(horizontal)
q-card-section.col-auto.q-pr-none
q-icon(name='las la-info-circle', size='sm')
q-card-section.text-caption
{{
$
t
(
'admin.login.providersVisbleWarning'
)
}}
q-card-section.text-caption
{{
t
(
'admin.login.providersVisbleWarning'
)
}}
</
template
>
<
script
>
<
script
setup
>
import
{
get
}
from
'vuex-pathify'
import
cloneDeep
from
'lodash/cloneDeep'
import
gql
from
'graphql-tag'
import
draggable
from
'vuedraggable'
import
{
createMetaMixin
}
from
'quasar'
export
default
{
mixins
:
[
createMetaMixin
(
function
()
{
return
{
title
:
this
.
$t
(
'admin.login.title'
)
}
})
],
components
:
{
draggable
},
data
()
{
return
{
invalidCharsRegex
:
/^
[^
<>"
]
+$/
,
loading
:
0
,
config
:
{
authAutoLogin
:
false
,
authHideLocal
:
false
,
authBypassUnauthorized
:
false
,
loginRedirect
:
'/'
,
welcomeRedirect
:
'/'
,
logoutRedirect
:
'/'
},
providers
:
[
{
id
:
'local'
,
label
:
'Local Authentication'
,
provider
:
'Username-Password'
,
icon
:
'database'
,
isActive
:
true
},
{
id
:
'google'
,
label
:
'Google'
,
provider
:
'Google'
,
icon
:
'google'
,
isActive
:
true
},
{
id
:
'slack'
,
label
:
'Slack'
,
provider
:
'Slack'
,
icon
:
'slack'
,
isActive
:
false
}
]
}
},
computed
:
{
currentSiteId
:
get
(
'admin/currentSiteId'
,
false
)
import
{
useI18n
}
from
'vue-i18n'
import
{
useMeta
,
useQuasar
}
from
'quasar'
import
{
computed
,
onMounted
,
reactive
,
watch
}
from
'vue'
import
{
useAdminStore
}
from
'src/stores/admin'
// QUASAR
const
$q
=
useQuasar
()
// STORES
const
adminStore
=
useAdminStore
()
// I18N
const
{
t
}
=
useI18n
()
// META
useMeta
({
title
:
t
(
'admin.login.title'
)
})
// DATA
const
state
=
reactive
({
invalidCharsRegex
:
/^
[^
<>"
]
+$/
,
loading
:
0
,
config
:
{
authAutoLogin
:
false
,
authHideLocal
:
false
,
authBypassUnauthorized
:
false
,
loginRedirect
:
'/'
,
welcomeRedirect
:
'/'
,
logoutRedirect
:
'/'
},
methods
:
{
async
load
()
{
this
.
loading
++
this
.
$q
.
loading
.
show
()
// const resp = await this.$apollo.query({
// query: gql`
// query getSite (
// $id: UUID!
// ) {
// siteById(
// id: $id
// ) {
// id
// }
// }
// `,
// variables: {
// id: this.currentSiteId
// },
// fetchPolicy: 'network-only'
// })
// this.config = cloneDeep(resp?.data?.siteById)
this
.
$q
.
loading
.
hide
()
this
.
loading
--
},
async
save
()
{
try
{
await
this
.
$apollo
.
mutate
({
mutation
:
gql
`
mutation saveLoginSettings (
$authAutoLogin: Boolean
$authEnforce2FA: Boolean
providers
:
[
{
id
:
'local'
,
label
:
'Local Authentication'
,
provider
:
'Username-Password'
,
icon
:
'database'
,
isActive
:
true
},
{
id
:
'google'
,
label
:
'Google'
,
provider
:
'Google'
,
icon
:
'google'
,
isActive
:
true
},
{
id
:
'slack'
,
label
:
'Slack'
,
provider
:
'Slack'
,
icon
:
'slack'
,
isActive
:
false
}
]
})
// METHODS
async
function
load
()
{
state
.
loading
++
$q
.
loading
.
show
()
// const resp = await APOLLO_CLIENT.query({
// query: gql`
// query getSite (
// $id: UUID!
// ) {
// siteById(
// id: $id
// ) {
// id
// }
// }
// `,
// variables: {
// id: adminStore.currentSiteId
// },
// fetchPolicy: 'network-only'
// })
// this.config = cloneDeep(resp?.data?.siteById)
$q
.
loading
.
hide
()
state
.
loading
--
}
async
function
save
()
{
try
{
await
APOLLO_CLIENT
.
mutate
({
mutation
:
gql
`
mutation saveLoginSettings (
$authAutoLogin: Boolean
$authEnforce2FA: Boolean
) {
site {
updateConfig(
authAutoLogin: $authAutoLogin,
authEnforce2FA: $authEnforce2FA
) {
site {
updateConfig(
authAutoLogin: $authAutoLogin,
authEnforce2FA: $authEnforce2FA
) {
responseResult {
succeeded
errorCode
slug
message
}
}
responseResult {
succeeded
errorCode
slug
message
}
}
`
,
variables
:
{
authAutoLogin
:
this
.
config
.
authAutoLogin
??
false
,
authEnforce2FA
:
this
.
config
.
authEnforce2FA
??
false
},
watchLoading
(
isLoading
)
{
this
.
$store
.
commit
(
`loading
${
isLoading
?
'Start'
:
'Stop'
}
`
,
'admin-site-update'
)
}
}
)
this
.
$store
.
commit
(
'showNotification'
,
{
style
:
'success'
,
message
:
'Configuration saved successfully.'
,
icon
:
'check'
})
}
catch
(
err
)
{
this
.
$store
.
commit
(
'pushGraphError'
,
err
)
}
`
,
variables
:
{
authAutoLogin
:
state
.
config
.
authAutoLogin
??
false
,
authEnforce2FA
:
state
.
config
.
authEnforce2FA
??
false
},
watchLoading
(
isLoading
)
{
this
.
$store
.
commit
(
`loading
${
isLoading
?
'Start'
:
'Stop'
}
`
,
'admin-site-update'
)
}
}
})
$q
.
notify
({
type
:
'positive'
,
message
:
'Configuration saved successfully.'
})
}
catch
(
err
)
{
$q
.
notify
({
type
:
'negative'
,
message
:
err
.
message
})
}
}
// MOUNTED
onMounted
(()
=>
{
if
(
adminStore
.
currentSiteId
)
{
load
()
}
})
</
script
>
<
style
lang=
'scss'
>
...
...
ux/src/pages/AdminSites.vue
View file @
36ba1eb1
...
...
@@ -16,7 +16,7 @@ q-page.admin-locale
target='_blank'
)
q-btn.q-mr-sm.acrylic-btn(
icon='
fa-solid fa-rotate
'
icon='
las la-redo-alt
'
flat
color='secondary'
@click='refresh'
...
...
@@ -100,7 +100,7 @@ q-page.admin-locale
<
script
setup
>
import
{
useMeta
,
useQuasar
}
from
'quasar'
import
{
useI18n
}
from
'vue-i18n'
import
{
defineAsyncComponent
,
nextTick
,
onMounted
,
reactive
,
ref
,
watch
}
from
'vue'
import
{
nextTick
,
onMounted
}
from
'vue'
import
{
useRouter
}
from
'vue-router'
import
{
useAdminStore
}
from
'../stores/admin'
...
...
ux/src/pages/AdminStorage.vue
View file @
36ba1eb1
...
...
@@ -4,22 +4,22 @@ q-page.admin-storage
.col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-ssd.svg')
.col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft
{{
$
t
(
'admin.storage.title'
)
}}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s
{{
$
t
(
'admin.storage.subtitle'
)
}}
.text-h5.text-primary.animated.fadeInLeft
{{
t
(
'admin.storage.title'
)
}}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s
{{
t
(
'admin.storage.subtitle'
)
}}
.col-auto.flex
q-spinner-tail.q-mr-md(
v-show='loading > 0'
v-show='
state.
loading > 0'
color='accent'
size='sm'
)
q-btn-toggle.q-mr-md(
v-model='displayMode'
v-model='
state.
displayMode'
push
no-caps
toggle-color='black'
:options=`[
{ label:
$
t('admin.storage.targets'), value: 'targets' },
{ label:
$
t('admin.storage.deliveryPaths'), value: 'delivery' }
{ label: t('admin.storage.targets'), value: 'targets' },
{ label: t('admin.storage.deliveryPaths'), value: 'delivery' }
]`
)
q-separator.q-mr-md(vertical)
...
...
@@ -33,11 +33,11 @@ q-page.admin-storage
)
q-btn(
unelevated
icon='
mdi
-check'
:label='
$
t(`common.actions.apply`)'
icon='
fa-solid fa
-check'
:label='t(`common.actions.apply`)'
color='secondary'
@click='save'
:loading='loading > 0'
:loading='
state.
loading > 0'
)
q-separator(inset)
...
...
@@ -45,7 +45,7 @@ q-page.admin-storage
//- TARGETS
//- ==========================================
.row.q-pa-md.q-col-gutter-md(v-if='displayMode === `targets`')
.row.q-pa-md.q-col-gutter-md(v-if='
state.
displayMode === `targets`')
.col-auto
q-card.rounded-borders.bg-dark
q-list(
...
...
@@ -54,11 +54,11 @@ q-page.admin-storage
dark
)
q-item(
v-for='tgt of targets'
v-for='tgt of
state.
targets'
:key='tgt.key'
active-class='bg-primary text-white'
:active='selectedTarget === tgt.id'
:to='`/_admin/` + currentSiteId + `/storage/` + tgt.id'
:active='s
tate.s
electedTarget === tgt.id'
:to='`/_admin/` +
adminStore.
currentSiteId + `/storage/` + tgt.id'
clickable
)
q-item-section(side)
...
...
@@ -70,76 +70,76 @@ q-page.admin-storage
q-item-label(caption, :class='getTargetSubtitleColor(tgt)')
{{
getTargetSubtitle
(
tgt
)
}}
q-item-section(side)
q-spinner-rings(:color='tgt.isEnabled ? `positive` : `negative`', size='sm')
.col(v-if='target')
.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'
)
}}
.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='target.contentTypes.activeTypes'
:color='target.module === `db` ? `grey` : `primary`'
v-model='
state.
target.contentTypes.activeTypes'
:color='
state.
target.module === `db` ? `grey` : `primary`'
val='pages'
:aria-label='
$
t(`admin.storage.contentTypePages`)'
:disable='target.module === `db`'
: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-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='target.contentTypes.activeTypes'
v-model='
state.
target.contentTypes.activeTypes'
color='primary'
val='images'
:aria-label='
$
t(`admin.storage.contentTypeImages`)'
: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-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='target.contentTypes.activeTypes'
v-model='
state.
target.contentTypes.activeTypes'
color='primary'
val='documents'
:aria-label='
$
t(`admin.storage.contentTypeDocuments`)'
: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-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='target.contentTypes.activeTypes'
v-model='
state.
target.contentTypes.activeTypes'
color='primary'
val='others'
:aria-label='
$
t(`admin.storage.contentTypeOthers`)'
: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-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='target.contentTypes.activeTypes'
v-model='
state.
target.contentTypes.activeTypes'
color='primary'
val='large'
:aria-label='
$
t(`admin.storage.contentTypeLargeFiles`)'
: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='
target.module === `db`', caption)
{{
$
t
(
`admin.storage.contentTypeLargeFilesDBWarn`
)
}}
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='target.contentTypes.largeThreshold'
:label='t(`admin.storage.contentTypeLargeFilesThreshold`)'
v-model='
state.
target.contentTypes.largeThreshold'
style='min-width: 150px;'
dense
)
...
...
@@ -149,41 +149,41 @@ q-page.admin-storage
//- -----------------------
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='target.assetDelivery.isStreamingSupported ? `label` : null')
.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='target.assetDelivery.streaming'
:color='
target.module === `db` || !
target.assetDelivery.isStreamingSupported ? `grey` : `primary`'
:aria-label='
$
t(`admin.storage.contentTypePages`)'
:disable='
target.module === `db` || !
target.assetDelivery.isStreamingSupported'
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='!
target.assetDelivery.isStreamingSupported', caption)
{{
$
t
(
`admin.storage.assetStreamingNotSupported`
)
}}
q-item(:tag='target.assetDelivery.isDirectAccessSupported ? `label` : null')
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='target.assetDelivery.directAccess'
:color='!target.assetDelivery.isDirectAccessSupported ? `grey` : `primary`'
:aria-label='
$
t(`admin.storage.contentTypePages`)'
:disable='!target.assetDelivery.isDirectAccessSupported'
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='!
target.assetDelivery.isDirectAccessSupported', caption)
{{
$
t
(
`admin.storage.assetDirectAccessNotSupported`
)
}}
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='
target.setup && target.setup.handler &&
target.setup.state !== `configured`')
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='
target.setup.handler === `github` &&
target.setup.state === `notconfigured`')
.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
...
...
@@ -191,42 +191,42 @@ q-page.admin-storage
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='target.setup.values.accountType'
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' }
{ label: t('admin.storage.githubAccTypeOrg'), value: 'org' },
{ label: t('admin.storage.githubAccTypePersonal'), value: 'personal' }
]`
)
q-separator.q-my-sm(inset)
template(v-if='target.setup.values.accountType === `org`')
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-label
{{
t
(
'admin.storage.githubOrg'
)
}}
q-item-label(caption)
{{
t
(
'admin.storage.githubOrgHint'
)
}}
q-item-section
q-input(
outlined
v-model='target.setup.values.org'
v-model='
state.
target.setup.values.org'
dense
:aria-label='
$
t(`admin.storage.githubOrg`)'
: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-label
{{
t
(
'admin.storage.githubPublicUrl'
)
}}
q-item-label(caption)
{{
t
(
'admin.storage.githubPublicUrlHint'
)
}}
q-item-section
q-input(
outlined
v-model='target.setup.values.publicUrl'
v-model='
state.
target.setup.values.publicUrl'
dense
:aria-label='
$
t(`admin.storage.githubPublicUrl`)'
:aria-label='t(`admin.storage.githubPublicUrl`)'
)
q-card-section.q-pt-sm.text-right
form(
...
...
@@ -242,37 +242,37 @@ q-page.admin-storage
q-btn(
unelevated
icon='las la-angle-double-right'
:label='
$
t(`admin.storage.startSetup`)'
:label='t(`admin.storage.startSetup`)'
color='secondary'
@click='setupGitHub'
:loading='setupCfg.loading'
)
template(v-else-if='
target.setup.handler === `github` &&
target.setup.state === `pendinginstall`')
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'
)
}}
)
{{
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`)'
:label='t(`admin.storage.cancelSetup`)'
color='negative'
@click='setupDestroy'
)
q-btn(
unelevated
icon='las la-angle-double-right'
:label='
$
t(`admin.storage.finishSetup`)'
: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='
target.setup && target.setup.handler &&
target.setup.state === `configured`')
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'
)
}}
.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
...
...
@@ -285,7 +285,7 @@ q-page.admin-storage
icon='las la-arrow-circle-right'
color='negative'
@click='setupDestroy'
:label='
$
t(`admin.storage.uninstall`)'
:label='t(`admin.storage.uninstall`)'
)
//- -----------------------
...
...
@@ -293,14 +293,14 @@ q-page.admin-storage
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section
.text-subtitle1
{{
$
t
(
'admin.storage.config'
)
}}
.text-subtitle1
{{
t
(
'admin.storage.config'
)
}}
q-banner.q-mt-md(
v-if='!
target.config || Object.keys(
target.config).length < 1'
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'
)
}}
)
{{
t
(
'admin.storage.noConfigOption'
)
}}
template(
v-for='(cfg, cfgKey, idx) in target.config'
v-for='(cfg, cfgKey, idx) in
state.
target.config'
)
template(
v-if='configIfCheck(cfg.if)'
...
...
@@ -317,7 +317,7 @@ q-page.admin-storage
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='
$
t(`admin.general.allowComments`)'
:aria-label='t(`admin.general.allowComments`)'
:disable='cfg.readOnly'
)
q-item(v-else)
...
...
@@ -364,34 +364,34 @@ q-page.admin-storage
//- -----------------------
//- Sync
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md(v-if='
target.sync && Object.keys(
target.sync).length > 0')
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'
)
}}
.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'
)
}}
)
{{
t
(
'admin.storage.noSyncModes'
)
}}
//- -----------------------
//- Actions
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section
.text-subtitle1
{{
$
t
(
'admin.storage.actions'
)
}}
.text-subtitle1
{{
t
(
'admin.storage.actions'
)
}}
q-banner.q-mt-md(
v-if='!
target.actions ||
target.actions.length < 1'
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'
)
}}
)
{{
t
(
'admin.storage.noActions'
)
}}
q-banner.q-mt-md(
v-else-if='!target.isEnabled'
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'
)
}}
)
{{
t
(
'admin.storage.actionsInactiveWarn'
)
}}
template(
v-if='target.isEnabled'
v-for='(act, idx) in target.actions'
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
...
...
@@ -406,32 +406,32 @@ q-page.admin-storage
icon='las la-arrow-circle-right'
color='primary'
@click=''
:label='
$
t(`common.actions.proceed`)'
:label='t(`common.actions.proceed`)'
)
.col-auto(v-if='target')
.col-auto(v-if='
state.
target')
//- -----------------------
//- Infobox
//- -----------------------
q-card.rounded-borders.q-pb-md(style='width: 350px;')
q-card-section
.text-subtitle1
{{
target
.
title
}}
.text-subtitle1
{{
state
.
target
.
title
}}
q-img.q-mt-sm.rounded-borders(
:src='target.banner'
fit='cover'
no-spinner
)
.text-body2.q-mt-md
{{
target
.
description
}}
.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
{{
target
.
vendor
}}
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='
target.website', target='_blank', rel='noreferrer')
{{
target
.
website
}}
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
...
...
@@ -439,25 +439,25 @@ q-page.admin-storage
q-card.rounded-borders.q-pb-md.q-mt-md(style='width: 350px;')
q-card-section
.text-subtitle1 Status
template(v-if='
target.module !== `db` && !(target.setup && target.setup.handler &&
target.setup.state !== `configured`)')
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='
target.module === `db`', caption)
{{
$
t
(
`admin.storage.enabledForced`
)
}}
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='target.isEnabled'
:disable='
target.module === `db` || (target.setup && target.setup.handler &&
target.setup.state !== `configured`)'
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.general.allowSearch`)'
:aria-label='t(`admin.general.allowSearch`)'
)
q-separator.q-my-sm(inset)
q-item
q-item-section
q-item-label.text-grey
{{
$
t
(
`admin.storage.currentState`
)
}}
q-item-label.text-grey
{{
t
(
`admin.storage.currentState`
)
}}
q-item-label.text-positive No issues detected.
//- -----------------------
...
...
@@ -465,50 +465,50 @@ q-page.admin-storage
//- -----------------------
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='target.versioning.isSupported ? `label` : null')
.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='!
target.versioning.isSupported', caption)
{{
$
t
(
`admin.storage.versioningNotSupported`
)
}}
q-item-label.text-deep-orange(v-if='
target.versioning.isForceEnabled', caption)
{{
$
t
(
`admin.storage.versioningForceEnabled`
)
}}
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='target.versioning.enabled'
:disable='!
target.versioning.isSupported ||
target.versioning.isForceEnabled'
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`)'
:aria-label='t(`admin.storage.useVersioning`)'
)
//- ==========================================
//- DELIVERY PATHS
//- ==========================================
.row.q-pa-md.q-col-gutter-md(v-if='displayMode === `delivery`')
.row.q-pa-md.q-col-gutter-md(v-if='
state.
displayMode === `delivery`')
.col
q-card.rounded-borders
q-card-section.flex.items-center
.text-caption.q-mr-sm
{{
$
t
(
'admin.storage.deliveryPathsLegend'
)
}}
.text-caption.q-mr-sm
{{
t
(
'admin.storage.deliveryPathsLegend'
)
}}
q-chip(square, dense, color='blue-1', text-color='blue-8')
q-avatar(icon='las la-ellipsis-h', color='blue', text-color='white')
span.text-caption.q-px-sm
{{
$
t
(
'admin.storage.deliveryPathsUserRequest'
)
}}
span.text-caption.q-px-sm
{{
t
(
'admin.storage.deliveryPathsUserRequest'
)
}}
q-chip(square, dense, color='teal-1', text-color='teal-8')
q-avatar(icon='las la-ellipsis-h', color='positive', text-color='white')
span.text-caption.q-px-sm
{{
$
t
(
'admin.storage.deliveryPathsPushToOrigin'
)
}}
span.text-caption.q-px-sm
{{
t
(
'admin.storage.deliveryPathsPushToOrigin'
)
}}
q-chip(square, dense, color='red-1', text-color='red-8')
q-avatar(icon='las la-minus', color='negative', text-color='white')
span.text-caption.q-px-sm
{{
$
t
(
'admin.storage.missingOrigin'
)
}}
span.text-caption.q-px-sm
{{
t
(
'admin.storage.missingOrigin'
)
}}
q-separator
v-network-graph(
:zoom-level='2'
:configs='deliveryConfig'
:nodes='deliveryNodes'
:edges='deliveryEdges'
:paths='deliveryPaths'
:layouts='deliveryLayouts'
:configs='
state.
deliveryConfig'
:nodes='
state.
deliveryNodes'
:edges='
state.
deliveryEdges'
:paths='
state.
deliveryPaths'
:layouts='
state.
deliveryLayouts'
style='height: 600px;'
)
template(#override-node='{ nodeId, scale, config, ...slotProps }')
...
...
@@ -522,57 +522,57 @@ q-page.admin-storage
v-bind='slotProps'
)
image(
v-if='
deliveryNodes[nodeId].icon &&
deliveryNodes[nodeId].icon.endsWith(`.svg`)'
v-if='
state.deliveryNodes[nodeId].icon && state.
deliveryNodes[nodeId].icon.endsWith(`.svg`)'
:x='(-config.radius + 5) * scale'
:y='(-config.radius + 5) * scale'
:width='(config.radius - 5) * scale * 2'
:height='(config.radius - 5) * scale * 2'
:xlink:href='deliveryNodes[nodeId].icon'
:xlink:href='
state.
deliveryNodes[nodeId].icon'
)
text(
v-if='
deliveryNodes[nodeId].icon &&
deliveryNodes[nodeId].iconText'
:class='deliveryNodes[nodeId].icon'
v-if='
state.deliveryNodes[nodeId].icon && state.
deliveryNodes[nodeId].iconText'
:class='
state.
deliveryNodes[nodeId].icon'
:font-size='22 * scale'
fill='#ffffff'
text-anchor='middle'
dominant-baseline='central'
v-html='deliveryNodes[nodeId].iconText'
v-html='
state.
deliveryNodes[nodeId].iconText'
)
//- .overline.my-5
{{
$
t
(
'admin.storage.syncDirection'
)
}}
//- .body-2.ml-3
{{
$
t
(
'admin.storage.syncDirectionSubtitle'
)
}}
//- .overline.my-5
{{
t
(
'admin.storage.syncDirection'
)
}}
//- .body-2.ml-3
{{
t
(
'admin.storage.syncDirectionSubtitle'
)
}}
//- .pr-3.pt-3
//- v-radio-group.ml-3.py-0(v-model='target.mode')
//- v-radio(
//- :label='
$
t(`admin.storage.syncDirBi`)'
//- :label='t(`admin.storage.syncDirBi`)'
//- color='primary'
//- value='sync'
//- :disabled='target.supportedModes.indexOf(`sync`) < 0'
//- )
//- v-radio(
//- :label='
$
t(`admin.storage.syncDirPush`)'
//- :label='t(`admin.storage.syncDirPush`)'
//- color='primary'
//- value='push'
//- :disabled='target.supportedModes.indexOf(`push`) < 0'
//- )
//- v-radio(
//- :label='
$
t(`admin.storage.syncDirPull`)'
//- :label='t(`admin.storage.syncDirPull`)'
//- color='primary'
//- value='pull'
//- :disabled='target.supportedModes.indexOf(`pull`) < 0'
//- )
//- .body-2.ml-3
//- strong
{{
$t
(
'admin.storage.syncDirBi'
)
}}
#[em.red--text.text--lighten-2(v-if='target.supportedModes.indexOf(`sync`) < 0')
{{
$
t
(
'admin.storage.unsupported'
)
}}
]
//- .pb-3
{{
$
t
(
'admin.storage.syncDirBiHint'
)
}}
//- strong
{{
$t
(
'admin.storage.syncDirPush'
)
}}
#[em.red--text.text--lighten-2(v-if='target.supportedModes.indexOf(`push`) < 0')
{{
$
t
(
'admin.storage.unsupported'
)
}}
]
//- .pb-3
{{
$
t
(
'admin.storage.syncDirPushHint'
)
}}
//- strong
{{
$t
(
'admin.storage.syncDirPull'
)
}}
#[em.red--text.text--lighten-2(v-if='target.supportedModes.indexOf(`pull`) < 0')
{{
$
t
(
'admin.storage.unsupported'
)
}}
]
//- .pb-3
{{
$
t
(
'admin.storage.syncDirPullHint'
)
}}
//- strong
{{
t
(
'admin.storage.syncDirBi'
)
}}
#[em.red--text.text--lighten-2(v-if='target.supportedModes.indexOf(`sync`) < 0')
{{
t
(
'admin.storage.unsupported'
)
}}
]
//- .pb-3
{{
t
(
'admin.storage.syncDirBiHint'
)
}}
//- strong
{{
t
(
'admin.storage.syncDirPush'
)
}}
#[em.red--text.text--lighten-2(v-if='target.supportedModes.indexOf(`push`) < 0')
{{
t
(
'admin.storage.unsupported'
)
}}
]
//- .pb-3
{{
t
(
'admin.storage.syncDirPushHint'
)
}}
//- strong
{{
t
(
'admin.storage.syncDirPull'
)
}}
#[em.red--text.text--lighten-2(v-if='target.supportedModes.indexOf(`pull`) < 0')
{{
t
(
'admin.storage.unsupported'
)
}}
]
//- .pb-3
{{
t
(
'admin.storage.syncDirPullHint'
)
}}
//- template(v-if='target.hasSchedule')
//- v-divider.mt-3
//- .overline.my-5
{{
$
t
(
'admin.storage.syncSchedule'
)
}}
//- .body-2.ml-3
{{
$
t
(
'admin.storage.syncScheduleHint'
)
}}
//- .overline.my-5
{{
t
(
'admin.storage.syncSchedule'
)
}}
//- .body-2.ml-3
{{
t
(
'admin.storage.syncScheduleHint'
)
}}
//- .pa-3
//- duration-picker(v-model='target.syncInterval')
//- i18next.caption.mt-3(path='admin.storage.syncScheduleCurrent', tag='div')
...
...
@@ -582,604 +582,662 @@ q-page.admin-storage
</
template
>
<
script
>
<
script
setup
>
import
find
from
'lodash/find'
import
cloneDeep
from
'lodash/cloneDeep'
import
gql
from
'graphql-tag'
import
{
get
}
from
'vuex-pathify'
import
transform
from
'lodash/transform'
import
*
as
vNG
from
'v-network-graph'
import
*
as
VNetworkGraph
from
'v-network-graph'
import
{
useI18n
}
from
'vue-i18n'
import
{
useMeta
,
useQuasar
}
from
'quasar'
import
{
computed
,
nextTick
,
onMounted
,
reactive
,
ref
,
watch
}
from
'vue'
import
{
useRouter
,
useRoute
}
from
'vue-router'
import
{
useAdminStore
}
from
'src/stores/admin'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
useDataStore
}
from
'src/stores/data'
import
GithubSetupInstallDialog
from
'../components/GithubSetupInstallDialog.vue'
export
default
{
data
()
{
return
{
loading
:
0
,
displayMode
:
'targets'
,
runningAction
:
false
,
runningActionHandler
:
''
,
selectedTarget
:
''
,
desiredTarget
:
''
,
target
:
null
,
targets
:
[],
setupCfg
:
{
action
:
''
,
manifest
:
''
,
loading
:
false
},
deliveryNodes
:
{},
deliveryEdges
:
{},
deliveryLayouts
:
{
nodes
:
{}
},
deliveryPaths
:
[],
deliveryConfig
:
vNG
.
defineConfigs
({
view
:
{
layoutHandler
:
new
vNG
.
GridLayout
({
grid
:
15
}),
fit
:
true
,
mouseWheelZoomEnabled
:
false
,
grid
:
{
visible
:
true
,
interval
:
2.5
,
thickIncrements
:
0
}
},
node
:
{
draggable
:
false
,
selectable
:
true
,
normal
:
{
type
:
'rect'
,
color
:
node
=>
node
.
color
||
'#1976D2'
,
borderRadius
:
node
=>
node
.
borderRadius
||
5
},
label
:
{
margin
:
8
}
},
edge
:
{
normal
:
{
width
:
3
,
dasharray
:
edge
=>
edge
.
animate
===
false
?
20
:
3
,
animate
:
edge
=>
!
(
edge
.
animate
===
false
),
animationSpeed
:
edge
=>
edge
.
animationSpeed
||
50
,
color
:
edge
=>
edge
.
color
||
'#1976D2'
},
type
:
'straight'
,
gap
:
7
,
margin
:
4
,
marker
:
{
source
:
{
type
:
'none'
},
target
:
{
type
:
'none'
}
}
},
path
:
{
visible
:
true
,
end
:
'edgeOfNode'
,
margin
:
4
,
path
:
{
width
:
7
,
color
:
p
=>
p
.
color
,
linecap
:
'square'
}
}
})
}
// QUASAR
const
$q
=
useQuasar
()
// STORES
const
adminStore
=
useAdminStore
()
const
siteStore
=
useSiteStore
()
const
dataStore
=
useDataStore
()
// ROUTER
const
router
=
useRouter
()
const
route
=
useRoute
()
// I18N
const
{
t
}
=
useI18n
()
// META
useMeta
({
title
:
t
(
'admin.storage.title'
)
})
// DATA
const
state
=
reactive
({
loading
:
0
,
displayMode
:
'targets'
,
runningAction
:
false
,
runningActionHandler
:
''
,
selectedTarget
:
''
,
desiredTarget
:
''
,
target
:
null
,
targets
:
[],
setupCfg
:
{
action
:
''
,
manifest
:
''
,
loading
:
false
},
computed
:
{
currentSiteId
:
get
(
'admin/currentSiteId'
,
false
)
deliveryNodes
:
{},
deliveryEdges
:
{},
deliveryLayouts
:
{
nodes
:
{}
},
watch
:
{
async
currentSiteId
(
newValue
)
{
await
this
.
load
()
this
.
$nextTick
(()
=>
{
this
.
$router
.
replace
(
`/_admin/
${
newValue
}
/storage/
${
this
.
selectedTarget
}
`
)
})
},
displayMode
(
newValue
)
{
if
(
newValue
===
'delivery'
)
{
thi
s
.
generateGraph
()
deliveryPaths
:
[],
deliveryConfig
:
VNetworkGraph
.
defineConfigs
(
{
view
:
{
layoutHandler
:
new
VNetworkGraph
.
GridLayout
({
grid
:
15
}),
fit
:
true
,
mouseWheelZoomEnabled
:
false
,
grid
:
{
visible
:
true
,
interval
:
2.5
,
thi
ckIncrements
:
0
}
},
selectedTarget
(
newValue
)
{
this
.
target
=
find
(
this
.
targets
,
[
'id'
,
newValue
])
||
null
node
:
{
draggable
:
false
,
selectable
:
true
,
normal
:
{
type
:
'rect'
,
color
:
node
=>
node
.
color
||
'#1976D2'
,
borderRadius
:
node
=>
node
.
borderRadius
||
5
},
label
:
{
margin
:
8
}
},
targets
(
newValue
)
{
if
(
newValue
&&
newValue
.
length
>
0
)
{
if
(
this
.
desiredTarget
)
{
this
.
selectedTarget
=
this
.
desiredTarget
this
.
desiredTarget
=
''
}
else
{
this
.
selectedTarget
=
find
(
this
.
targets
,
[
'module'
,
'db'
])?.
id
||
null
if
(
!
this
.
$route
.
params
.
id
)
{
this
.
$router
.
replace
(
`/_admin/
${
this
.
currentSiteId
}
/storage/
${
this
.
selectedTarget
}
`
)
}
edge
:
{
normal
:
{
width
:
3
,
dasharray
:
edge
=>
edge
.
animate
===
false
?
20
:
3
,
animate
:
edge
=>
!
(
edge
.
animate
===
false
),
animationSpeed
:
edge
=>
edge
.
animationSpeed
||
50
,
color
:
edge
=>
edge
.
color
||
'#1976D2'
},
type
:
'straight'
,
gap
:
7
,
margin
:
4
,
marker
:
{
source
:
{
type
:
'none'
},
target
:
{
type
:
'none'
}
this
.
handleSetupCallback
()
}
},
$route
(
to
,
from
)
{
if
(
!
to
.
params
.
id
)
{
return
}
if
(
this
.
targets
.
length
<
1
)
{
this
.
desiredTarget
=
to
.
params
.
id
}
else
{
this
.
selectedTarget
=
to
.
params
.
id
path
:
{
visible
:
true
,
end
:
'edgeOfNode'
,
margin
:
4
,
path
:
{
width
:
7
,
color
:
p
=>
p
.
color
,
linecap
:
'square'
}
}
},
mounted
()
{
if
(
!
this
.
selectedTarget
&&
this
.
$route
.
params
.
id
)
{
if
(
this
.
targets
.
length
<
1
)
{
this
.
desiredTarget
=
this
.
$route
.
params
.
id
}
else
{
this
.
selectedTarget
=
this
.
$route
.
params
.
id
})
})
// REFS
const
githubSetupForm
=
ref
(
null
)
// WATCHERS
watch
(()
=>
adminStore
.
currentSiteId
,
async
(
newValue
)
=>
{
await
load
()
nextTick
(()
=>
{
router
.
replace
(
`/_admin/
${
newValue
}
/storage/
${
state
.
selectedTarget
}
`
)
})
})
watch
(()
=>
state
.
displayMode
,
(
newValue
)
=>
{
if
(
newValue
===
'delivery'
)
{
generateGraph
()
}
})
watch
(()
=>
state
.
selectedTarget
,
(
newValue
)
=>
{
state
.
target
=
find
(
state
.
targets
,
[
'id'
,
newValue
])
||
null
})
watch
(()
=>
state
.
targets
,
(
newValue
)
=>
{
if
(
newValue
&&
newValue
.
length
>
0
)
{
if
(
state
.
desiredTarget
)
{
state
.
selectedTarget
=
state
.
desiredTarget
state
.
desiredTarget
=
''
}
else
{
state
.
selectedTarget
=
find
(
state
.
targets
,
[
'module'
,
'db'
])?.
id
||
null
if
(
!
route
.
params
.
id
)
{
router
.
replace
(
`/_admin/
${
adminStore
.
currentSiteId
}
/storage/
${
state
.
selectedTarget
}
`
)
}
}
if
(
this
.
currentSiteId
)
{
this
.
load
()
}
this
.
handleSetupCallback
()
},
methods
:
{
async
load
()
{
this
.
loading
++
this
.
$q
.
loading
.
show
()
const
resp
=
await
this
.
$apollo
.
query
({
query
:
gql
`
query getStorageTargets (
$siteId: UUID!
handleSetupCallback
()
}
})
watch
(()
=>
route
,
(
to
,
from
)
=>
{
if
(
!
to
.
params
.
id
)
{
return
}
if
(
state
.
targets
.
length
<
1
)
{
state
.
desiredTarget
=
to
.
params
.
id
}
else
{
state
.
selectedTarget
=
to
.
params
.
id
}
})
// METHODS
async
function
load
()
{
state
.
loading
++
$q
.
loading
.
show
()
try
{
const
resp
=
await
APOLLO_CLIENT
.
query
({
query
:
gql
`
query getStorageTargets (
$siteId: UUID!
) {
storageTargets (
siteId: $siteId
) {
id
isEnabled
module
title
description
icon
banner
vendor
website
contentTypes
assetDelivery
versioning
sync
status
setup
config
actions
}
}`
,
variables
:
{
siteId
:
adminStore
.
currentSiteId
},
fetchPolicy
:
'network-only'
})
state
.
targets
=
cloneDeep
(
resp
?.
data
?.
storageTargets
)
}
catch
(
err
)
{
$q
.
notify
({
type
:
'negative'
,
message
:
'Failed to load storage configuration.'
,
caption
:
err
.
message
,
timeout
:
20000
})
}
$q
.
loading
.
hide
()
state
.
loading
--
}
function
configIfCheck
(
ifs
)
{
if
(
!
ifs
||
ifs
.
length
<
1
)
{
return
true
}
return
ifs
.
every
(
s
=>
state
.
target
.
config
[
s
.
key
]?.
value
===
s
.
eq
)
}
async
function
refresh
()
{
await
load
()
$q
.
notify
({
type
:
'positive'
,
message
:
'List of storage targets has been refreshed.'
})
}
async
function
save
({
silent
})
{
let
saveSuccess
=
false
if
(
!
silent
)
{
$q
.
loading
.
show
()
}
try
{
const
resp
=
await
APOLLO_CLIENT
.
mutate
({
mutation
:
gql
`
mutation (
$siteId: UUID!
$targets: [StorageTargetInput]!
) {
storageTargets
(
updateStorageTargets
(
siteId: $siteId
targets: $targets
) {
id
isEnabled
module
title
description
icon
banner
vendor
website
contentTypes
assetDelivery
versioning
sync
status
setup
config
actions
}
}`
,
variables
:
{
siteId
:
this
.
currentSiteId
},
fetchPolicy
:
'network-only'
})
this
.
targets
=
cloneDeep
(
resp
?.
data
?.
storageTargets
)
this
.
$q
.
loading
.
hide
()
this
.
loading
--
},
configIfCheck
(
ifs
)
{
if
(
!
ifs
||
ifs
.
length
<
1
)
{
return
true
}
return
ifs
.
every
(
s
=>
this
.
target
.
config
[
s
.
key
]?.
value
===
s
.
eq
)
},
async
refresh
()
{
await
this
.
$apollo
.
queries
.
targets
.
refetch
()
this
.
$store
.
commit
(
'showNotification'
,
{
message
:
'List of storage targets has been refreshed.'
,
style
:
'success'
,
icon
:
'cached'
})
},
async
save
({
silent
})
{
let
saveSuccess
=
false
if
(
!
silent
)
{
this
.
$q
.
loading
.
show
()
}
try
{
const
resp
=
await
this
.
$apollo
.
mutate
({
mutation
:
gql
`
mutation (
$siteId: UUID!
$targets: [StorageTargetInput]!
) {
updateStorageTargets(
siteId: $siteId
targets: $targets
) {
status {
succeeded
message
}
}
status {
succeeded
message
}
`
,
variables
:
{
siteId
:
this
.
currentSiteId
,
targets
:
this
.
targets
.
map
(
tgt
=>
({
id
:
tgt
.
id
,
module
:
tgt
.
module
,
isEnabled
:
tgt
.
isEnabled
,
contentTypes
:
tgt
.
contentTypes
.
activeTypes
,
largeThreshold
:
tgt
.
contentTypes
.
largeThreshold
,
assetDeliveryFileStreaming
:
tgt
.
assetDelivery
.
streaming
,
assetDeliveryDirectAccess
:
tgt
.
assetDelivery
.
directAccess
,
useVersioning
:
tgt
.
versioning
.
enabled
,
config
:
transform
(
tgt
.
config
,
(
r
,
v
,
k
)
=>
{
r
[
k
]
=
v
.
value
},
{})
}))
}
})
if
(
resp
?.
data
?.
updateStorageTargets
?.
status
?.
succeeded
)
{
saveSuccess
=
true
if
(
!
silent
)
{
this
.
$q
.
notify
({
type
:
'positive'
,
message
:
this
.
$t
(
'admin.storage.saveSuccess'
)
})
}
}
else
{
throw
new
Error
(
resp
?.
data
?.
updateStorageTargets
?.
status
?.
message
||
'Unexpected error'
)
}
}
catch
(
err
)
{
this
.
$q
.
notify
({
type
:
'negative'
,
message
:
this
.
$t
(
'admin.storage.saveFailed'
),
caption
:
err
.
message
})
}
if
(
!
silent
)
{
this
.
$q
.
loading
.
hide
()
}
return
saveSuccess
},
getTargetSubtitle
(
target
)
{
if
(
!
target
.
isEnabled
)
{
return
this
.
$t
(
'admin.storage.inactiveTarget'
)
}
const
hasPages
=
target
.
contentTypes
?.
activeTypes
?.
includes
(
'pages'
)
const
hasAssets
=
target
.
contentTypes
?.
activeTypes
?.
filter
(
c
=>
c
!==
'pages'
)?.
length
>
0
if
(
hasPages
&&
hasAssets
)
{
return
this
.
$t
(
'admin.storage.pagesAndAssets'
)
}
else
if
(
hasPages
)
{
return
this
.
$t
(
'admin.storage.pagesOnly'
)
}
else
if
(
hasAssets
)
{
return
this
.
$t
(
'admin.storage.assetsOnly'
)
}
else
{
return
this
.
$t
(
'admin.storage.notConfigured'
)
}
},
getTargetSubtitleColor
(
target
)
{
if
(
this
.
selectedTarget
===
target
.
id
)
{
return
'text-blue-2'
}
else
if
(
target
.
isEnabled
)
{
return
'text-positive'
}
else
{
return
'text-grey-7'
`
,
variables
:
{
siteId
:
adminStore
.
currentSiteId
,
targets
:
state
.
targets
.
map
(
tgt
=>
({
id
:
tgt
.
id
,
module
:
tgt
.
module
,
isEnabled
:
tgt
.
isEnabled
,
contentTypes
:
tgt
.
contentTypes
.
activeTypes
,
largeThreshold
:
tgt
.
contentTypes
.
largeThreshold
,
assetDeliveryFileStreaming
:
tgt
.
assetDelivery
.
streaming
,
assetDeliveryDirectAccess
:
tgt
.
assetDelivery
.
directAccess
,
useVersioning
:
tgt
.
versioning
.
enabled
,
config
:
transform
(
tgt
.
config
,
(
r
,
v
,
k
)
=>
{
r
[
k
]
=
v
.
value
},
{})
}))
}
},
getDefaultSchedule
(
val
)
{
if
(
!
val
)
{
return
'N/A'
}
return
''
// moment.duration(val).format('y [years], M [months], d [days], h [hours], m [minutes]')
},
async
executeAction
(
targetKey
,
handler
)
{
this
.
$store
.
commit
(
'loadingStart'
,
'admin-storage-executeaction'
)
this
.
runningAction
=
true
this
.
runningActionHandler
=
handler
try
{
await
this
.
$apollo
.
mutate
({
mutation
:
gql
`{}`
,
variables
:
{
targetKey
,
handler
}
})
this
.
$store
.
commit
(
'showNotification'
,
{
message
:
'Action completed.'
,
style
:
'success'
,
icon
:
'check'
})
if
(
resp
?.
data
?.
updateStorageTargets
?.
status
?.
succeeded
)
{
saveSuccess
=
true
if
(
!
silent
)
{
$q
.
notify
({
type
:
'positive'
,
message
:
t
(
'admin.storage.saveSuccess'
)
})
}
catch
(
err
)
{
console
.
warn
(
err
)
}
this
.
runningAction
=
false
this
.
runningActionHandler
=
''
this
.
$store
.
commit
(
'loadingStop'
,
'admin-storage-executeaction'
)
},
async
handleSetupCallback
()
{
if
(
this
.
targets
.
length
<
1
||
!
this
.
selectedTarget
)
{
return
}
}
else
{
throw
new
Error
(
resp
?.
data
?.
updateStorageTargets
?.
status
?.
message
||
'Unexpected error'
)
}
}
catch
(
err
)
{
$q
.
notify
({
type
:
'negative'
,
message
:
t
(
'admin.storage.saveFailed'
),
caption
:
err
.
message
})
}
if
(
!
silent
)
{
$q
.
loading
.
hide
()
}
return
saveSuccess
}
this
.
$nextTick
(()
=>
{
if
(
this
.
target
?.
setup
?.
handler
===
'github'
&&
this
.
$route
.
query
.
code
)
{
this
.
setupGitHubStep
(
'connect'
,
this
.
$route
.
query
.
code
)
}
})
},
async
setupDestroy
()
{
this
.
$q
.
dialog
({
title
:
this
.
$t
(
'admin.storage.destroyConfirm'
),
message
:
this
.
$t
(
'admin.storage.destroyConfirmInfo'
),
cancel
:
true
,
persistent
:
true
}).
onOk
(
async
()
=>
{
this
.
$q
.
loading
.
show
({
message
:
this
.
$t
(
'admin.storage.destroyingSetup'
)
})
function
getTargetSubtitle
(
target
)
{
if
(
!
target
.
isEnabled
)
{
return
t
(
'admin.storage.inactiveTarget'
)
}
const
hasPages
=
target
.
contentTypes
?.
activeTypes
?.
includes
(
'pages'
)
const
hasAssets
=
target
.
contentTypes
?.
activeTypes
?.
filter
(
c
=>
c
!==
'pages'
)?.
length
>
0
if
(
hasPages
&&
hasAssets
)
{
return
t
(
'admin.storage.pagesAndAssets'
)
}
else
if
(
hasPages
)
{
return
t
(
'admin.storage.pagesOnly'
)
}
else
if
(
hasAssets
)
{
return
t
(
'admin.storage.assetsOnly'
)
}
else
{
return
t
(
'admin.storage.notConfigured'
)
}
}
function
getTargetSubtitleColor
(
target
)
{
if
(
state
.
selectedTarget
===
target
.
id
)
{
return
'text-blue-2'
}
else
if
(
target
.
isEnabled
)
{
return
'text-positive'
}
else
{
return
'text-grey-7'
}
}
function
getDefaultSchedule
(
val
)
{
if
(
!
val
)
{
return
'N/A'
}
return
''
// moment.duration(val).format('y [years], M [months], d [days], h [hours], m [minutes]')
}
async
function
executeAction
(
targetKey
,
handler
)
{
// this.$store.commit('loadingStart', 'admin-storage-executeaction')
// this.runningAction = true
// this.runningActionHandler = handler
// try {
// await this.$apollo.mutate({
// mutation: gql`{}`,
// variables: {
// targetKey,
// handler
// }
// })
// this.$store.commit('showNotification', {
// message: 'Action completed.',
// style: 'success',
// icon: 'check'
// })
// } catch (err) {
// console.warn(err)
// }
// this.runningAction = false
// this.runningActionHandler = ''
// this.$store.commit('loadingStop', 'admin-storage-executeaction')
}
try
{
const
resp
=
await
this
.
$apollo
.
mutate
({
mutation
:
gql
`
mutation (
$targetId: UUID!
) {
destroyStorageTargetSetup(
targetId: $targetId
) {
status {
succeeded
message
}
}
async
function
handleSetupCallback
()
{
if
(
state
.
targets
.
length
<
1
||
!
state
.
selectedTarget
)
{
return
}
nextTick
(()
=>
{
if
(
state
.
target
?.
setup
?.
handler
===
'github'
&&
route
.
query
.
code
)
{
setupGitHubStep
(
'connect'
,
route
.
query
.
code
)
}
})
}
async
function
setupDestroy
()
{
$q
.
dialog
({
title
:
t
(
'admin.storage.destroyConfirm'
),
message
:
t
(
'admin.storage.destroyConfirmInfo'
),
cancel
:
true
,
persistent
:
true
}).
onOk
(
async
()
=>
{
$q
.
loading
.
show
({
message
:
t
(
'admin.storage.destroyingSetup'
)
})
try
{
const
resp
=
await
APOLLO_CLIENT
.
mutate
({
mutation
:
gql
`
mutation (
$targetId: UUID!
) {
destroyStorageTargetSetup(
targetId: $targetId
) {
status {
succeeded
message
}
`
,
variables
:
{
targetId
:
this
.
selectedTarget
}
})
if
(
resp
?.
data
?.
destroyStorageTargetSetup
?.
status
?.
succeeded
)
{
this
.
target
.
setup
.
state
=
'notconfigured'
setTimeout
(()
=>
{
this
.
$q
.
loading
.
hide
()
this
.
$q
.
notify
({
type
:
'positive'
,
message
:
this
.
$t
(
'admin.storage.githubSetupDestroySuccess'
)
})
},
2000
)
}
else
{
throw
new
Error
(
resp
?.
data
?.
destroyStorageTargetSetup
?.
status
?.
message
||
'Unexpected error'
)
}
}
catch
(
err
)
{
this
.
$q
.
notify
({
type
:
'negative'
,
message
:
this
.
$t
(
'admin.storage.githubSetupDestroyFailed'
),
caption
:
err
.
message
})
this
.
$q
.
loading
.
hide
()
`
,
variables
:
{
targetId
:
state
.
selectedTarget
}
})
},
async
setupGitHub
()
{
// -> Format values
this
.
target
.
setup
.
values
.
publicUrl
=
this
.
target
.
setup
.
values
.
publicUrl
.
toLowerCase
()
// -> Basic input check
if
(
this
.
target
.
setup
.
values
.
accountType
===
'org'
&&
this
.
target
.
setup
.
values
.
org
.
length
<
1
)
{
return
this
.
$q
.
notify
({
type
:
'negative'
,
message
:
'Invalid GitHub Organization'
,
caption
:
'Enter a valid github organization.'
})
}
if
(
this
.
target
.
setup
.
values
.
publicUrl
.
length
<
11
||
!
/^https
?
:
\/\/
.
{4,}
$/
.
test
(
this
.
target
.
setup
.
values
.
publicUrl
))
{
return
this
.
$q
.
notify
({
type
:
'negative'
,
message
:
'Invalid Wiki Public URL'
,
caption
:
'Enter a valid public URL for your wiki.'
})
}
if
(
this
.
target
.
setup
.
values
.
publicUrl
.
endsWith
(
'/'
))
{
this
.
target
.
setup
.
values
.
publicUrl
=
this
.
target
.
setup
.
values
.
publicUrl
.
slice
(
0
,
-
1
)
}
// -> Generate manifest
this
.
setupCfg
.
loading
=
true
if
(
this
.
target
.
setup
.
values
.
accountType
===
'org'
)
{
this
.
setupCfg
.
action
=
`https://github.com/organizations/
${
this
.
target
.
setup
.
values
.
org
}
/settings/apps/new`
if
(
resp
?.
data
?.
destroyStorageTargetSetup
?.
status
?.
succeeded
)
{
state
.
target
.
setup
.
state
=
'notconfigured'
setTimeout
(()
=>
{
$q
.
loading
.
hide
()
$q
.
notify
({
type
:
'positive'
,
message
:
t
(
'admin.storage.githubSetupDestroySuccess'
)
})
},
2000
)
}
else
{
th
is
.
setupCfg
.
action
=
'https://github.com/settings/apps/new'
th
row
new
Error
(
resp
?.
data
?.
destroyStorageTargetSetup
?.
status
?.
message
||
'Unexpected error'
)
}
this
.
setupCfg
.
manifest
=
JSON
.
stringify
({
name
:
`Wiki.js -
${
this
.
currentSiteId
.
slice
(
-
12
)}
`
,
description
:
'Connects your Wiki.js to GitHub repositories and synchronize their contents.'
,
url
:
this
.
target
.
setup
.
values
.
publicUrl
,
hook_attributes
:
{
url
:
`
${
this
.
target
.
setup
.
values
.
publicUrl
}
/_github/
${
this
.
currentSiteId
}
/events`
},
redirect_url
:
`
${
this
.
target
.
setup
.
values
.
publicUrl
}
/_admin/
${
this
.
currentSiteId
}
/storage/
${
this
.
target
.
id
}
`
,
callback_urls
:
[
`
${
this
.
target
.
setup
.
values
.
publicUrl
}
/_admin/
${
this
.
currentSiteId
}
/storage/
${
this
.
target
.
id
}
`
],
public
:
false
,
default_permissions
:
{
contents
:
'write'
,
metadata
:
'read'
,
members
:
'read'
},
default_events
:
[
'create'
,
'delete'
,
'push'
]
})
this
.
$q
.
loading
.
show
({
message
:
this
.
$t
(
'admin.storage.githubPreparingManifest'
)
}
catch
(
err
)
{
$q
.
notify
({
type
:
'negative'
,
message
:
t
(
'admin.storage.githubSetupDestroyFailed'
),
caption
:
err
.
message
})
if
(
await
this
.
save
({
silent
:
true
}))
{
this
.
$refs
.
githubSetupForm
.
submit
()
}
else
{
this
.
setupCfg
.
loading
=
false
this
.
$q
.
loading
.
hide
()
}
$q
.
loading
.
hide
()
}
})
}
async
function
setupGitHub
()
{
// -> Format values
state
.
target
.
setup
.
values
.
publicUrl
=
state
.
target
.
setup
.
values
.
publicUrl
.
toLowerCase
()
// -> Basic input check
if
(
state
.
target
.
setup
.
values
.
accountType
===
'org'
&&
state
.
target
.
setup
.
values
.
org
.
length
<
1
)
{
return
$q
.
notify
({
type
:
'negative'
,
message
:
'Invalid GitHub Organization'
,
caption
:
'Enter a valid github organization.'
})
}
if
(
state
.
target
.
setup
.
values
.
publicUrl
.
length
<
11
||
!
/^https
?
:
\/\/
.
{4,}
$/
.
test
(
state
.
target
.
setup
.
values
.
publicUrl
))
{
return
$q
.
notify
({
type
:
'negative'
,
message
:
'Invalid Wiki Public URL'
,
caption
:
'Enter a valid public URL for your wiki.'
})
}
if
(
state
.
target
.
setup
.
values
.
publicUrl
.
endsWith
(
'/'
))
{
state
.
target
.
setup
.
values
.
publicUrl
=
state
.
target
.
setup
.
values
.
publicUrl
.
slice
(
0
,
-
1
)
}
// -> Generate manifest
state
.
setupCfg
.
loading
=
true
if
(
state
.
target
.
setup
.
values
.
accountType
===
'org'
)
{
state
.
setupCfg
.
action
=
`https://github.com/organizations/
${
state
.
target
.
setup
.
values
.
org
}
/settings/apps/new`
}
else
{
state
.
setupCfg
.
action
=
'https://github.com/settings/apps/new'
}
state
.
setupCfg
.
manifest
=
JSON
.
stringify
({
name
:
`Wiki.js -
${
adminStore
.
currentSiteId
.
slice
(
-
12
)}
`
,
description
:
'Connects your Wiki.js to GitHub repositories and synchronize their contents.'
,
url
:
state
.
target
.
setup
.
values
.
publicUrl
,
hook_attributes
:
{
url
:
`
${
state
.
target
.
setup
.
values
.
publicUrl
}
/_github/
${
adminStore
.
currentSiteId
}
/events`
},
async
setupGitHubStep
(
step
,
code
)
{
this
.
$q
.
loading
.
show
({
message
:
this
.
$t
(
'admin.storage.githubVerifying'
)
})
redirect_url
:
`
${
state
.
target
.
setup
.
values
.
publicUrl
}
/_admin/
${
adminStore
.
currentSiteId
}
/storage/
${
state
.
target
.
id
}
`
,
callback_urls
:
[
`
${
state
.
target
.
setup
.
values
.
publicUrl
}
/_admin/
${
adminStore
.
currentSiteId
}
/storage/
${
state
.
target
.
id
}
`
],
public
:
false
,
default_permissions
:
{
contents
:
'write'
,
metadata
:
'read'
,
members
:
'read'
},
default_events
:
[
'create'
,
'delete'
,
'push'
]
})
$q
.
loading
.
show
({
message
:
t
(
'admin.storage.githubPreparingManifest'
)
})
if
(
await
save
({
silent
:
true
}))
{
githubSetupForm
.
value
.
submit
()
}
else
{
state
.
setupCfg
.
loading
=
false
$q
.
loading
.
hide
()
}
}
try
{
const
resp
=
await
this
.
$apollo
.
mutate
({
mutation
:
gql
`
mutation (
$targetId: UUID!
$state: JSON!
) {
setupStorageTarget(
targetId: $targetId
state: $state
) {
status {
succeeded
message
}
state
}
}
`
,
variables
:
{
targetId
:
this
.
selectedTarget
,
state
:
{
step
,
...
code
&&
{
code
}
}
}
})
if
(
resp
?.
data
?.
setupStorageTarget
?.
status
?.
succeeded
)
{
switch
(
resp
.
data
.
setupStorageTarget
.
state
?.
nextStep
)
{
case
'installApp'
:
{
this
.
$router
.
replace
({
query
:
null
})
this
.
$q
.
loading
.
hide
()
this
.
$q
.
dialog
({
component
:
GithubSetupInstallDialog
,
persistent
:
true
}).
onOk
(()
=>
{
this
.
$q
.
loading
.
show
({
message
:
this
.
$t
(
'admin.storage.githubRedirecting'
)
})
window
.
location
.
assign
(
resp
.
data
.
setupStorageTarget
.
state
?.
url
)
}).
onCancel
(()
=>
{
throw
new
Error
(
'Setup was aborted prematurely.'
)
})
break
}
case
'completed'
:
{
this
.
target
.
isEnabled
=
true
this
.
target
.
setup
.
state
=
'configured'
setTimeout
(()
=>
{
this
.
$q
.
loading
.
hide
()
this
.
$q
.
notify
({
type
:
'positive'
,
message
:
this
.
$t
(
'admin.storage.githubSetupSuccess'
)
})
},
2000
)
break
}
default
:
{
throw
new
Error
(
'Unknown Setup Step'
)
async
function
setupGitHubStep
(
step
,
code
)
{
$q
.
loading
.
show
({
message
:
t
(
'admin.storage.githubVerifying'
)
})
try
{
const
resp
=
await
APOLLO_CLIENT
.
mutate
({
mutation
:
gql
`
mutation (
$targetId: UUID!
$state: JSON!
) {
setupStorageTarget(
targetId: $targetId
state: $state
) {
status {
succeeded
message
}
state
}
}
else
{
throw
new
Error
(
resp
?.
data
?.
setupStorageTarget
?.
status
?.
message
||
'Unexpected error'
)
}
}
catch
(
err
)
{
this
.
$q
.
loading
.
hide
()
this
.
$q
.
notify
({
type
:
'negative'
,
message
:
this
.
$t
(
'admin.storage.githubSetupFailed'
),
caption
:
err
.
message
})
}
},
generateGraph
()
{
const
types
=
[
{
key
:
'images'
,
label
:
this
.
$t
(
'admin.storage.contentTypeImages'
),
icon
:
'las'
,
iconText
:
''
},
{
key
:
'documents'
,
label
:
this
.
$t
(
'admin.storage.contentTypeDocuments'
),
icon
:
'las'
,
iconText
:
''
},
{
key
:
'others'
,
label
:
this
.
$t
(
'admin.storage.contentTypeOthers'
),
icon
:
'las'
,
iconText
:
''
},
{
key
:
'large'
,
label
:
this
.
$t
(
'admin.storage.contentTypeLargeFiles'
),
icon
:
'las'
,
iconText
:
''
}
]
// -> Create PagesNodes
this
.
deliveryNodes
=
{
user
:
{
name
:
this
.
$t
(
'admin.storage.deliveryPathsUser'
),
borderRadius
:
16
,
icon
:
'/_assets/icons/fluent-account.svg'
},
pages
:
{
name
:
this
.
$t
(
'admin.storage.contentTypePages'
),
color
:
'#3f51b5'
,
icon
:
'las'
,
iconText
:
''
},
pages_wiki
:
{
name
:
'Wiki.js'
,
icon
:
'/_assets/logo-wikijs.svg'
,
color
:
'#161b22'
}
}
this
.
deliveryEdges
=
{
user_pages
:
{
source
:
'user'
,
target
:
'pages'
},
pages_in
:
{
source
:
'pages'
,
target
:
'pages_wiki'
},
pages_out
:
{
source
:
'pages_wiki'
,
target
:
'pages'
}
`
,
variables
:
{
targetId
:
this
.
selectedTarget
,
state
:
{
step
,
...
code
&&
{
code
}
}
}
this
.
deliveryLayouts
.
nodes
=
{
user
:
{
x
:
-
30
,
y
:
30
},
pages
:
{
x
:
0
,
y
:
0
},
pages_wiki
:
{
x
:
60
,
y
:
0
}
})
if
(
resp
?.
data
?.
setupStorageTarget
?.
status
?.
succeeded
)
{
switch
(
resp
.
data
.
setupStorageTarget
.
state
?.
nextStep
)
{
case
'installApp'
:
{
router
.
replace
({
query
:
null
})
$q
.
loading
.
hide
()
$q
.
dialog
({
component
:
GithubSetupInstallDialog
,
persistent
:
true
}).
onOk
(()
=>
{
$q
.
loading
.
show
({
message
:
t
(
'admin.storage.githubRedirecting'
)
})
window
.
location
.
assign
(
resp
.
data
.
setupStorageTarget
.
state
?.
url
)
}).
onCancel
(()
=>
{
throw
new
Error
(
'Setup was aborted prematurely.'
)
})
break
}
case
'completed'
:
{
this
.
target
.
isEnabled
=
true
this
.
target
.
setup
.
state
=
'configured'
setTimeout
(()
=>
{
$q
.
loading
.
hide
()
$q
.
notify
({
type
:
'positive'
,
message
:
t
(
'admin.storage.githubSetupSuccess'
)
})
},
2000
)
break
}
default
:
{
throw
new
Error
(
'Unknown Setup Step'
)
}
}
this
.
deliveryPaths
=
[]
}
else
{
throw
new
Error
(
resp
?.
data
?.
setupStorageTarget
?.
status
?.
message
||
'Unexpected error'
)
}
}
catch
(
err
)
{
$q
.
loading
.
hide
()
$q
.
notify
({
type
:
'negative'
,
message
:
t
(
'admin.storage.githubSetupFailed'
),
caption
:
err
.
message
})
}
}
// -> Create Asset Nodes
function
generateGraph
()
{
const
types
=
[
{
key
:
'images'
,
label
:
t
(
'admin.storage.contentTypeImages'
),
icon
:
'las'
,
iconText
:
''
},
{
key
:
'documents'
,
label
:
t
(
'admin.storage.contentTypeDocuments'
),
icon
:
'las'
,
iconText
:
''
},
{
key
:
'others'
,
label
:
t
(
'admin.storage.contentTypeOthers'
),
icon
:
'las'
,
iconText
:
''
},
{
key
:
'large'
,
label
:
t
(
'admin.storage.contentTypeLargeFiles'
),
icon
:
'las'
,
iconText
:
''
}
]
for
(
const
[
i
,
t
]
of
types
.
entries
())
{
this
.
deliveryNodes
[
t
.
key
]
=
{
name
:
t
.
label
,
color
:
'#3f51b5'
,
icon
:
t
.
icon
,
iconText
:
t
.
iconText
}
this
.
deliveryEdges
[
`user_
${
t
.
key
}
`
]
=
{
source
:
'user'
,
target
:
t
.
key
}
this
.
deliveryLayouts
.
nodes
[
t
.
key
]
=
{
x
:
0
,
y
:
(
i
+
1
)
*
15
}
// -> Create PagesNodes
// -> Find target with direct access
const
dt
=
find
(
this
.
targets
,
tgt
=>
{
return
tgt
.
module
!==
'db'
&&
tgt
.
contentTypes
.
activeTypes
.
includes
(
t
.
key
)
&&
tgt
.
isEnabled
&&
tgt
.
assetDelivery
.
isDirectAccessSupported
&&
tgt
.
assetDelivery
.
directAccess
})
state
.
deliveryNodes
=
{
user
:
{
name
:
t
(
'admin.storage.deliveryPathsUser'
),
borderRadius
:
16
,
icon
:
'/_assets/icons/fluent-account.svg'
},
pages
:
{
name
:
t
(
'admin.storage.contentTypePages'
),
color
:
'#3f51b5'
,
icon
:
'las'
,
iconText
:
''
},
pages_wiki
:
{
name
:
'Wiki.js'
,
icon
:
'/_assets/logo-wikijs.svg'
,
color
:
'#161b22'
}
}
state
.
deliveryEdges
=
{
user_pages
:
{
source
:
'user'
,
target
:
'pages'
},
pages_in
:
{
source
:
'pages'
,
target
:
'pages_wiki'
},
pages_out
:
{
source
:
'pages_wiki'
,
target
:
'pages'
}
}
state
.
deliveryLayouts
.
nodes
=
{
user
:
{
x
:
-
30
,
y
:
30
},
pages
:
{
x
:
0
,
y
:
0
},
pages_wiki
:
{
x
:
60
,
y
:
0
}
}
state
.
deliveryPaths
=
[]
if
(
dt
)
{
this
.
deliveryNodes
[
`
${
t
.
key
}
_
${
dt
.
module
}
`
]
=
{
name
:
dt
.
title
,
icon
:
dt
.
icon
}
this
.
deliveryNodes
[
`
${
t
.
key
}
_wiki`
]
=
{
name
:
'Wiki.js'
,
icon
:
'/_assets/logo-wikijs.svg'
,
color
:
'#161b22'
}
this
.
deliveryLayouts
.
nodes
[
`
${
t
.
key
}
_
${
dt
.
module
}
`
]
=
{
x
:
60
,
y
:
(
i
+
1
)
*
15
}
this
.
deliveryLayouts
.
nodes
[
`
${
t
.
key
}
_wiki`
]
=
{
x
:
120
,
y
:
(
i
+
1
)
*
15
}
this
.
deliveryEdges
[
`
${
t
.
key
}
_
${
dt
.
module
}
_in`
]
=
{
source
:
t
.
key
,
target
:
`
${
t
.
key
}
_
${
dt
.
module
}
`
}
this
.
deliveryEdges
[
`
${
t
.
key
}
_
${
dt
.
module
}
_out`
]
=
{
source
:
`
${
t
.
key
}
_
${
dt
.
module
}
`
,
target
:
t
.
key
}
this
.
deliveryEdges
[
`
${
t
.
key
}
_
${
dt
.
module
}
_wiki`
]
=
{
source
:
`
${
t
.
key
}
_wiki`
,
target
:
`
${
t
.
key
}
_
${
dt
.
module
}
`
,
color
:
'#02c39a'
,
animationSpeed
:
25
}
continue
}
// -> Create Asset Nodes
// -> Find target with streaming
for
(
const
[
i
,
tp
]
of
types
.
entries
())
{
state
.
deliveryNodes
[
tp
.
key
]
=
{
name
:
tp
.
label
,
color
:
'#3f51b5'
,
icon
:
tp
.
icon
,
iconText
:
tp
.
iconText
}
state
.
deliveryEdges
[
`user_
${
tp
.
key
}
`
]
=
{
source
:
'user'
,
target
:
t
.
key
}
state
.
deliveryLayouts
.
nodes
[
tp
.
key
]
=
{
x
:
0
,
y
:
(
i
+
1
)
*
15
}
const
st
=
find
(
this
.
targets
,
tgt
=>
{
return
tgt
.
module
!==
'db'
&&
tgt
.
contentTypes
.
activeTypes
.
includes
(
t
.
key
)
&&
tgt
.
isEnabled
&&
tgt
.
assetDelivery
.
isStreamingSupported
&&
tgt
.
assetDelivery
.
streaming
})
// -> Find target with direct access
const
dt
=
find
(
state
.
targets
,
tgt
=>
{
return
tgt
.
module
!==
'db'
&&
tgt
.
contentTypes
.
activeTypes
.
includes
(
tp
.
key
)
&&
tgt
.
isEnabled
&&
tgt
.
assetDelivery
.
isDirectAccessSupported
&&
tgt
.
assetDelivery
.
directAccess
})
if
(
st
)
{
this
.
deliveryNodes
[
`
${
t
.
key
}
_
${
st
.
module
}
`
]
=
{
name
:
st
.
title
,
icon
:
st
.
icon
}
this
.
deliveryNodes
[
`
${
t
.
key
}
_wiki`
]
=
{
name
:
'Wiki.js'
,
icon
:
'/_assets/logo-wikijs.svg'
,
color
:
'#161b22'
}
this
.
deliveryLayouts
.
nodes
[
`
${
t
.
key
}
_
${
st
.
module
}
`
]
=
{
x
:
120
,
y
:
(
i
+
1
)
*
15
}
this
.
deliveryLayouts
.
nodes
[
`
${
t
.
key
}
_wiki`
]
=
{
x
:
60
,
y
:
(
i
+
1
)
*
15
}
this
.
deliveryEdges
[
`
${
t
.
key
}
_wiki_in`
]
=
{
source
:
t
.
key
,
target
:
`
${
t
.
key
}
_wiki`
}
this
.
deliveryEdges
[
`
${
t
.
key
}
_wiki_out`
]
=
{
source
:
`
${
t
.
key
}
_wiki`
,
target
:
t
.
key
}
this
.
deliveryEdges
[
`
${
t
.
key
}
_
${
st
.
module
}
_out`
]
=
{
source
:
`
${
t
.
key
}
_
${
st
.
module
}
`
,
target
:
`
${
t
.
key
}
_wiki`
}
this
.
deliveryEdges
[
`
${
t
.
key
}
_
${
st
.
module
}
_in`
]
=
{
source
:
`
${
t
.
key
}
_wiki`
,
target
:
`
${
t
.
key
}
_
${
st
.
module
}
`
}
this
.
deliveryEdges
[
`
${
t
.
key
}
_
${
st
.
module
}
_wiki`
]
=
{
source
:
`
${
t
.
key
}
_wiki`
,
target
:
`
${
t
.
key
}
_
${
st
.
module
}
`
,
color
:
'#02c39a'
,
animationSpeed
:
25
}
continue
}
if
(
dt
)
{
state
.
deliveryNodes
[
`
${
tp
.
key
}
_
${
dt
.
module
}
`
]
=
{
name
:
dt
.
title
,
icon
:
dt
.
icon
}
state
.
deliveryNodes
[
`
${
tp
.
key
}
_wiki`
]
=
{
name
:
'Wiki.js'
,
icon
:
'/_assets/logo-wikijs.svg'
,
color
:
'#161b22'
}
state
.
deliveryLayouts
.
nodes
[
`
${
tp
.
key
}
_
${
dt
.
module
}
`
]
=
{
x
:
60
,
y
:
(
i
+
1
)
*
15
}
state
.
deliveryLayouts
.
nodes
[
`
${
tp
.
key
}
_wiki`
]
=
{
x
:
120
,
y
:
(
i
+
1
)
*
15
}
state
.
deliveryEdges
[
`
${
tp
.
key
}
_
${
dt
.
module
}
_in`
]
=
{
source
:
tp
.
key
,
target
:
`
${
tp
.
key
}
_
${
dt
.
module
}
`
}
state
.
deliveryEdges
[
`
${
tp
.
key
}
_
${
dt
.
module
}
_out`
]
=
{
source
:
`
${
tp
.
key
}
_
${
dt
.
module
}
`
,
target
:
tp
.
key
}
state
.
deliveryEdges
[
`
${
tp
.
key
}
_
${
dt
.
module
}
_wiki`
]
=
{
source
:
`
${
tp
.
key
}
_wiki`
,
target
:
`
${
tp
.
key
}
_
${
dt
.
module
}
`
,
color
:
'#02c39a'
,
animationSpeed
:
25
}
continue
}
// -> Check DB fallback
const
dbt
=
find
(
this
.
targets
,
[
'module'
,
'db'
])
if
(
dbt
.
contentTypes
.
activeTypes
.
includes
(
t
.
key
))
{
this
.
deliveryNodes
[
`
${
t
.
key
}
_wiki`
]
=
{
name
:
'Wiki.js'
,
icon
:
'/_assets/logo-wikijs.svg'
,
color
:
'#161b22'
}
this
.
deliveryLayouts
.
nodes
[
`
${
t
.
key
}
_wiki`
]
=
{
x
:
60
,
y
:
(
i
+
1
)
*
15
}
this
.
deliveryEdges
[
`
${
t
.
key
}
_db_in`
]
=
{
source
:
t
.
key
,
target
:
`
${
t
.
key
}
_wiki`
}
this
.
deliveryEdges
[
`
${
t
.
key
}
_db_out`
]
=
{
source
:
`
${
t
.
key
}
_wiki`
,
target
:
t
.
key
}
}
else
{
this
.
deliveryNodes
[
`
${
t
.
key
}
_wiki`
]
=
{
name
:
this
.
$t
(
'admin.storage.missingOrigin'
),
color
:
'#f03a47'
,
icon
:
'las'
,
iconText
:
''
}
this
.
deliveryLayouts
.
nodes
[
`
${
t
.
key
}
_wiki`
]
=
{
x
:
60
,
y
:
(
i
+
1
)
*
15
}
this
.
deliveryEdges
[
`
${
t
.
key
}
_db_in`
]
=
{
source
:
t
.
key
,
target
:
`
${
t
.
key
}
_wiki`
,
color
:
'#f03a47'
,
animate
:
false
}
this
.
deliveryPaths
.
push
({
edges
:
[
`
${
t
.
key
}
_db_in`
],
color
:
'#f03a4755'
})
}
}
// -> Find target with streaming
const
st
=
find
(
state
.
targets
,
tgt
=>
{
return
tgt
.
module
!==
'db'
&&
tgt
.
contentTypes
.
activeTypes
.
includes
(
tp
.
key
)
&&
tgt
.
isEnabled
&&
tgt
.
assetDelivery
.
isStreamingSupported
&&
tgt
.
assetDelivery
.
streaming
})
if
(
st
)
{
state
.
deliveryNodes
[
`
${
tp
.
key
}
_
${
st
.
module
}
`
]
=
{
name
:
st
.
title
,
icon
:
st
.
icon
}
state
.
deliveryNodes
[
`
${
tp
.
key
}
_wiki`
]
=
{
name
:
'Wiki.js'
,
icon
:
'/_assets/logo-wikijs.svg'
,
color
:
'#161b22'
}
state
.
deliveryLayouts
.
nodes
[
`
${
tp
.
key
}
_
${
st
.
module
}
`
]
=
{
x
:
120
,
y
:
(
i
+
1
)
*
15
}
state
.
deliveryLayouts
.
nodes
[
`
${
tp
.
key
}
_wiki`
]
=
{
x
:
60
,
y
:
(
i
+
1
)
*
15
}
state
.
deliveryEdges
[
`
${
tp
.
key
}
_wiki_in`
]
=
{
source
:
tp
.
key
,
target
:
`
${
tp
.
key
}
_wiki`
}
state
.
deliveryEdges
[
`
${
tp
.
key
}
_wiki_out`
]
=
{
source
:
`
${
tp
.
key
}
_wiki`
,
target
:
tp
.
key
}
state
.
deliveryEdges
[
`
${
tp
.
key
}
_
${
st
.
module
}
_out`
]
=
{
source
:
`
${
tp
.
key
}
_
${
st
.
module
}
`
,
target
:
`
${
tp
.
key
}
_wiki`
}
state
.
deliveryEdges
[
`
${
tp
.
key
}
_
${
st
.
module
}
_in`
]
=
{
source
:
`
${
tp
.
key
}
_wiki`
,
target
:
`
${
tp
.
key
}
_
${
st
.
module
}
`
}
state
.
deliveryEdges
[
`
${
tp
.
key
}
_
${
st
.
module
}
_wiki`
]
=
{
source
:
`
${
tp
.
key
}
_wiki`
,
target
:
`
${
tp
.
key
}
_
${
st
.
module
}
`
,
color
:
'#02c39a'
,
animationSpeed
:
25
}
continue
}
// -> Check DB fallback
const
dbt
=
find
(
state
.
targets
,
[
'module'
,
'db'
])
if
(
dbt
.
contentTypes
.
activeTypes
.
includes
(
tp
.
key
))
{
state
.
deliveryNodes
[
`
${
tp
.
key
}
_wiki`
]
=
{
name
:
'Wiki.js'
,
icon
:
'/_assets/logo-wikijs.svg'
,
color
:
'#161b22'
}
state
.
deliveryLayouts
.
nodes
[
`
${
tp
.
key
}
_wiki`
]
=
{
x
:
60
,
y
:
(
i
+
1
)
*
15
}
state
.
deliveryEdges
[
`
${
tp
.
key
}
_db_in`
]
=
{
source
:
tp
.
key
,
target
:
`
${
tp
.
key
}
_wiki`
}
state
.
deliveryEdges
[
`
${
tp
.
key
}
_db_out`
]
=
{
source
:
`
${
tp
.
key
}
_wiki`
,
target
:
tp
.
key
}
}
else
{
state
.
deliveryNodes
[
`
${
tp
.
key
}
_wiki`
]
=
{
name
:
t
(
'admin.storage.missingOrigin'
),
color
:
'#f03a47'
,
icon
:
'las'
,
iconText
:
''
}
state
.
deliveryLayouts
.
nodes
[
`
${
tp
.
key
}
_wiki`
]
=
{
x
:
60
,
y
:
(
i
+
1
)
*
15
}
state
.
deliveryEdges
[
`
${
tp
.
key
}
_db_in`
]
=
{
source
:
tp
.
key
,
target
:
`
${
tp
.
key
}
_wiki`
,
color
:
'#f03a47'
,
animate
:
false
}
state
.
deliveryPaths
.
push
({
edges
:
[
`
${
tp
.
key
}
_db_in`
],
color
:
'#f03a4755'
})
}
}
}
// MOUNTED
onMounted
(()
=>
{
if
(
!
state
.
selectedTarget
&&
route
.
params
.
id
)
{
if
(
state
.
targets
.
length
<
1
)
{
state
.
desiredTarget
=
route
.
params
.
id
}
else
{
state
.
selectedTarget
=
route
.
params
.
id
}
}
if
(
adminStore
.
currentSiteId
)
{
load
()
}
handleSetupCallback
()
})
</
script
>
<
style
lang=
'scss'
scoped
>
...
...
ux/src/pages/AdminSystem.vue
View file @
36ba1eb1
...
...
@@ -16,7 +16,7 @@ q-page.admin-system
type='a'
)
q-btn.q-mr-sm.acrylic-btn(
icon='
fa-solid fa-rotate
'
icon='
las la-redo-alt
'
flat
color='secondary'
:loading='state.loading > 0'
...
...
@@ -234,26 +234,10 @@ import { useMeta, useQuasar } from 'quasar'
import
{
computed
,
onMounted
,
reactive
,
ref
,
watch
}
from
'vue'
import
ClipboardJS
from
'clipboard'
import
{
useAdminStore
}
from
'src/stores/admin'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
useDataStore
}
from
'src/stores/data'
import
{
useRoute
,
useRouter
}
from
'vue-router'
// QUASAR
const
$q
=
useQuasar
()
// STORES
const
adminStore
=
useAdminStore
()
const
siteStore
=
useSiteStore
()
const
dataStore
=
useDataStore
()
// ROUTER
const
router
=
useRouter
()
const
route
=
useRoute
()
// I18N
const
{
t
}
=
useI18n
()
...
...
ux/src/router/routes.js
View file @
36ba1eb1
...
...
@@ -30,20 +30,20 @@ const routes = [
{
path
:
''
,
redirect
:
'/_admin/dashboard'
},
{
path
:
'dashboard'
,
component
:
()
=>
import
(
'../pages/AdminDashboard.vue'
)
},
{
path
:
'sites'
,
component
:
()
=>
import
(
'../pages/AdminSites.vue'
)
},
//
//
-> Site
// -> Site
{
path
:
':siteid/general'
,
component
:
()
=>
import
(
'../pages/AdminGeneral.vue'
)
},
{
path
:
':siteid/editors'
,
component
:
()
=>
import
(
'../pages/AdminEditors.vue'
)
},
//
{ path: ':siteid/locale', component: () => import('../pages/AdminLocale.vue') },
//
{ path: ':siteid/login', component: () => import('../pages/AdminLogin.vue') },
{
path
:
':siteid/locale'
,
component
:
()
=>
import
(
'../pages/AdminLocale.vue'
)
},
{
path
:
':siteid/login'
,
component
:
()
=>
import
(
'../pages/AdminLogin.vue'
)
},
// { path: ':siteid/navigation', component: () => import('../pages/AdminNavigation.vue') },
//
{ path: ':siteid/storage/:id?', component: () => import('../pages/AdminStorage.vue') },
{
path
:
':siteid/storage/:id?'
,
component
:
()
=>
import
(
'../pages/AdminStorage.vue'
)
},
// { path: ':siteid/rendering', component: () => import('../pages/AdminRendering.vue') },
// { path: ':siteid/theme', component: () => import('../pages/AdminTheme.vue') },
//
//
-> Users
// -> Users
// { path: 'auth', component: () => import('../pages/AdminAuth.vue') },
// { path: 'groups/:id?/:section?', component: () => import('../pages/AdminGroups.vue') },
// { path: 'users/:id?/:section?', component: () => import('../pages/AdminUsers.vue') },
//
//
-> System
// -> System
// { path: 'api', component: () => import('../pages/AdminApi.vue') },
// { path: 'extensions', component: () => import('../pages/AdminExtensions.vue') },
// { path: 'mail', component: () => import('../pages/AdminMail.vue') },
...
...
ux/yarn.lock
View file @
36ba1eb1
This diff was suppressed by a .gitattributes entry.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment