feat: admin - groups single view
This commit is contained in:
116
client/components/admin/admin-api.vue
Normal file
116
client/components/admin/admin-api.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template lang='pug'>
|
||||
v-card(flat)
|
||||
v-card(flat, color='grey lighten-5').pa-3.pt-4
|
||||
.headline.blue--text.text--darken-2 API
|
||||
.subheading.grey--text Manage keys to access the API
|
||||
v-card
|
||||
v-card-title
|
||||
v-btn(color='green', dark)
|
||||
v-icon(left) power_settings_new
|
||||
| Enable API
|
||||
v-btn(color='primary', dark)
|
||||
v-icon(left) add
|
||||
| New API Key
|
||||
v-btn(icon)
|
||||
v-icon.grey--text refresh
|
||||
v-spacer
|
||||
v-text-field(append-icon='search', label='Search', single-line, hide-details, v-model='search')
|
||||
v-data-table(
|
||||
v-model='selected'
|
||||
:items='items',
|
||||
:headers='headers',
|
||||
:search='search',
|
||||
:pagination.sync='pagination',
|
||||
:rows-per-page-items='[15]'
|
||||
select-all,
|
||||
hide-actions,
|
||||
disable-initial-sort
|
||||
)
|
||||
template(slot='headers', slot-scope='props')
|
||||
tr
|
||||
th(width='50')
|
||||
th.text-xs-right(
|
||||
width='80'
|
||||
:class='[`column sortable`, pagination.descending ? `desc` : `asc`, pagination.sortBy === `id` ? `active` : ``]'
|
||||
@click='changeSort(`id`)'
|
||||
)
|
||||
v-icon(small) arrow_upward
|
||||
| ID
|
||||
th.text-xs-left(
|
||||
v-for='header in props.headers'
|
||||
:key='header.text'
|
||||
:width='header.width'
|
||||
:class='[`column sortable`, pagination.descending ? `desc` : `asc`, header.value === pagination.sortBy ? `active` : ``]'
|
||||
@click='changeSort(header.value)'
|
||||
)
|
||||
| {{ header.text }}
|
||||
v-icon(small) arrow_upward
|
||||
template(slot='items', slot-scope='props')
|
||||
tr(:active='props.selected')
|
||||
td
|
||||
v-checkbox(hide-details, :input-value='props.selected', color='blue darken-2', @click='props.selected = !props.selected')
|
||||
td.text-xs-right {{ props.item.id }}
|
||||
td {{ props.item.name }}
|
||||
td {{ props.item.key }}
|
||||
td {{ props.item.createdOn }}
|
||||
td {{ props.item.updatedOn }}
|
||||
td: v-btn(icon): v-icon.grey--text.text--darken-1 more_horiz
|
||||
template(slot='no-data')
|
||||
v-alert(icon='warning', :value='true') No users to display!
|
||||
.text-xs-center.py-2
|
||||
v-pagination(v-model='pagination.page', :length='pages')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
selected: [],
|
||||
pagination: {},
|
||||
items: [
|
||||
{ id: 1, key: 'xxxxxxxxxxxxx' },
|
||||
{ id: 2, key: 'xxxxxxxxxxxxy' },
|
||||
{ id: 3, key: 'xxxxxxxxxxxxz' }
|
||||
],
|
||||
headers: [
|
||||
{ text: 'Name', value: 'name' },
|
||||
{ text: 'Key', value: 'key' },
|
||||
{ text: 'Created On', value: 'createdOn' },
|
||||
{ text: 'Updated On', value: 'updatedOn' },
|
||||
{ text: '', value: 'actions', sortable: false, width: 50 }
|
||||
],
|
||||
search: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pages () {
|
||||
if (this.pagination.rowsPerPage == null || this.pagination.totalItems == null) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return Math.ceil(this.pagination.totalItems / this.pagination.rowsPerPage)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeSort (column) {
|
||||
if (this.pagination.sortBy === column) {
|
||||
this.pagination.descending = !this.pagination.descending
|
||||
} else {
|
||||
this.pagination.sortBy = column
|
||||
this.pagination.descending = false
|
||||
}
|
||||
},
|
||||
toggleAll () {
|
||||
if (this.selected.length) {
|
||||
this.selected = []
|
||||
} else {
|
||||
this.selected = this.items.slice()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
117
client/components/admin/admin-auth.vue
Normal file
117
client/components/admin/admin-auth.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<template lang='pug'>
|
||||
v-card(flat)
|
||||
v-card(color='grey lighten-5')
|
||||
.pa-3.pt-4
|
||||
.headline.primary--text Authentication
|
||||
.subheading.grey--text Configure the authentication settings of your wiki
|
||||
v-tabs(color='grey lighten-4', fixed-tabs, slider-color='primary', show-arrows)
|
||||
v-tab(key='settings'): v-icon settings
|
||||
v-tab(v-for='provider in activeProviders', :key='provider.key') {{ provider.title }}
|
||||
|
||||
v-tab-item(key='settings', :transition='false', :reverse-transition='false')
|
||||
v-card.pa-3
|
||||
.body-2.pb-2 Select which authentication providers to enable:
|
||||
v-form
|
||||
v-checkbox(
|
||||
v-for='(provider, n) in providers',
|
||||
v-model='auths',
|
||||
:key='provider.key',
|
||||
:label='provider.title',
|
||||
:value='provider.key',
|
||||
color='primary',
|
||||
:disabled='provider.key === `local`'
|
||||
hide-details
|
||||
)
|
||||
v-divider
|
||||
v-btn(color='primary')
|
||||
v-icon(left) chevron_right
|
||||
| Set Providers
|
||||
v-btn(color='black', dark)
|
||||
v-icon(left) layers_clear
|
||||
| Flush Sessions
|
||||
v-btn(icon, @click='refresh')
|
||||
v-icon.grey--text refresh
|
||||
|
||||
v-tab-item(v-for='(provider, n) in activeProviders', :key='provider.key', :transition='false', :reverse-transition='false')
|
||||
v-card.pa-3
|
||||
v-form
|
||||
v-subheader Provider Configuration
|
||||
.body-1(v-if='!provider.props || provider.props.length < 1') This provider has no configuration options you can modify.
|
||||
v-text-field(v-else, v-for='prop in provider.props', :key='prop', :label='prop', prepend-icon='mode_edit')
|
||||
v-divider
|
||||
v-subheader Registration
|
||||
v-switch.ml-3(
|
||||
v-model='auths',
|
||||
label='Allow self-registration',
|
||||
:value='true',
|
||||
color='primary',
|
||||
hint='Allow any user successfully authorized by the provider to access the wiki.',
|
||||
persistent-hint
|
||||
)
|
||||
v-text-field(label='Limit to specific email domains', prepend-icon='mail_outline')
|
||||
v-text-field(label='Assign to group', prepend-icon='people')
|
||||
v-divider
|
||||
v-btn(color='primary')
|
||||
v-icon(left) chevron_right
|
||||
| Save Configuration
|
||||
|
||||
v-snackbar(
|
||||
color='success'
|
||||
top
|
||||
v-model='refreshCompleted'
|
||||
)
|
||||
v-icon.mr-3(dark) cached
|
||||
| List of providers has been refreshed.
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
providers: [],
|
||||
auths: ['local'],
|
||||
refreshCompleted: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
activeProviders() {
|
||||
return _.filter(this.providers, 'isEnabled')
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
providers: {
|
||||
query: gql`
|
||||
query {
|
||||
authentication {
|
||||
providers {
|
||||
isEnabled
|
||||
key
|
||||
props
|
||||
title
|
||||
useForm
|
||||
config {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
update: (data) => data.authentication.providers
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async refresh() {
|
||||
await this.$apollo.queries.providers.refetch()
|
||||
this.refreshCompleted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
19
client/components/admin/admin-dashboard.vue
Normal file
19
client/components/admin/admin-dashboard.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template lang='pug'>
|
||||
v-container(fluid, fill-height)
|
||||
v-layout(row wrap)
|
||||
v-flex(xs12)
|
||||
.headline.primary--text Dashboard
|
||||
.subheading.grey--text Coming soon
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
138
client/components/admin/admin-dev.vue
Normal file
138
client/components/admin/admin-dev.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template lang='pug'>
|
||||
v-card(flat)
|
||||
v-card(color='grey lighten-5')
|
||||
.pa-3.pt-4
|
||||
.headline.primary--text Developer Tools
|
||||
.subheading.grey--text ¯\_(ツ)_/¯
|
||||
v-tabs(v-model='selectedTab', color='grey lighten-4', fixed-tabs, slider-color='primary', show-arrows, @input='tabChanged')
|
||||
v-tab(key='0') Graph API Playground
|
||||
v-tab(key='1') Graph API Map
|
||||
v-tabs-items(v-model='selectedTab')
|
||||
v-tab-item(key='0', :transition='false', :reverse-transition='false')
|
||||
#graphiql
|
||||
|
||||
v-tab-item(key='1', :transition='false', :reverse-transition='false')
|
||||
#voyager
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import GraphiQL from 'graphiql'
|
||||
import { Voyager } from 'graphql-voyager'
|
||||
import 'graphiql/graphiql.css'
|
||||
import 'graphql-voyager/dist/voyager.css'
|
||||
|
||||
const fetcher = (qry, respType) => {
|
||||
return fetch('/graphql', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(qry),
|
||||
credentials: 'include'
|
||||
}).then(response => {
|
||||
if (respType === 'json') {
|
||||
return response.json()
|
||||
} else {
|
||||
return response.text()
|
||||
}
|
||||
}).then(responseBody => {
|
||||
try {
|
||||
return JSON.parse(responseBody)
|
||||
} catch (error) {
|
||||
return responseBody
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let graphiQLInstance
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
selectedTab: '0'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.renderGraphiQL()
|
||||
},
|
||||
methods: {
|
||||
tabChanged (tabId) {
|
||||
switch (tabId) {
|
||||
case '1':
|
||||
this.renderVoyager()
|
||||
break
|
||||
}
|
||||
},
|
||||
renderGraphiQL() {
|
||||
ReactDOM.render(
|
||||
React.createElement(GraphiQL, {
|
||||
ref(el) { graphiQLInstance = el },
|
||||
async fetcher(qry) {
|
||||
let resp = await fetcher(qry, 'text')
|
||||
_.delay(() => {
|
||||
graphiQLInstance.resultComponent.viewer.refresh()
|
||||
}, 500)
|
||||
return resp
|
||||
},
|
||||
query: '',
|
||||
response: null,
|
||||
variables: null,
|
||||
operationName: null,
|
||||
websocketConnectionParams: null
|
||||
}),
|
||||
document.getElementById('graphiql')
|
||||
)
|
||||
graphiQLInstance.queryEditorComponent.editor.refresh()
|
||||
graphiQLInstance.variableEditorComponent.editor.refresh()
|
||||
graphiQLInstance.state.variableEditorOpen = true
|
||||
},
|
||||
renderVoyager() {
|
||||
ReactDOM.render(
|
||||
React.createElement(Voyager, {
|
||||
introspection: qry => fetcher({ query: qry }, 'json'),
|
||||
workerURI: '/js/voyager.worker.js'
|
||||
}),
|
||||
document.getElementById('voyager')
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
#graphiql {
|
||||
height: calc(100vh - 230px);
|
||||
|
||||
.topBar {
|
||||
background-color: mc('grey', '200');
|
||||
background-image: none;
|
||||
padding: 1.5rem 0;
|
||||
|
||||
> .title {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
background-color: initial;
|
||||
box-shadow: initial;
|
||||
}
|
||||
}
|
||||
|
||||
#voyager {
|
||||
height: calc(100vh - 250px);
|
||||
|
||||
.title-area {
|
||||
display: none;
|
||||
}
|
||||
.type-doc {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
45
client/components/admin/admin-editor.vue
Normal file
45
client/components/admin/admin-editor.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template lang='pug'>
|
||||
v-card(flat)
|
||||
v-card(color='grey lighten-5')
|
||||
.pa-3.pt-4
|
||||
.headline.primary--text Editor
|
||||
.subheading.grey--text Configure the content editor
|
||||
v-tabs(color='grey lighten-4', fixed-tabs, slider-color='primary', show-arrows)
|
||||
v-tab(key='settings'): v-icon settings
|
||||
v-tab(key='code') Markdown
|
||||
|
||||
v-tab-item(key='settings', :transition='false', :reverse-transition='false')
|
||||
v-card.pa-3
|
||||
v-form
|
||||
v-radio-group(v-model='selectedEditor')
|
||||
v-radio(v-for='(editor, n) in editors', :key='n', :label='editor.text', :value='editor.value', color='primary')
|
||||
v-divider
|
||||
v-btn(color='primary')
|
||||
v-icon(left) chevron_right
|
||||
| Set Editor
|
||||
v-btn(icon)
|
||||
v-icon.grey--text refresh
|
||||
v-tab-item(key='code', :transition='false', :reverse-transition='false')
|
||||
v-card.pa-3
|
||||
v-form
|
||||
v-subheader Editor Configuration
|
||||
.body-1 This editor has no configuration options you can modify.
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
editors: [
|
||||
{ text: 'Markdown (default)', value: 'code' }
|
||||
],
|
||||
selectedEditor: 'code'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
64
client/components/admin/admin-general.vue
Normal file
64
client/components/admin/admin-general.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template lang='pug'>
|
||||
v-container(fluid, fill-height, grid-list-lg)
|
||||
v-layout(row wrap)
|
||||
v-flex(xs12)
|
||||
.headline.primary--text General
|
||||
.subheading.grey--text Main settings of your wiki
|
||||
v-form.pt-3
|
||||
v-layout(row wrap)
|
||||
v-flex(lg6 xs12)
|
||||
v-form
|
||||
v-card
|
||||
v-toolbar(color='primary', dark, dense, flat)
|
||||
v-toolbar-title
|
||||
.subheading Site Info
|
||||
v-subheader General
|
||||
.px-3
|
||||
v-text-field(label='Site Title', required, :counter='50', v-model='siteTitle', prepend-icon='public')
|
||||
v-divider
|
||||
v-subheader SEO
|
||||
.px-3
|
||||
v-text-field(label='Site Description', :counter='255', prepend-icon='public')
|
||||
v-text-field(label='Site Keywords', :counter='255', prepend-icon='public')
|
||||
v-select(label='Meta Robots', chips, tags, :items='metaRobots', v-model='metaRobotsSelection', prepend-icon='public')
|
||||
v-divider
|
||||
.px-3.pb-3
|
||||
v-btn(color='primary') Save
|
||||
v-flex(lg6 xs12)
|
||||
v-card
|
||||
v-toolbar(color='primary', dark, dense, flat)
|
||||
v-toolbar-title
|
||||
.subheading Site Branding
|
||||
v-card-text ---
|
||||
v-card.mt-3
|
||||
v-toolbar(color='primary', dark, dense, flat)
|
||||
v-toolbar-title
|
||||
.subheading Maintenance Mode
|
||||
v-card-text
|
||||
.body-1 Maintenance mode restrict access to the site to administrators only, regarless of current permissions.
|
||||
v-btn.mt-3(color='orange darken-2', dark)
|
||||
icon-home-alert.mr-2(fillColor='#FFFFFF')
|
||||
| Turn On Maintenance Mode
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IconHomeAlert from 'mdi/home-alert'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
IconHomeAlert
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
siteTitle: 'Wiki.js',
|
||||
metaRobotsSelection: ['Index', 'Follow'],
|
||||
metaRobots: ['Index', 'Follow', 'No Index', 'No Follow']
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
187
client/components/admin/admin-groups-edit.vue
Normal file
187
client/components/admin/admin-groups-edit.vue
Normal file
@@ -0,0 +1,187 @@
|
||||
<template lang='pug'>
|
||||
v-card
|
||||
v-card(flat, color='grey lighten-5').pa-3.pt-4
|
||||
.headline.blue--text.text--darken-2 Edit Group
|
||||
.subheading.grey--text {{group.name}}
|
||||
v-btn(color='primary', fab, absolute, bottom, right, small, to='/groups'): v-icon arrow_upward
|
||||
v-tabs(v-model='tab', color='grey lighten-4', fixed-tabs, slider-color='primary', show-arrows)
|
||||
v-tab(key='properties') Properties
|
||||
v-tab(key='rights') Permissions
|
||||
v-tab(key='users') Users
|
||||
|
||||
v-tab-item(key='properties', :transition='false', :reverse-transition='false')
|
||||
v-card
|
||||
v-card-text
|
||||
v-text-field(v-model='group.name', label='Group Name', counter='255', prepend-icon='people')
|
||||
v-card-actions.pa-3
|
||||
v-btn(color='primary', @click='')
|
||||
v-icon(left) check
|
||||
| Save Changes
|
||||
.caption.ml-4.grey--text ID: {{group.id}}
|
||||
v-spacer
|
||||
v-dialog(v-model='deleteGroupDialog', max-width='500')
|
||||
v-btn(color='red', flat, @click='', slot='activator')
|
||||
v-icon(left) delete
|
||||
| Delete Group
|
||||
v-card
|
||||
.dialog-header.is-red Delete Group?
|
||||
v-card-text Are you sure you want to delete group #[strong {{ group.name }}]? All users will be unassigned from this group.
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(flat, @click='deleteGroupDialog = false') Cancel
|
||||
v-btn(color='red', dark, @click='deleteGroup') Delete
|
||||
|
||||
v-tab-item(key='rights', :transition='false', :reverse-transition='false')
|
||||
v-card
|
||||
v-card-title.pb-0
|
||||
v-btn(color='primary')
|
||||
v-icon(left) add
|
||||
| Add Rule
|
||||
v-spacer
|
||||
v-btn(flat)
|
||||
v-icon(left) vertical_align_bottom
|
||||
| Import Rules
|
||||
v-list(dense, two-line)
|
||||
v-list-tile.grey.lighten-5.px-2
|
||||
v-list-tile-avatar(color='red'): v-icon(color='white') remove_circle
|
||||
v-list-tile-content
|
||||
v-list-tile-title /javascript/*
|
||||
v-list-tile-sub-title.caption #[strong WRITE]
|
||||
v-list-tile-action
|
||||
v-btn(icon): v-icon(color='grey') delete
|
||||
v-divider(inset).my-0
|
||||
v-list-tile.grey.lighten-5.px-2
|
||||
v-list-tile-avatar(color='green'): v-icon(color='white') check
|
||||
v-list-tile-content
|
||||
v-list-tile-title /javascript/*
|
||||
v-list-tile-sub-title.caption #[strong WRITE]
|
||||
v-list-tile-action
|
||||
v-btn(icon): v-icon(color='grey') delete
|
||||
v-divider(inset).my-0
|
||||
v-list-tile.grey.lighten-5.px-2
|
||||
v-list-tile-avatar(color='green'): v-icon(color='white') check
|
||||
v-list-tile-content
|
||||
v-list-tile-title /javascript/*
|
||||
v-list-tile-sub-title.caption #[strong READ]
|
||||
v-list-tile-action
|
||||
v-btn(icon): v-icon(color='grey') delete
|
||||
|
||||
v-tab-item(key='users', :transition='false', :reverse-transition='false')
|
||||
v-card
|
||||
v-card-title.pb-0
|
||||
v-btn(color='primary')
|
||||
v-icon(left) assignment_ind
|
||||
| Assign User
|
||||
v-data-table(
|
||||
:items='group.users',
|
||||
:headers='headers',
|
||||
:search='search',
|
||||
:pagination.sync='pagination',
|
||||
:rows-per-page-items='[15]'
|
||||
hide-actions
|
||||
)
|
||||
template(slot='items', slot-scope='props')
|
||||
tr(:active='props.selected')
|
||||
td.text-xs-right {{ props.item.id }}
|
||||
td {{ props.item.name }}
|
||||
td {{ props.item.email }}
|
||||
td
|
||||
v-menu(bottom, right, min-width='200')
|
||||
v-btn(icon, slot='activator'): v-icon.grey--text.text--darken-1 more_horiz
|
||||
v-list
|
||||
v-list-tile(@click='deleteGroupConfirm(props.item)')
|
||||
v-list-tile-action: v-icon(color='orange') highlight_off
|
||||
v-list-tile-content
|
||||
v-list-tile-title Unassign
|
||||
template(slot='no-data')
|
||||
v-alert.ma-3(icon='warning', :value='true', outline) No users to display.
|
||||
.text-xs-center.py-2(v-if='users.length > 15')
|
||||
v-pagination(v-model='pagination.page', :length='pages')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import groupQuery from 'gql/admin-groups-query-single.gql'
|
||||
import deleteGroupMutation from 'gql/admin-groups-mutation-delete.gql'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
group: {
|
||||
id: 7,
|
||||
name: 'Editors',
|
||||
users: []
|
||||
},
|
||||
deleteGroupDialog: false,
|
||||
pagination: {},
|
||||
users: [],
|
||||
headers: [
|
||||
{ text: 'ID', value: 'id', width: 50, align: 'right' },
|
||||
{ text: 'Name', value: 'name' },
|
||||
{ text: 'Email', value: 'email' },
|
||||
{ text: '', value: 'actions', sortable: false, width: 50 }
|
||||
],
|
||||
search: '',
|
||||
tab: '1'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pages () {
|
||||
if (this.pagination.rowsPerPage == null || this.pagination.totalItems == null) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return Math.ceil(this.pagination.totalItems / this.pagination.rowsPerPage)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async deleteGroupConfirm(group) {
|
||||
this.deleteGroupDialog = true
|
||||
this.selectedGroup = group
|
||||
},
|
||||
async deleteGroup() {
|
||||
this.deleteGroupDialog = false
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: deleteGroupMutation,
|
||||
variables: {
|
||||
id: this.group.id
|
||||
},
|
||||
watchLoading (isLoading) {
|
||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-groups-delete')
|
||||
}
|
||||
})
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'success',
|
||||
message: `Group ${this.group.name} has been deleted.`,
|
||||
icon: 'delete'
|
||||
})
|
||||
this.$router.replace('/groups')
|
||||
} catch (err) {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: err.message,
|
||||
icon: 'warning'
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
group: {
|
||||
query: groupQuery,
|
||||
variables() {
|
||||
return {
|
||||
id: this.$route.params.id
|
||||
}
|
||||
},
|
||||
update: (data) => data.groups.single,
|
||||
watchLoading (isLoading) {
|
||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-groups-refresh')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
170
client/components/admin/admin-groups.vue
Normal file
170
client/components/admin/admin-groups.vue
Normal file
@@ -0,0 +1,170 @@
|
||||
<template lang='pug'>
|
||||
v-card(flat)
|
||||
v-card(flat, color='grey lighten-5').pa-3.pt-4
|
||||
.headline.blue--text.text--darken-2 Groups
|
||||
.subheading.grey--text Manage groups and their permissions
|
||||
v-card
|
||||
v-card-title
|
||||
v-dialog(v-model='newGroupDialog', max-width='500')
|
||||
v-btn(color='primary', dark, slot='activator')
|
||||
v-icon(left) add
|
||||
| New Group
|
||||
v-card
|
||||
.dialog-header.is-short New Group
|
||||
v-card-text
|
||||
v-text-field(v-model='newGroupName', label='Group Name', autofocus, counter='255', @keyup.enter='createGroup')
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(flat, @click='newGroupDialog = false') Cancel
|
||||
v-btn(color='primary', @click='createGroup') Create
|
||||
v-btn(icon, @click='refresh')
|
||||
v-icon.grey--text refresh
|
||||
v-spacer
|
||||
v-text-field(solo, append-icon='search', label='Search', single-line, hide-details, v-model='search')
|
||||
v-data-table(
|
||||
:items='groups',
|
||||
:headers='headers',
|
||||
:search='search',
|
||||
:pagination.sync='pagination',
|
||||
:rows-per-page-items='[15]'
|
||||
hide-actions
|
||||
)
|
||||
template(slot='items', slot-scope='props')
|
||||
tr.is-clickable(:active='props.selected', @click='$router.push("/groups/" + props.item.id)')
|
||||
td.text-xs-right {{ props.item.id }}
|
||||
td {{ props.item.name }}
|
||||
td {{ props.item.userCount }}
|
||||
td {{ props.item.createdAt | moment('calendar') }}
|
||||
td {{ props.item.updatedAt | moment('calendar') }}
|
||||
template(slot='no-data')
|
||||
v-alert.ma-3(icon='warning', :value='true', outline) No groups to display.
|
||||
.text-xs-center.py-2(v-if='groups.length > 15')
|
||||
v-pagination(v-model='pagination.page', :length='pages')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
|
||||
import groupsQuery from 'gql/admin-groups-query-list.gql'
|
||||
import createGroupMutation from 'gql/admin-groups-mutation-create.gql'
|
||||
import deleteGroupMutation from 'gql/admin-groups-mutation-delete.gql'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
newGroupDialog: false,
|
||||
newGroupName: '',
|
||||
selectedGroup: {},
|
||||
pagination: {},
|
||||
groups: [],
|
||||
headers: [
|
||||
{ text: 'ID', value: 'id', width: 50, align: 'right' },
|
||||
{ text: 'Name', value: 'name' },
|
||||
{ text: 'Users', value: 'userCount', width: 200 },
|
||||
{ text: 'Created', value: 'createdAt', width: 250 },
|
||||
{ text: 'Last Updated', value: 'updatedAt', width: 250 }
|
||||
],
|
||||
search: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pages () {
|
||||
if (this.pagination.rowsPerPage == null || this.pagination.totalItems == null) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return Math.ceil(this.pagination.totalItems / this.pagination.rowsPerPage)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async refresh() {
|
||||
await this.$apollo.queries.groups.refetch()
|
||||
this.$store.commit('showNotification', {
|
||||
message: 'Groups have been refreshed.',
|
||||
style: 'success',
|
||||
icon: 'cached'
|
||||
})
|
||||
},
|
||||
async createGroup() {
|
||||
this.newGroupDialog = false
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: createGroupMutation,
|
||||
variables: {
|
||||
name: this.newGroupName
|
||||
},
|
||||
update (store, resp) {
|
||||
const data = _.get(resp, 'data.groups.create', { responseResult: {} })
|
||||
if (data.responseResult.succeeded === true) {
|
||||
const apolloData = store.readQuery({ query: groupsQuery })
|
||||
data.group.userCount = 0
|
||||
apolloData.groups.list.push(data.group)
|
||||
store.writeQuery({ query: groupsQuery, data: apolloData })
|
||||
} else {
|
||||
throw new Error(data.responseResult.message)
|
||||
}
|
||||
},
|
||||
watchLoading (isLoading) {
|
||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-groups-create')
|
||||
}
|
||||
})
|
||||
this.newGroupName = ''
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'success',
|
||||
message: `Group has been created successfully.`,
|
||||
icon: 'check'
|
||||
})
|
||||
} catch (err) {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: err.message,
|
||||
icon: 'warning'
|
||||
})
|
||||
}
|
||||
},
|
||||
async deleteGroupConfirm(group) {
|
||||
this.deleteGroupDialog = true
|
||||
this.selectedGroup = group
|
||||
},
|
||||
async deleteGroup() {
|
||||
this.deleteGroupDialog = false
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: deleteGroupMutation,
|
||||
variables: {
|
||||
id: this.selectedGroup.id
|
||||
},
|
||||
watchLoading (isLoading) {
|
||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-groups-delete')
|
||||
}
|
||||
})
|
||||
await this.$apollo.queries.groups.refetch()
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'success',
|
||||
message: `Group ${this.selectedGroup.name} has been deleted.`,
|
||||
icon: 'delete'
|
||||
})
|
||||
} catch (err) {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: err.message,
|
||||
icon: 'warning'
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
groups: {
|
||||
query: groupsQuery,
|
||||
update: (data) => data.groups.list,
|
||||
watchLoading (isLoading) {
|
||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-groups-refresh')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
86
client/components/admin/admin-locale.vue
Normal file
86
client/components/admin/admin-locale.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template lang='pug'>
|
||||
v-container(fluid, fill-height, grid-list-lg)
|
||||
v-layout(row wrap)
|
||||
v-flex(xs12)
|
||||
.headline.primary--text Locale
|
||||
.subheading.grey--text Set localization options for your wiki
|
||||
v-form.pt-3
|
||||
v-layout(row wrap)
|
||||
v-flex(lg6 xs12)
|
||||
v-card
|
||||
v-toolbar(color='primary', dark, dense, flat)
|
||||
v-toolbar-title
|
||||
.subheading Locale Settings
|
||||
v-card-text
|
||||
v-select(:items='locales', prepend-icon='public', v-model='selectedLocale', label='Site Locale', persistent-hint, hint='All UI text elements will be displayed in selected language.')
|
||||
template(slot='item', slot-scope='data')
|
||||
v-list-tile-avatar
|
||||
v-avatar.blue.white--text(tile, size='40', v-html='data.item.value.toUpperCase()')
|
||||
v-list-tile-content
|
||||
v-list-tile-title(v-html='data.item.text')
|
||||
v-list-tile-sub-title(v-html='data.item.original')
|
||||
v-divider
|
||||
v-switch(v-model='rtlEnabled', label='RTL Display Mode', color='primary', persistent-hint, hint='For Right-to-Left languages, e.g. Arabic')
|
||||
v-divider
|
||||
.px-3.pb-3
|
||||
v-btn(color='primary') Save
|
||||
v-flex(lg6 xs12)
|
||||
v-card
|
||||
v-toolbar(color='teal', dark, dense, flat)
|
||||
v-toolbar-title
|
||||
.subheading Download Locale
|
||||
v-list
|
||||
v-list-tile(@click='')
|
||||
v-list-tile-avatar
|
||||
v-avatar.teal.white--text(tile, size='40') ZH
|
||||
v-list-tile-content
|
||||
v-list-tile-title Chinese
|
||||
v-list-tile-sub-title 中文
|
||||
v-list-tile-action
|
||||
v-btn(icon)
|
||||
v-icon.grey--text cloud_download
|
||||
v-list-tile(@click='')
|
||||
v-list-tile-avatar
|
||||
v-avatar.teal.white--text(tile, size='40') EN
|
||||
v-list-tile-content
|
||||
v-list-tile-title English
|
||||
v-list-tile-sub-title English
|
||||
v-list-tile-action
|
||||
v-icon.green--text check
|
||||
v-list-tile(@click='')
|
||||
v-list-tile-avatar
|
||||
v-avatar.teal.white--text(tile, size='40') FR
|
||||
v-list-tile-content
|
||||
v-list-tile-title French
|
||||
v-list-tile-sub-title Français
|
||||
v-list-tile-action
|
||||
v-icon.green--text check
|
||||
v-list-tile(@click='')
|
||||
v-list-tile-avatar
|
||||
v-avatar.teal.white--text(tile, size='40') RU
|
||||
v-list-tile-content
|
||||
v-list-tile-title Russian
|
||||
v-list-tile-sub-title Русский
|
||||
v-list-tile-action
|
||||
v-btn(icon)
|
||||
v-icon.blue--text update
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
locales: [
|
||||
{ text: 'English', original: 'English', value: 'en' },
|
||||
{ text: 'French', original: 'Français', value: 'fr' }
|
||||
],
|
||||
selectedLocale: 'en',
|
||||
rtlEnabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
91
client/components/admin/admin-logging.vue
Normal file
91
client/components/admin/admin-logging.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template lang='pug'>
|
||||
v-card(flat)
|
||||
v-card(color='grey lighten-5')
|
||||
.pa-3.pt-4
|
||||
.headline.primary--text Logging
|
||||
.subheading.grey--text Configure the system logger(s)
|
||||
v-tabs(color='grey lighten-4', fixed-tabs, slider-color='primary', show-arrows)
|
||||
v-tab(key='settings'): v-icon settings
|
||||
v-tab(v-for='svc in activeServices', :key='svc.key') {{ svc.title }}
|
||||
|
||||
v-tab-item(key='settings', :transition='false', :reverse-transition='false')
|
||||
v-card.pa-3
|
||||
.body-2.pb-2 Select which logging service to enable:
|
||||
v-form
|
||||
v-checkbox(
|
||||
v-for='(svc, n) in services',
|
||||
v-model='selectedServices',
|
||||
:key='svc.key',
|
||||
:label='svc.title',
|
||||
:value='svc.key',
|
||||
color='primary',
|
||||
:disabled='svc.key === `console`'
|
||||
hide-details
|
||||
)
|
||||
v-divider
|
||||
v-btn(color='primary')
|
||||
v-icon(left) chevron_right
|
||||
| Set Services
|
||||
v-btn(color='black', dark)
|
||||
v-icon(left) keyboard
|
||||
| View Console
|
||||
v-btn(color='black', dark)
|
||||
v-icon(left) layers_clear
|
||||
| Purge Logs
|
||||
v-btn(icon, @click='refresh')
|
||||
v-icon.grey--text refresh
|
||||
|
||||
v-tab-item(v-for='(svc, n) in activeServices', :key='svc.key', :transition='false', :reverse-transition='false')
|
||||
v-card.pa-3
|
||||
v-form
|
||||
v-subheader Service Configuration
|
||||
.body-1(v-if='!svc.props || svc.props.length < 1') This logging service has no configuration options you can modify.
|
||||
v-text-field(v-else, v-for='prop in svc.props', :key='prop', :label='prop', prepend-icon='mode_edit')
|
||||
v-divider
|
||||
v-btn(color='primary')
|
||||
v-icon(left) chevron_right
|
||||
| Save Configuration
|
||||
|
||||
v-snackbar(
|
||||
color='success'
|
||||
top
|
||||
v-model='refreshCompleted'
|
||||
)
|
||||
v-icon.mr-3(dark) cached
|
||||
| List of logging services has been refreshed.
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
services: [],
|
||||
selectedServices: ['console'],
|
||||
refreshCompleted: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
activeServices() {
|
||||
return _.filter(this.services, 'isEnabled')
|
||||
}
|
||||
},
|
||||
// apollo: {
|
||||
// services: {
|
||||
// query: CONSTANTS.GRAPH.AUTHENTICATION.QUERY_PROVIDERS,
|
||||
// update: (data) => data.authentication.providers
|
||||
// }
|
||||
// },
|
||||
methods: {
|
||||
async refresh() {
|
||||
await this.$apollo.queries.services.refetch()
|
||||
this.refreshCompleted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
19
client/components/admin/admin-rendering.vue
Normal file
19
client/components/admin/admin-rendering.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template lang='pug'>
|
||||
v-container(fluid, fill-height)
|
||||
v-layout(row wrap)
|
||||
v-flex(xs12)
|
||||
.headline.primary--text Content Rendering
|
||||
.subheading.grey--text Configure how content is rendered
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
57
client/components/admin/admin-search.vue
Normal file
57
client/components/admin/admin-search.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template lang='pug'>
|
||||
v-card(flat)
|
||||
v-card(color='grey lighten-5')
|
||||
.pa-3.pt-4
|
||||
.headline.primary--text Search Engine
|
||||
.subheading.grey--text Configure the search capabilities of your wiki
|
||||
v-tabs(color='grey lighten-4', fixed-tabs, slider-color='primary', show-arrows)
|
||||
v-tab(key='settings'): v-icon settings
|
||||
v-tab(key='db') Database
|
||||
v-tab(key='algolia') Algolia
|
||||
v-tab(key='elasticsearch') Elasticsearch
|
||||
v-tab(key='solr') Solr
|
||||
|
||||
v-tab-item(key='settings')
|
||||
v-card.pa-3
|
||||
v-form
|
||||
v-radio-group(v-model='selectedEngine')
|
||||
v-radio(v-for='(engine, n) in engines', :key='n', :label='engine.text', :value='engine.value', color='primary')
|
||||
v-divider
|
||||
v-btn(color='primary')
|
||||
v-icon(left) chevron_right
|
||||
| Set Engine
|
||||
v-btn(color='black', dark)
|
||||
v-icon(left) refresh
|
||||
| Rebuild Index
|
||||
v-tab-item(key='db')
|
||||
v-card.pa-3 TODO
|
||||
v-tab-item(key='algolia')
|
||||
v-card.pa-3 TODO
|
||||
v-tab-item(key='elasticsearch')
|
||||
v-card.pa-3 TODO
|
||||
v-tab-item(key='solr')
|
||||
v-card.pa-3 TODO
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
engines: [
|
||||
{ text: 'Disabled', value: 'disabled' },
|
||||
{ text: 'Database (built-in)', value: 'db' },
|
||||
{ text: 'Algolia', value: 'algolia' },
|
||||
{ text: 'Elasticsearch', value: 'elasticsearch' },
|
||||
{ text: 'Solr', value: 'solr' }
|
||||
],
|
||||
selectedEngine: 'db',
|
||||
darkMode: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
19
client/components/admin/admin-stats.vue
Normal file
19
client/components/admin/admin-stats.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template lang='pug'>
|
||||
v-container(fluid, fill-height)
|
||||
v-layout(row wrap)
|
||||
v-flex(xs12)
|
||||
.headline.primary--text Statistics
|
||||
.subheading.grey--text Useful information about your wiki
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
61
client/components/admin/admin-storage.vue
Normal file
61
client/components/admin/admin-storage.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template lang='pug'>
|
||||
v-card(flat)
|
||||
v-card(color='grey lighten-5')
|
||||
.pa-3.pt-4
|
||||
.headline.primary--text Storage
|
||||
.subheading.grey--text Set backup and sync targets for your content
|
||||
v-tabs(color='grey lighten-4', fixed-tabs, slider-color='primary', show-arrows)
|
||||
v-tab(key='settings'): v-icon settings
|
||||
v-tab(key='local') Local FS
|
||||
v-tab(key='git') Git
|
||||
v-tab(key='s3') Amazon S3
|
||||
v-tab(key='azure') Azure Blob Storage
|
||||
v-tab(key='digitalocean') DigitalOcean Spaces
|
||||
v-tab(key='dropbox') Dropbox
|
||||
v-tab(key='gdrive') Google Drive
|
||||
v-tab(key='onedrive') OneDrive
|
||||
v-tab(key='scp') SCP (SSH)
|
||||
|
||||
v-tab-item(key='settings')
|
||||
v-card.pa-3
|
||||
v-form
|
||||
v-checkbox(
|
||||
v-for='(target, n) in targets',
|
||||
v-model='auths',
|
||||
:key='n',
|
||||
:label='target.text',
|
||||
:value='target.value',
|
||||
color='primary',
|
||||
hide-details
|
||||
)
|
||||
v-divider
|
||||
v-btn(color='primary')
|
||||
v-icon(left) chevron_right
|
||||
| Set Backup Targets
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
targets: [
|
||||
{ text: 'Local Filesystem', value: 'local' },
|
||||
{ text: 'Git', value: 'auth0' },
|
||||
{ text: 'Amazon S3', value: 'algolia' },
|
||||
{ text: 'Azure Blob Storage', value: 'elasticsearch' },
|
||||
{ text: 'DigitalOcean Spaces', value: 'solr' },
|
||||
{ text: 'Dropbox', value: 'solr' },
|
||||
{ text: 'Google Drive', value: 'solr' },
|
||||
{ text: 'OneDrive', value: 'solr' },
|
||||
{ text: 'SCP (SSH)', value: 'solr' }
|
||||
],
|
||||
auths: ['local']
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
140
client/components/admin/admin-system.vue
Normal file
140
client/components/admin/admin-system.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template lang='pug'>
|
||||
v-container(fluid, fill-height, grid-list-lg)
|
||||
v-layout(row, wrap)
|
||||
v-flex(xs12)
|
||||
.headline.primary--text System Info
|
||||
.subheading.grey--text Information about your system
|
||||
v-layout.mt-3(row wrap)
|
||||
v-flex(lg6 xs12)
|
||||
v-card
|
||||
v-btn(fab, absolute, right, top, small, light, @click='refresh'): v-icon refresh
|
||||
v-list(two-line, dense)
|
||||
v-subheader Wiki.js
|
||||
v-list-tile(avatar)
|
||||
v-list-tile-avatar
|
||||
v-icon.blue.white--text system_update_alt
|
||||
v-list-tile-content
|
||||
v-list-tile-title Current Version
|
||||
v-list-tile-sub-title {{ info.currentVersion }}
|
||||
v-list-tile(avatar)
|
||||
v-list-tile-avatar
|
||||
v-icon.blue.white--text open_in_browser
|
||||
v-list-tile-content
|
||||
v-list-tile-title Latest Version
|
||||
v-list-tile-sub-title {{ info.latestVersion }}
|
||||
v-list-tile-action
|
||||
v-list-tile-action-text Published X days ago
|
||||
|
||||
v-divider
|
||||
|
||||
v-subheader Host Information
|
||||
v-list-tile(avatar)
|
||||
v-list-tile-avatar
|
||||
v-icon.blue-grey.white--text bubble_chart
|
||||
v-list-tile-content
|
||||
v-list-tile-title Operating System
|
||||
v-list-tile-sub-title {{ info.operatingSystem }}
|
||||
v-list-tile(avatar)
|
||||
v-list-tile-avatar
|
||||
v-icon.blue-grey.white--text computer
|
||||
v-list-tile-content
|
||||
v-list-tile-title Hostname
|
||||
v-list-tile-sub-title {{ info.hostname }}
|
||||
v-list-tile(avatar)
|
||||
v-list-tile-avatar
|
||||
v-icon.blue-grey.white--text nfc
|
||||
v-list-tile-content
|
||||
v-list-tile-title CPU Cores
|
||||
v-list-tile-sub-title {{ info.cpuCores }}
|
||||
v-list-tile(avatar)
|
||||
v-list-tile-avatar
|
||||
v-icon.blue-grey.white--text memory
|
||||
v-list-tile-content
|
||||
v-list-tile-title Total RAM
|
||||
v-list-tile-sub-title {{ info.ramTotal }}
|
||||
v-list-tile(avatar)
|
||||
v-list-tile-avatar
|
||||
v-icon.blue-grey.white--text last_page
|
||||
v-list-tile-content
|
||||
v-list-tile-title Working Directory
|
||||
v-list-tile-sub-title {{ info.workingDirectory }}
|
||||
|
||||
v-flex(lg6 xs12)
|
||||
v-card.pb-3
|
||||
v-list(dense)
|
||||
v-subheader Node.js
|
||||
v-list-tile(avatar)
|
||||
v-list-tile-avatar
|
||||
v-avatar.light-green(size='40')
|
||||
icon-node-js(fillColor='#FFFFFF')
|
||||
v-list-tile-content
|
||||
v-list-tile-title {{ info.nodeVersion }}
|
||||
|
||||
v-divider
|
||||
|
||||
v-subheader Redis
|
||||
v-list-tile(avatar)
|
||||
v-list-tile-avatar
|
||||
v-avatar.red(size='40')
|
||||
icon-cube(fillColor='#FFFFFF')
|
||||
v-list-tile-content
|
||||
v-list-tile-title {{ info.redisVersion }}
|
||||
v-list-tile-sub-title {{ info.redisHost }}
|
||||
v-list-tile-action
|
||||
v-list-tile-action-text RAM Usage: {{ info.redisUsedRAM }} / {{ info.redisTotalRAM }}
|
||||
|
||||
v-divider
|
||||
|
||||
v-subheader PostgreSQL
|
||||
v-list-tile(avatar)
|
||||
v-list-tile-avatar
|
||||
v-avatar.indigo.darken-1(size='40')
|
||||
icon-database(fillColor='#FFFFFF')
|
||||
v-list-tile-content
|
||||
v-list-tile-title {{ info.postgreVersion }}
|
||||
v-list-tile-sub-title {{ info.postgreHost }}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IconCube from 'mdi/cube'
|
||||
import IconDatabase from 'mdi/database'
|
||||
import IconNodeJs from 'mdi/nodejs'
|
||||
|
||||
import systemInfoQuery from 'gql/admin-system-query-info.gql'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
IconCube,
|
||||
IconDatabase,
|
||||
IconNodeJs
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
info: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async refresh() {
|
||||
await this.$apollo.queries.info.refetch()
|
||||
this.$store.commit('showNotification', {
|
||||
message: 'System Info has been refreshed.',
|
||||
style: 'success',
|
||||
icon: 'cached'
|
||||
})
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
info: {
|
||||
query: systemInfoQuery,
|
||||
update: (data) => data.system.info,
|
||||
watchLoading (isLoading) {
|
||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-system-refresh')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
51
client/components/admin/admin-theme.vue
Normal file
51
client/components/admin/admin-theme.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template lang='pug'>
|
||||
v-container(fluid, fill-height, grid-list-lg)
|
||||
v-layout(row wrap)
|
||||
v-flex(xs12)
|
||||
.headline.primary--text Theme
|
||||
.subheading.grey--text Modify the look & feel of your wiki
|
||||
v-form.pt-3
|
||||
v-layout(row wrap)
|
||||
v-flex(lg6 xs12)
|
||||
v-card
|
||||
v-toolbar(color='primary', dark, dense, flat)
|
||||
v-toolbar-title
|
||||
.subheading Theme
|
||||
v-card-text
|
||||
v-select(:items='themes', prepend-icon='palette', v-model='selectedTheme', label='Site Theme', persistent-hint, hint='Themes affect how content pages are displayed. Other site sections (such as the editor or admin area) are not affected.')
|
||||
template(slot='item', slot-scope='data')
|
||||
v-list-tile-avatar
|
||||
v-icon.blue--text(dark) filter_frames
|
||||
v-list-tile-content
|
||||
v-list-tile-title(v-html='data.item.text')
|
||||
v-list-tile-sub-title(v-html='data.item.author')
|
||||
v-divider
|
||||
v-switch(v-model='darkMode', label='Dark Mode', color='primary', persistent-hint, hint='Not recommended for accessibility')
|
||||
v-divider
|
||||
.px-3.pb-3
|
||||
v-btn(color='primary') Save
|
||||
v-flex(lg6 xs12)
|
||||
v-card
|
||||
v-toolbar(color='primary', dark, dense, flat)
|
||||
v-toolbar-title
|
||||
.subheading ---
|
||||
v-card-text ---
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
themes: [
|
||||
{ text: 'Default', author: 'requarks.io', value: 'default' }
|
||||
],
|
||||
selectedTheme: 'default',
|
||||
darkMode: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
135
client/components/admin/admin-users.vue
Normal file
135
client/components/admin/admin-users.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<template lang='pug'>
|
||||
v-card(flat)
|
||||
v-card(flat, color='grey lighten-5').pa-3.pt-4
|
||||
.headline.blue--text.text--darken-2 Users
|
||||
.subheading.grey--text Manage users
|
||||
v-card
|
||||
v-card-title
|
||||
v-btn(color='primary', dark)
|
||||
v-icon(left) add
|
||||
| New User
|
||||
v-btn(color='primary', dark)
|
||||
v-icon(left) lock_outline
|
||||
| Authorize User
|
||||
v-btn(icon)
|
||||
v-icon.grey--text refresh
|
||||
v-spacer
|
||||
v-text-field(append-icon='search', label='Search', single-line, hide-details, v-model='search')
|
||||
v-data-table(
|
||||
v-model='selected'
|
||||
:items='items',
|
||||
:headers='headers',
|
||||
:search='search',
|
||||
:pagination.sync='pagination',
|
||||
:rows-per-page-items='[15]'
|
||||
select-all,
|
||||
hide-actions,
|
||||
disable-initial-sort
|
||||
)
|
||||
template(slot='headers', slot-scope='props')
|
||||
tr
|
||||
th(width='50')
|
||||
th.text-xs-right(
|
||||
width='80'
|
||||
:class='[`column sortable`, pagination.descending ? `desc` : `asc`, pagination.sortBy === `id` ? `active` : ``]'
|
||||
@click='changeSort(`id`)'
|
||||
)
|
||||
v-icon(small) arrow_upward
|
||||
| ID
|
||||
th.text-xs-left(
|
||||
v-for='header in props.headers'
|
||||
:key='header.text'
|
||||
:width='header.width'
|
||||
:class='[`column sortable`, pagination.descending ? `desc` : `asc`, header.value === pagination.sortBy ? `active` : ``]'
|
||||
@click='changeSort(header.value)'
|
||||
)
|
||||
| {{ header.text }}
|
||||
v-icon(small) arrow_upward
|
||||
template(slot='items', slot-scope='props')
|
||||
tr(:active='props.selected')
|
||||
td
|
||||
v-checkbox(hide-details, :input-value='props.selected', color='blue darken-2', @click='props.selected = !props.selected')
|
||||
td.text-xs-right {{ props.item.id }}
|
||||
td {{ props.item.email }}
|
||||
td {{ props.item.name }}
|
||||
td {{ props.item.provider }}
|
||||
td {{ props.item.createdOn }}
|
||||
td {{ props.item.updatedOn }}
|
||||
td: v-btn(icon): v-icon.grey--text.text--darken-1 more_horiz
|
||||
template(slot='no-data')
|
||||
v-alert(icon='warning', :value='true') No users to display!
|
||||
.text-xs-center.py-2
|
||||
v-pagination(v-model='pagination.page', :length='pages')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
selected: [],
|
||||
pagination: {},
|
||||
items: [
|
||||
{ id: 1, email: 'user@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 2, email: 'dude@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 3, email: 'dude@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 4, email: 'dude@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 5, email: 'dude@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 6, email: 'dude@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 7, email: 'dude@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 8, email: 'dude@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 9, email: 'dude@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 10, email: 'dude@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 11, email: 'dude@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 12, email: 'dude@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 13, email: 'dude@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 14, email: 'dude@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 15, email: 'dude@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 16, email: 'dude@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 17, email: 'dude@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 18, email: 'dude@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 19, email: 'dude@test.com', name: 'John Doe', provider: 'local' },
|
||||
{ id: 20, email: 'dude@test.com', name: 'John Doe', provider: 'local' }
|
||||
],
|
||||
headers: [
|
||||
{ text: 'Email', value: 'email' },
|
||||
{ text: 'Name', value: 'name' },
|
||||
{ text: 'Provider', value: 'provider' },
|
||||
{ text: 'Created On', value: 'createdOn' },
|
||||
{ text: 'Updated On', value: 'updatedOn' },
|
||||
{ text: '', value: 'actions', sortable: false, width: 50 }
|
||||
],
|
||||
search: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pages () {
|
||||
if (this.pagination.rowsPerPage == null || this.pagination.totalItems == null) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return Math.ceil(this.pagination.totalItems / this.pagination.rowsPerPage)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeSort (column) {
|
||||
if (this.pagination.sortBy === column) {
|
||||
this.pagination.descending = !this.pagination.descending
|
||||
} else {
|
||||
this.pagination.sortBy = column
|
||||
this.pagination.descending = false
|
||||
}
|
||||
},
|
||||
toggleAll () {
|
||||
if (this.selected.length) {
|
||||
this.selected = []
|
||||
} else {
|
||||
this.selected = this.items.slice()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
19
client/components/admin/admin-utilities.vue
Normal file
19
client/components/admin/admin-utilities.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template lang='pug'>
|
||||
v-container(fluid, fill-height)
|
||||
v-layout(row wrap)
|
||||
v-flex(xs12)
|
||||
.headline.primary--text Utilities
|
||||
.subheading.grey--text Maintenance and troubleshooting tools
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
|
||||
</style>
|
Reference in New Issue
Block a user