AdminLayout.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. <template lang='pug'>
  2. q-layout.admin(view='hHh Lpr lff')
  3. q-header.bg-black.text-white
  4. .row.no-wrap
  5. q-toolbar(style='height: 64px;', dark)
  6. q-btn(dense, flat, to='/')
  7. q-avatar(size='34px', square)
  8. img(src='/_assets/logo-wikijs.svg')
  9. q-toolbar-title.text-h6 Wiki.js
  10. q-toolbar.gt-sm.justify-center(style='height: 64px;', dark)
  11. .text-overline.text-uppercase.text-grey {{t('admin.adminArea')}}
  12. q-badge.q-ml-sm(
  13. label='v3 Preview'
  14. color='pink'
  15. outline
  16. )
  17. q-toolbar(style='height: 64px;', dark)
  18. q-space
  19. transition(name='syncing')
  20. q-spinner-tail(
  21. v-show='siteStore.routerLoading'
  22. color='accent'
  23. size='24px'
  24. )
  25. q-btn.q-ml-md(flat, dense, icon='las la-times-circle', label='Exit' color='pink', to='/')
  26. account-menu
  27. q-drawer.admin-sidebar(v-model='leftDrawerOpen', show-if-above, bordered)
  28. q-scroll-area.admin-nav(
  29. :thumb-style='thumbStyle'
  30. :bar-style='barStyle'
  31. )
  32. q-list.text-white.q-pb-lg(padding, dense)
  33. q-item.q-mb-sm
  34. q-item-section
  35. q-btn.acrylic-btn(
  36. flat
  37. color='pink'
  38. icon='las la-heart'
  39. :label='t(`admin.contribute.title`)'
  40. no-caps
  41. href='https://js.wiki/donate'
  42. target='_blank'
  43. type='a'
  44. )
  45. q-item(to='/_admin/dashboard', v-ripple, active-class='bg-primary text-white')
  46. q-item-section(avatar)
  47. q-icon(name='img:/_assets/icons/fluent-apps-tab.svg')
  48. q-item-section {{ t('admin.dashboard.title') }}
  49. q-item(to='/_admin/sites', v-ripple, active-class='bg-primary text-white')
  50. q-item-section(avatar)
  51. q-icon(name='img:/_assets/icons/fluent-change-theme.svg')
  52. q-item-section {{ t('admin.sites.title') }}
  53. q-item-section(side)
  54. q-badge(color='dark-3', :label='adminStore.sites.length')
  55. q-item-label.q-mt-sm(header).text-caption.text-blue-grey-4 {{ t('admin.nav.site') }}
  56. q-item.q-mb-md
  57. q-item-section
  58. q-select(
  59. dark
  60. standout
  61. dense
  62. v-model='adminStore.currentSiteId'
  63. :options='adminStore.sites'
  64. option-value='id'
  65. option-label='title'
  66. emit-value
  67. map-options
  68. )
  69. q-item(:to='`/_admin/` + adminStore.currentSiteId + `/general`', v-ripple, active-class='bg-primary text-white')
  70. q-item-section(avatar)
  71. q-icon(name='img:/_assets/icons/fluent-web.svg')
  72. q-item-section {{ t('admin.general.title') }}
  73. template(v-if='flagsStore.experimental')
  74. q-item(:to='`/_admin/` + adminStore.currentSiteId + `/analytics`', v-ripple, active-class='bg-primary text-white', disabled)
  75. q-item-section(avatar)
  76. q-icon(name='img:/_assets/icons/fluent-bar-chart.svg')
  77. q-item-section {{ t('admin.analytics.title') }}
  78. q-item(:to='`/_admin/` + adminStore.currentSiteId + `/approvals`', v-ripple, active-class='bg-primary text-white', disabled)
  79. q-item-section(avatar)
  80. q-icon(name='img:/_assets/icons/fluent-inspection.svg')
  81. q-item-section {{ t('admin.approval.title') }}
  82. q-item(:to='`/_admin/` + adminStore.currentSiteId + `/comments`', v-ripple, active-class='bg-primary text-white', disabled)
  83. q-item-section(avatar)
  84. q-icon(name='img:/_assets/icons/fluent-comments.svg')
  85. q-item-section {{ t('admin.comments.title') }}
  86. q-item(:to='`/_admin/` + adminStore.currentSiteId + `/blocks`', v-ripple, active-class='bg-primary text-white', disabled)
  87. q-item-section(avatar)
  88. q-icon(name='img:/_assets/icons/fluent-rfid-tag.svg')
  89. q-item-section {{ t('admin.blocks.title') }}
  90. q-item(:to='`/_admin/` + adminStore.currentSiteId + `/editors`', v-ripple, active-class='bg-primary text-white')
  91. q-item-section(avatar)
  92. q-icon(name='img:/_assets/icons/fluent-cashbook.svg')
  93. q-item-section {{ t('admin.editors.title') }}
  94. q-item(:to='`/_admin/` + adminStore.currentSiteId + `/locale`', v-ripple, active-class='bg-primary text-white')
  95. q-item-section(avatar)
  96. q-icon(name='img:/_assets/icons/fluent-language.svg')
  97. q-item-section {{ t('admin.locale.title') }}
  98. q-item(:to='`/_admin/` + adminStore.currentSiteId + `/login`', v-ripple, active-class='bg-primary text-white')
  99. q-item-section(avatar)
  100. q-icon(name='img:/_assets/icons/fluent-bunch-of-keys.svg')
  101. q-item-section {{ t('admin.login.title') }}
  102. q-item(:to='`/_admin/` + adminStore.currentSiteId + `/navigation`', v-ripple, active-class='bg-primary text-white')
  103. q-item-section(avatar)
  104. q-icon(name='img:/_assets/icons/fluent-tree-structure.svg')
  105. q-item-section {{ t('admin.navigation.title') }}
  106. q-item(:to='`/_admin/` + adminStore.currentSiteId + `/storage`', v-ripple, active-class='bg-primary text-white')
  107. q-item-section(avatar)
  108. q-icon(name='img:/_assets/icons/fluent-ssd.svg')
  109. q-item-section {{ t('admin.storage.title') }}
  110. q-item(:to='`/_admin/` + adminStore.currentSiteId + `/theme`', v-ripple, active-class='bg-primary text-white')
  111. q-item-section(avatar)
  112. q-icon(name='img:/_assets/icons/fluent-paint-roller.svg')
  113. q-item-section {{ t('admin.theme.title') }}
  114. q-item-label.q-mt-sm(header).text-caption.text-blue-grey-4 {{ t('admin.nav.users') }}
  115. q-item(to='/_admin/auth', v-ripple, active-class='bg-primary text-white')
  116. q-item-section(avatar)
  117. q-icon(name='img:/_assets/icons/fluent-security-lock.svg')
  118. q-item-section {{ t('admin.auth.title') }}
  119. q-item(to='/_admin/groups', v-ripple, active-class='bg-primary text-white')
  120. q-item-section(avatar)
  121. q-icon(name='img:/_assets/icons/fluent-people.svg')
  122. q-item-section {{ t('admin.groups.title') }}
  123. q-item-section(side)
  124. q-badge(color='dark-3', :label='adminStore.info.groupsTotal')
  125. q-item(to='/_admin/users', v-ripple, active-class='bg-primary text-white')
  126. q-item-section(avatar)
  127. q-icon(name='img:/_assets/icons/fluent-account.svg')
  128. q-item-section {{ t('admin.users.title') }}
  129. q-item-section(side)
  130. q-badge(color='dark-3', :label='adminStore.info.usersTotal')
  131. q-item-label.q-mt-sm(header).text-caption.text-blue-grey-4 {{ t('admin.nav.system') }}
  132. q-item(to='/_admin/api', v-ripple, active-class='bg-primary text-white')
  133. q-item-section(avatar)
  134. q-icon(name='img:/_assets/icons/fluent-rest-api.svg')
  135. q-item-section {{ t('admin.api.title') }}
  136. q-item-section(side)
  137. status-light(:color='adminStore.info.isApiEnabled ? `positive` : `negative`')
  138. q-item(to='/_admin/audit', v-ripple, active-class='bg-primary text-white', disabled, v-if='flagsStore.experimental')
  139. q-item-section(avatar)
  140. q-icon(name='img:/_assets/icons/fluent-event-log.svg')
  141. q-item-section {{ t('admin.audit.title') }}
  142. q-item(to='/_admin/extensions', v-ripple, active-class='bg-primary text-white')
  143. q-item-section(avatar)
  144. q-icon(name='img:/_assets/icons/fluent-module.svg')
  145. q-item-section {{ t('admin.extensions.title') }}
  146. q-item(to='/_admin/icons', v-ripple, active-class='bg-primary text-white')
  147. q-item-section(avatar)
  148. q-icon(name='img:/_assets/icons/fluent-spring.svg')
  149. q-item-section {{ t('admin.icons.title') }}
  150. q-item(to='/_admin/instances', v-ripple, active-class='bg-primary text-white')
  151. q-item-section(avatar)
  152. q-icon(name='img:/_assets/icons/fluent-network.svg')
  153. q-item-section {{ t('admin.instances.title') }}
  154. q-item(to='/_admin/mail', v-ripple, active-class='bg-primary text-white')
  155. q-item-section(avatar)
  156. q-icon(name='img:/_assets/icons/fluent-message-settings.svg')
  157. q-item-section {{ t('admin.mail.title') }}
  158. q-item-section(side)
  159. status-light(:color='adminStore.info.isMailConfigured ? `positive` : `warning`')
  160. q-item(to='/_admin/rendering', v-ripple, active-class='bg-primary text-white', disabled, v-if='flagsStore.experimental')
  161. q-item-section(avatar)
  162. q-icon(name='img:/_assets/icons/fluent-rich-text-converter.svg')
  163. q-item-section {{ t('admin.rendering.title') }}
  164. q-item(to='/_admin/scheduler', v-ripple, active-class='bg-primary text-white')
  165. q-item-section(avatar)
  166. q-icon(name='img:/_assets/icons/fluent-bot.svg')
  167. q-item-section {{ t('admin.scheduler.title') }}
  168. q-item-section(side)
  169. status-light(:color='adminStore.info.isSchedulerHealthy ? `positive` : `warning`')
  170. q-item(to='/_admin/security', v-ripple, active-class='bg-primary text-white')
  171. q-item-section(avatar)
  172. q-icon(name='img:/_assets/icons/fluent-protect.svg')
  173. q-item-section {{ t('admin.security.title') }}
  174. q-item(to='/_admin/ssl', v-ripple, active-class='bg-primary text-white', disabled, v-if='flagsStore.experimental')
  175. q-item-section(avatar)
  176. q-icon(name='img:/_assets/icons/fluent-security-ssl.svg')
  177. q-item-section {{ t('admin.ssl.title') }}
  178. q-item(to='/_admin/system', v-ripple, active-class='bg-primary text-white')
  179. q-item-section(avatar)
  180. q-icon(name='img:/_assets/icons/fluent-processor.svg')
  181. q-item-section {{ t('admin.system.title') }}
  182. q-item-section(side)
  183. status-light(:color='adminStore.isVersionLatest ? `positive` : `warning`')
  184. q-item(to='/_admin/terminal', v-ripple, active-class='bg-primary text-white')
  185. q-item-section(avatar)
  186. q-icon(name='img:/_assets/icons/fluent-linux-terminal.svg')
  187. q-item-section {{ t('admin.terminal.title') }}
  188. q-item(to='/_admin/utilities', v-ripple, active-class='bg-primary text-white')
  189. q-item-section(avatar)
  190. q-icon(name='img:/_assets/icons/fluent-swiss-army-knife.svg')
  191. q-item-section {{ t('admin.utilities.title') }}
  192. q-item(to='/_admin/webhooks', v-ripple, active-class='bg-primary text-white')
  193. q-item-section(avatar)
  194. q-icon(name='img:/_assets/icons/fluent-lightning-bolt.svg')
  195. q-item-section {{ t('admin.webhooks.title') }}
  196. q-item(to='/_admin/flags', v-ripple, active-class='bg-primary text-white')
  197. q-item-section(avatar)
  198. q-icon(name='img:/_assets/icons/fluent-windsock.svg')
  199. q-item-section {{ t('admin.dev.flags.title') }}
  200. q-page-container.admin-container
  201. router-view(v-slot='{ Component }')
  202. component(:is='Component')
  203. q-dialog.admin-overlay(
  204. v-model='overlayIsShown'
  205. persistent
  206. full-width
  207. full-height
  208. no-shake
  209. transition-show='jump-up'
  210. transition-hide='jump-down'
  211. )
  212. component(:is='overlays[adminStore.overlay]')
  213. footer-nav.admin-footer(generic)
  214. </template>
  215. <script setup>
  216. import { useMeta, useQuasar, setCssVar } from 'quasar'
  217. import { defineAsyncComponent, onMounted, reactive, ref, watch } from 'vue'
  218. import { useRouter, useRoute } from 'vue-router'
  219. import { useI18n } from 'vue-i18n'
  220. import { useAdminStore } from 'src/stores/admin'
  221. import { useFlagsStore } from 'src/stores/flags'
  222. import { useSiteStore } from 'src/stores/site'
  223. // COMPONENTS
  224. import AccountMenu from '../components/AccountMenu.vue'
  225. import FooterNav from 'src/components/FooterNav.vue'
  226. const overlays = {
  227. EditorMarkdownConfig: defineAsyncComponent(() => import('../components/EditorMarkdownConfigOverlay.vue')),
  228. GroupEditOverlay: defineAsyncComponent(() => import('../components/GroupEditOverlay.vue')),
  229. UserEditOverlay: defineAsyncComponent(() => import('../components/UserEditOverlay.vue'))
  230. }
  231. // QUASAR
  232. const $q = useQuasar()
  233. // STORES
  234. const adminStore = useAdminStore()
  235. const flagsStore = useFlagsStore()
  236. const siteStore = useSiteStore()
  237. // ROUTER
  238. const router = useRouter()
  239. const route = useRoute()
  240. // I18N
  241. const { t } = useI18n()
  242. // META
  243. useMeta({
  244. titleTemplate: title => `${title} - ${t('admin.adminArea')} - Wiki.js`
  245. })
  246. // DATA
  247. const leftDrawerOpen = ref(true)
  248. const overlayIsShown = ref(false)
  249. const search = ref('')
  250. const user = reactive({
  251. name: 'John Doe',
  252. email: 'test@example.com',
  253. picture: null
  254. })
  255. const thumbStyle = {
  256. right: '1px',
  257. borderRadius: '5px',
  258. backgroundColor: '#666',
  259. width: '5px',
  260. opacity: 0.5
  261. }
  262. const barStyle = {
  263. width: '7px'
  264. }
  265. // WATCHERS
  266. watch(() => adminStore.sites, (newValue) => {
  267. if (adminStore.currentSiteId === null && newValue.length > 0) {
  268. adminStore.$patch({
  269. currentSiteId: siteStore.id
  270. })
  271. }
  272. })
  273. watch(() => adminStore.overlay, (newValue) => {
  274. overlayIsShown.value = !!newValue
  275. })
  276. watch(() => adminStore.currentSiteId, (newValue) => {
  277. if (newValue && route.params.siteid !== newValue) {
  278. router.push({ params: { siteid: newValue } })
  279. }
  280. })
  281. // MOUNTED
  282. onMounted(async () => {
  283. await adminStore.fetchSites()
  284. if (route.params.siteid) {
  285. adminStore.$patch({
  286. currentSiteId: route.params.siteid
  287. })
  288. }
  289. adminStore.fetchInfo()
  290. })
  291. </script>
  292. <style lang="scss">
  293. .admin-nav {
  294. height: 100%;
  295. }
  296. .admin-icon {
  297. height: 64px;
  298. }
  299. .admin-sidebar {
  300. @at-root .body--light & {
  301. background-color: $dark-4;
  302. }
  303. @at-root .body--dark & {
  304. background-color: $dark-5;
  305. }
  306. .q-item__label--header {
  307. box-shadow: 0 -1px 0 0 rgba(255,255,255,.15), 0 -2px 0 0 darken($dark-6, 1%);
  308. padding-top: 16px;
  309. }
  310. }
  311. .admin-container {
  312. @at-root .body--light & {
  313. background-color: $grey-1;
  314. }
  315. @at-root .body--dark & {
  316. background-color: $dark-4;
  317. }
  318. .q-card {
  319. @at-root .body--light & {
  320. background-color: #FFF;
  321. }
  322. @at-root .body--dark & {
  323. background-color: $dark-3;
  324. }
  325. }
  326. }
  327. .admin-overlay {
  328. > .q-dialog__backdrop {
  329. background-color: rgba(0,0,0,.6);
  330. backdrop-filter: blur(5px);
  331. }
  332. > .q-dialog__inner {
  333. padding: 24px 64px;
  334. @media (max-width: $breakpoint-sm-max) {
  335. padding: 0;
  336. }
  337. > .q-layout-container {
  338. @at-root .body--light & {
  339. background-image: linear-gradient(to bottom, $dark-5 10px, $grey-3 11px, $grey-4);
  340. }
  341. @at-root .body--dark & {
  342. background-image: linear-gradient(to bottom, $dark-4 10px, $dark-4 11px, $dark-3);
  343. }
  344. border-radius: 6px;
  345. box-shadow: 0 0 0 1px rgba(0,0,0,.5);
  346. }
  347. }
  348. }
  349. .admin-footer > .q-bar {
  350. @at-root .body--light & {
  351. background-color: #FFF !important;
  352. color: $blue-grey-5 !important;
  353. a {
  354. color: $blue-grey-9 !important;
  355. text-decoration: none;
  356. }
  357. }
  358. @at-root .body--dark & {
  359. background-color: $dark-6 !important;
  360. color: $blue-grey-5 !important;
  361. a {
  362. color: $blue-grey-5 !important;
  363. text-decoration: none;
  364. }
  365. }
  366. }
  367. </style>