Jelajahi Sumber

feat: admin auth page (wip)

Nicolas Giard 2 tahun lalu
induk
melakukan
6625267bc9
40 mengubah file dengan 387 tambahan dan 208 penghapusan
  1. 0 2
      server/db/migrations/3.0.0.js
  2. 10 15
      server/graph/resolvers/authentication.js
  3. 15 6
      server/graph/schemas/authentication.graphql
  4. 3 2
      server/helpers/common.js
  5. 3 2
      server/models/authentication.js
  6. 1 0
      server/modules/authentication/auth0/definition.yml
  7. 2 1
      server/modules/authentication/azure/definition.yml
  8. 1 0
      server/modules/authentication/cas/definition.yml
  9. 1 0
      server/modules/authentication/discord/definition.yml
  10. 1 0
      server/modules/authentication/dropbox/definition.yml
  11. 1 0
      server/modules/authentication/facebook/definition.yml
  12. 0 36
      server/modules/authentication/firebase/authentication.js
  13. 0 11
      server/modules/authentication/firebase/definition.yml
  14. 1 0
      server/modules/authentication/github/definition.yml
  15. 1 0
      server/modules/authentication/gitlab/definition.yml
  16. 1 0
      server/modules/authentication/google/definition.yml
  17. 1 0
      server/modules/authentication/keycloak/definition.yml
  18. 2 0
      server/modules/authentication/ldap/definition.yml
  19. 3 1
      server/modules/authentication/local/definition.yml
  20. 1 0
      server/modules/authentication/microsoft/definition.yml
  21. 1 0
      server/modules/authentication/oauth2/definition.yml
  22. 1 0
      server/modules/authentication/oidc/definition.yml
  23. 1 0
      server/modules/authentication/okta/definition.yml
  24. 1 0
      server/modules/authentication/rocketchat/definition.yml
  25. 1 0
      server/modules/authentication/saml/definition.yml
  26. 1 0
      server/modules/authentication/slack/definition.yml
  27. 1 0
      server/modules/authentication/twitch/definition.yml
  28. 10 0
      ux/public/_assets/icons/ultraviolet-auth0.svg
  29. 11 0
      ux/public/_assets/icons/ultraviolet-gitlab.svg
  30. 13 0
      ux/public/_assets/icons/ultraviolet-keycloak.svg
  31. 4 0
      ux/public/_assets/icons/ultraviolet-oauth2.svg
  32. 7 0
      ux/public/_assets/icons/ultraviolet-okta.svg
  33. 11 0
      ux/public/_assets/icons/ultraviolet-openid.svg
  34. 1 0
      ux/public/_assets/icons/ultraviolet-rename.svg
  35. 13 0
      ux/public/_assets/icons/ultraviolet-rocketchat.svg
  36. 7 0
      ux/public/_assets/icons/ultraviolet-saml.svg
  37. 9 1
      ux/public/_assets/icons/ultraviolet-twitch.svg
  38. 7 1
      ux/src/i18n/locales/en.json
  39. 237 128
      ux/src/pages/AdminAuth.vue
  40. 2 2
      ux/src/pages/AdminStorage.vue

+ 0 - 2
server/db/migrations/3.0.0.js

@@ -63,13 +63,11 @@ exports.up = async knex => {
       table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
       table.string('module').notNullable()
       table.boolean('isEnabled').notNullable().defaultTo(false)
-      table.integer('order').unsigned().notNullable().defaultTo(0)
       table.string('displayName').notNullable().defaultTo('')
       table.jsonb('config').notNullable().defaultTo('{}')
       table.boolean('selfRegistration').notNullable().defaultTo(false)
       table.jsonb('domainWhitelist').notNullable().defaultTo('[]')
       table.jsonb('autoEnrollGroups').notNullable().defaultTo('[]')
-      table.jsonb('hideOnSites').notNullable().defaultTo('[]')
     })
     .createTable('commentProviders', table => {
       table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))

+ 10 - 15
server/graph/resolvers/authentication.js

@@ -31,18 +31,18 @@ module.exports = {
     async authStrategies () {
       return WIKI.data.authentication.map(stg => ({
         ...stg,
-        isAvailable: stg.isAvailable === true,
-        props: _.sortBy(_.transform(stg.props, (res, value, key) => {
-          res.push({
-            key,
-            value: JSON.stringify(value)
-          })
-        }, []), 'key')
+        isAvailable: stg.isAvailable === true
       }))
     },
     /**
      * Fetch active authentication strategies
      */
+    async authActiveStrategies (obj, args, context) {
+      return WIKI.models.authentication.getStrategies()
+    },
+    /**
+     * Fetch site authentication strategies
+     */
     async authSiteStrategies (obj, args, context, info) {
       let strategies = await WIKI.models.authentication.getStrategies()
       strategies = strategies.map(stg => {
@@ -268,14 +268,9 @@ module.exports = {
       }
     }
   },
-  AuthenticationStrategy: {
-    icon (ap, args) {
-      return fs.readFile(path.join(WIKI.ROOTPATH, `assets/svg/auth-icon-${ap.key}.svg`), 'utf8').catch(err => {
-        if (err.code === 'ENOENT') {
-          return null
-        }
-        throw err
-      })
+  AuthenticationActiveStrategy: {
+    strategy (obj, args, context) {
+      return _.find(WIKI.data.authentication, ['key', obj.module])
     }
   }
 }

+ 15 - 6
server/graph/schemas/authentication.graphql

@@ -9,9 +9,11 @@ extend type Query {
 
   authStrategies: [AuthenticationStrategy]
 
+  authActiveStrategies: [AuthenticationActiveStrategy]
+
   authSiteStrategies(
     siteId: UUID!
-    enabledOnly: Boolean
+    visibleOnly: Boolean
   ): [AuthenticationSiteStrategy]
 }
 
@@ -73,7 +75,7 @@ extend type Mutation {
 
 type AuthenticationStrategy {
   key: String
-  props: [KeyValuePair]
+  props: JSON
   title: String
   description: String
   isAvailable: Boolean
@@ -81,22 +83,29 @@ type AuthenticationStrategy {
   usernameType: String
   logo: String
   color: String
+  vendor: String
   website: String
   icon: String
 }
 
-type AuthenticationSiteStrategy {
-  key: String
+type AuthenticationActiveStrategy {
+  id: UUID
   strategy: AuthenticationStrategy
   displayName: String
-  order: Int
   isEnabled: Boolean
-  config: [KeyValuePair]
+  config: JSON
   selfRegistration: Boolean
   domainWhitelist: [String]
   autoEnrollGroups: [Int]
 }
 
+type AuthenticationSiteStrategy {
+  id: UUID
+  activeStrategy: AuthenticationActiveStrategy
+  order: Int
+  isVisible: Boolean
+}
+
 type AuthenticationLoginResponse {
   operation: Operation
   jwt: String

+ 3 - 2
server/helpers/common.js

@@ -29,11 +29,12 @@ module.exports = {
         default: defaultValue,
         type: (value.type || value).toLowerCase(),
         title: value.title || _.startCase(key),
-        hint: value.hint || false,
+        hint: value.hint || '',
         enum: value.enum || false,
+        enumDisplay: value.enumDisplay || 'select',
         multiline: value.multiline || false,
         sensitive: value.sensitive || false,
-        maxWidth: value.maxWidth || 0,
+        icon: value.icon || 'rename',
         order: value.order || 100
       })
       return result

+ 3 - 2
server/models/authentication.js

@@ -51,13 +51,14 @@ module.exports = class Authentication extends Model {
       for (const dir of authenticationDirs) {
         const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/authentication', dir, 'definition.yml'), 'utf8')
         const defParsed = yaml.load(def)
+        if (!defParsed.isAvailable) { continue }
         defParsed.key = dir
         defParsed.props = commonHelper.parseModuleProps(defParsed.props)
-        WIKI.data.analytics.push(defParsed)
+        WIKI.data.authentication.push(defParsed)
         WIKI.logger.debug(`Loaded authentication module definition ${dir}: [ OK ]`)
       }
 
-      WIKI.logger.info(`Loaded ${WIKI.data.analytics.length} authentication module definitions: [ OK ]`)
+      WIKI.logger.info(`Loaded ${WIKI.data.authentication.length} authentication module definitions: [ OK ]`)
     } catch (err) {
       WIKI.logger.error(`Failed to scan or load authentication providers: [ FAILED ]`)
       WIKI.logger.error(err)

+ 1 - 0
server/modules/authentication/auth0/definition.yml

@@ -3,6 +3,7 @@ title: Auth0
 description: Auth0 provides universal identity platform for web, mobile, IoT, and internal applications.
 author: requarks.io
 logo: https://static.requarks.io/logo/auth0.svg
+icon: /_assets/icons/ultraviolet-auth0.svg
 color: deep-orange
 website: https://auth0.com/
 isAvailable: true

+ 2 - 1
server/modules/authentication/azure/definition.yml

@@ -1,8 +1,9 @@
 key: azure
 title: Azure Active Directory
-description: Azure Active Directory (Azure AD) is Microsofts multi-tenant, cloud-based directory, and identity management service that combines core directory services, application access management, and identity protection into a single solution.
+description: Azure Active Directory (Azure AD) is Microsoft's multi-tenant, cloud-based directory, and identity management service that combines core directory services, application access management, and identity protection into a single solution.
 author: requarks.io
 logo: https://static.requarks.io/logo/azure.svg
+icon: /_assets/icons/ultraviolet-azure.svg
 color: blue darken-3
 website: https://azure.microsoft.com/services/active-directory/
 isAvailable: true

+ 1 - 0
server/modules/authentication/cas/definition.yml

@@ -3,6 +3,7 @@ title: CAS
 description: The Central Authentication Service (CAS) is a single sign-on protocol for the web.
 author: requarks.io
 logo: https://static.requarks.io/logo/cas.svg
+icon: /_assets/icons/ultraviolet-cas.svg
 color: green darken-2
 website: https://apereo.github.io/cas/
 useForm: false

+ 1 - 0
server/modules/authentication/discord/definition.yml

@@ -3,6 +3,7 @@ title: Discord
 description: Discord is a proprietary freeware VoIP application designed for gaming communities, that specializes in text, video and audio communication between users in a chat channel.
 author: requarks.io
 logo: https://static.requarks.io/logo/discord.svg
+icon: /_assets/icons/ultraviolet-discord.svg
 color: indigo lighten-2
 website: https://discord.com/
 isAvailable: true

+ 1 - 0
server/modules/authentication/dropbox/definition.yml

@@ -3,6 +3,7 @@ title: Dropbox
 description: Dropbox is a file hosting service that offers cloud storage, file synchronization, personal cloud, and client software.
 author: requarks.io
 logo: https://static.requarks.io/logo/dropbox.svg
+icon: /_assets/icons/ultraviolet-dropbox.svg
 color: blue darken-2
 website: https://dropbox.com
 isAvailable: true

+ 1 - 0
server/modules/authentication/facebook/definition.yml

@@ -3,6 +3,7 @@ title: Facebook
 description: Facebook is an online social media and social networking service company.
 author: requarks.io
 logo: https://static.requarks.io/logo/facebook.svg
+icon: /_assets/icons/ultraviolet-facebook.svg
 color: indigo
 website: https://facebook.com/
 isAvailable: true

+ 0 - 36
server/modules/authentication/firebase/authentication.js

@@ -1,36 +0,0 @@
-/* global WIKI */
-
-// ------------------------------------
-// Firebase Account
-// ------------------------------------
-
-// INCOMPLETE / TODO
-
-const FirebaseStrategy = require('passport-github2').Strategy
-const _ = require('lodash')
-
-module.exports = {
-  init (passport, conf) {
-    passport.use(conf.key,
-      new FirebaseStrategy({
-        clientID: conf.clientId,
-        clientSecret: conf.clientSecret,
-        callbackURL: conf.callbackURL,
-        scope: ['user:email']
-      }, async (req, accessToken, refreshToken, profile, cb) => {
-        try {
-          const user = await WIKI.models.users.processProfile({
-            providerKey: req.params.strategy,
-            profile: {
-              ...profile,
-              picture: _.get(profile, 'photos[0].value', '')
-            }
-          })
-          cb(null, user)
-        } catch (err) {
-          cb(err, null)
-        }
-      }
-      ))
-  }
-}

+ 0 - 11
server/modules/authentication/firebase/definition.yml

@@ -1,11 +0,0 @@
-key: firebase
-title: Firebase
-description: Firebase is Google's mobile platform that helps you quickly develop high-quality apps and grow your business.
-author: requarks.io
-logo: https://static.requarks.io/logo/firebase.svg
-color: yellow darken-3
-website: https://firebase.google.com/
-isAvailable: false
-useForm: false
-props: {}
-

+ 1 - 0
server/modules/authentication/github/definition.yml

@@ -3,6 +3,7 @@ title: GitHub
 description: GitHub Inc. is a web-based hosting service for version control using Git.
 author: requarks.io
 logo: https://static.requarks.io/logo/github.svg
+icon: /_assets/icons/ultraviolet-github.svg
 color: grey darken-3
 website: https://github.com
 isAvailable: true

+ 1 - 0
server/modules/authentication/gitlab/definition.yml

@@ -3,6 +3,7 @@ title: GitLab
 description: GitLab is a web-based DevOps lifecycle tool that provides a Git-repository manager providing wiki, issue-tracking and CI/CD pipeline features.
 author: requarks.io
 logo: https://static.requarks.io/logo/gitlab.svg
+icon: /_assets/icons/ultraviolet-gitlab.svg
 color: deep-orange
 website: https://gitlab.com
 isAvailable: true

+ 1 - 0
server/modules/authentication/google/definition.yml

@@ -3,6 +3,7 @@ title: Google
 description: Google specializes in Internet-related services and products, which include online advertising technologies, search engine, cloud computing, software, and hardware.
 author: requarks.io
 logo: https://static.requarks.io/logo/google.svg
+icon: /_assets/icons/ultraviolet-google.svg
 color: red darken-1
 website: https://console.developers.google.com/
 isAvailable: true

+ 1 - 0
server/modules/authentication/keycloak/definition.yml

@@ -3,6 +3,7 @@ title: Keycloak
 description: Keycloak is an open source software product to allow single sign-on with Identity Management and Access Management aimed at modern applications and services.
 author: D4uS1
 logo: https://static.requarks.io/logo/keycloak.svg
+icon: /_assets/icons/ultraviolet-keycloak.svg
 color: blue-grey darken-2
 website: https://www.keycloak.org/
 useForm: false

+ 2 - 0
server/modules/authentication/ldap/definition.yml

@@ -3,7 +3,9 @@ title: LDAP / Active Directory
 description: Active Directory is a directory service that Microsoft developed for the Windows domain networks.
 author: requarks.io
 logo: https://static.requarks.io/logo/active-directory.svg
+icon: /_assets/icons/ultraviolet-windows8.svg
 color: blue darken-3
+vendor: Microsoft Corporation
 website: https://www.microsoft.com/windowsserver
 isAvailable: true
 useForm: true

+ 3 - 1
server/modules/authentication/local/definition.yml

@@ -3,8 +3,10 @@ title: Local Database
 description: Built-in authentication for Wiki.js
 author: requarks.io
 logo: https://static.requarks.io/logo/wikijs.svg
+icon: /_assets/icons/ultraviolet-data-protection.svg
 color: primary
-website: https://wiki.js.org
+vendor: 'Wiki.js'
+website: 'https://js.wiki'
 isAvailable: true
 useForm: true
 usernameType: email

+ 1 - 0
server/modules/authentication/microsoft/definition.yml

@@ -3,6 +3,7 @@ title: Microsoft
 description: Microsoft is a software company, best known for it's Windows, Office, Azure, Xbox and Surface products.
 author: requarks.io
 logo: https://static.requarks.io/logo/microsoft.svg
+icon: /_assets/icons/ultraviolet-microsoft.svg
 color: blue
 website: https://apps.dev.microsoft.com/
 isAvailable: false

+ 1 - 0
server/modules/authentication/oauth2/definition.yml

@@ -3,6 +3,7 @@ title: Generic OAuth2
 description: OAuth 2.0 is the industry-standard protocol for authorization.
 author: requarks.io
 logo: https://static.requarks.io/logo/oauth2.svg
+icon: /_assets/icons/ultraviolet-oauth2.svg
 color: blue-grey darken-2
 website: https://oauth.net/2/
 isAvailable: true

+ 1 - 0
server/modules/authentication/oidc/definition.yml

@@ -3,6 +3,7 @@ title: Generic OpenID Connect / OAuth2
 description: OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol.
 author: requarks.io
 logo: https://static.requarks.io/logo/oidc.svg
+icon: /_assets/icons/ultraviolet-openid.svg
 color: blue-grey darken-2
 website: http://openid.net/connect/
 isAvailable: true

+ 1 - 0
server/modules/authentication/okta/definition.yml

@@ -3,6 +3,7 @@ title: Okta
 description: Okta provide secure identity management and single sign-on to any application.
 author: requarks.io
 logo: https://static.requarks.io/logo/okta.svg
+icon: /_assets/icons/ultraviolet-okta.svg
 color: blue darken-1
 website: https://www.okta.com/
 isAvailable: true

+ 1 - 0
server/modules/authentication/rocketchat/definition.yml

@@ -3,6 +3,7 @@ title: Rocket.chat
 description: Communicate and collaborate with your team, share files, chat in real-time, or switch to video/audio conferencing.
 author: requarks.io
 logo: https://static.requarks.io/logo/rocketchat.svg
+icon: /_assets/icons/ultraviolet-rocketchat.svg
 color: red accent-3
 website: https://rocket.chat/
 isAvailable: true

+ 1 - 0
server/modules/authentication/saml/definition.yml

@@ -3,6 +3,7 @@ title: SAML 2.0
 description: Security Assertion Markup Language 2.0 (SAML 2.0) is a version of the SAML standard for exchanging authentication and authorization data between security domains.
 author: requarks.io
 logo: https://static.requarks.io/logo/saml.svg
+icon: /_assets/icons/ultraviolet-saml.svg
 color: red darken-3
 website: https://wiki.oasis-open.org/security/FrontPage
 isAvailable: true

+ 1 - 0
server/modules/authentication/slack/definition.yml

@@ -3,6 +3,7 @@ title: Slack
 description: Slack is a cloud-based set of proprietary team collaboration tools and services.
 author: requarks.io
 logo: https://static.requarks.io/logo/slack.svg
+icon: /_assets/icons/ultraviolet-slack.svg
 color: green
 website: https://api.slack.com/docs/oauth
 isAvailable: true

+ 1 - 0
server/modules/authentication/twitch/definition.yml

@@ -3,6 +3,7 @@ title: Twitch
 description: Twitch is a live streaming video platform.
 author: requarks.io
 logo: https://static.requarks.io/logo/twitch.svg
+icon: /_assets/icons/ultraviolet-twitch.svg
 color: indigo darken-2
 website: https://dev.twitch.tv/docs/authentication/
 isAvailable: true

+ 10 - 0
ux/public/_assets/icons/ultraviolet-auth0.svg

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 96 96" width="96px" height="96px">
+<g id="surface100632812">
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(87.450981%,94.117647%,99.607843%);fill-opacity:1;" d="M 25.070312 75.71875 L 48.023438 92.570312 L 70.878906 75.695312 L 48.015625 58.648438 L 25.117188 75.660156 L 33.882812 48.050781 L 10.871094 31.148438 L 39.246094 30.980469 L 48.027344 3.429688 L 19.535156 3.429688 L 10.882812 31.09375 C 5.855469 47.109375 10.996094 65.289062 25.070312 75.71875 Z M 25.070312 75.71875 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(87.450981%,94.117647%,99.607843%);fill-opacity:1;" d="M 85.035156 31.09375 L 62.203125 48.046875 L 70.902344 75.679688 L 70.925781 75.664062 C 84.886719 65.226562 90.195312 47.109375 85.113281 31.035156 L 85.101562 31.042969 L 76.339844 3.429688 L 48.03125 3.429688 L 56.792969 30.929688 Z M 85.035156 31.09375 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(27.843139%,53.333336%,78.039217%);fill-opacity:1;" d="M 74.597656 77.851562 L 74.578125 77.808594 L 74.554688 77.824219 Z M 74.597656 77.851562 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(27.843139%,53.333336%,78.039217%);fill-opacity:1;" d="M 48.023438 96 C 47.394531 96 46.769531 95.804688 46.234375 95.410156 L 23.285156 78.398438 C 23.28125 78.398438 23.28125 78.394531 23.277344 78.390625 C 8.574219 67.386719 2.449219 47.960938 8.019531 30.039062 L 16.671875 2.113281 C 17.058594 0.855469 18.222656 0 19.535156 0 L 48.027344 0 C 48.984375 0 49.878906 0.457031 50.449219 1.226562 C 51.011719 2 51.175781 2.992188 50.886719 3.902344 L 42.109375 31.71875 C 41.714844 32.964844 40.566406 33.808594 39.265625 33.816406 L 19.882812 33.933594 L 35.671875 45.640625 C 36.699219 46.398438 37.132812 47.726562 36.746094 48.953125 L 30.777344 67.933594 L 46.21875 56.351562 C 47.292969 55.550781 48.757812 55.554688 49.824219 56.355469 L 72.683594 73.566406 C 73.4375 74.136719 73.878906 75.027344 73.878906 75.972656 C 73.878906 76.914062 73.433594 77.804688 72.671875 78.367188 L 49.816406 95.40625 C 49.285156 95.800781 48.65625 96 48.023438 96 Z M 30.066406 75.957031 L 48.015625 89.261719 L 65.871094 75.949219 L 48.011719 62.5 Z M 12.683594 36.0625 C 10.21875 48.875 14.34375 62.019531 23.578125 70.808594 L 30.382812 49.1875 Z M 21.746094 6 L 14.945312 27.960938 L 37.042969 27.828125 L 43.9375 6 Z M 21.746094 6 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(27.843139%,53.333336%,78.039217%);fill-opacity:1;" d="M 70.902344 78.9375 C 70.601562 78.9375 70.292969 78.886719 69.992188 78.792969 C 69.0625 78.492188 68.324219 77.769531 68.035156 76.835938 L 59.335938 48.941406 C 58.957031 47.726562 59.382812 46.40625 60.398438 45.648438 L 76.097656 33.875 L 56.769531 33.765625 C 55.464844 33.757812 54.316406 32.90625 53.925781 31.667969 L 45.164062 3.902344 C 44.875 2.992188 45.039062 2 45.605469 1.226562 C 46.171875 0.457031 47.070312 0 48.027344 0 L 76.335938 0 C 77.648438 0 78.804688 0.847656 79.195312 2.101562 L 87.867188 29.699219 C 87.90625 29.789062 87.941406 29.878906 87.96875 29.972656 C 93.550781 47.796875 87.425781 67.222656 72.722656 78.320312 C 72.199219 78.722656 71.550781 78.9375 70.902344 78.9375 Z M 65.695312 49.175781 L 72.414062 70.726562 C 81.65625 61.886719 85.785156 48.726562 83.308594 35.972656 Z M 58.992188 27.78125 L 81.015625 27.90625 L 74.136719 6 L 52.121094 6 Z M 58.992188 27.78125 "/>
+</g>
+</svg>

File diff ditekan karena terlalu besar
+ 11 - 0
ux/public/_assets/icons/ultraviolet-gitlab.svg


+ 13 - 0
ux/public/_assets/icons/ultraviolet-keycloak.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <g id="g110">
+        <path id="path38" d="M438.48,152C437.114,152.015 435.845,151.292 435.16,150.11L377.39,49.94C376.675,48.754 375.385,48.035 374,48.05L138.33,48.05C136.96,48.032 135.687,48.754 135,49.94L75,153.89L19.17,254.09C18.5,255.275 18.5,256.725 19.17,257.91L75,358L135,462C135.685,463.182 136.954,463.905 138.32,463.89L374,463.89C375.374,463.894 376.65,463.177 377.36,462L435.2,361.9C435.885,360.718 437.154,359.995 438.52,360.01L438.45,152L438.48,152Z" style="fill:rgb(182,220,254);fill-rule:nonzero;"/>
+        <path id="path27674" d="M76.014,152L17.659,256.009L76,360L512,360L512,152" style="fill:rgb(223,240,254);fill-rule:nonzero;"/>
+        <path id="path381" serif:id="path38" d="M72.104,158.97L76.014,152L76.091,152L135,49.94C135.687,48.754 136.96,48.032 138.33,48.05L374,48.05C375.385,48.035 376.675,48.754 377.39,49.94L435.16,150.11C435.845,151.292 437.114,152.015 438.48,152L512,152L512,360L438.52,360L438.52,360.01C437.154,359.995 435.885,360.718 435.2,361.9L377.36,462C376.65,463.177 375.374,463.894 374,463.89L138.32,463.89C136.954,463.905 135.685,463.182 135,462L78.472,364.018L83.111,355.986L140.73,455.858L371.632,455.858L428.25,357.874C430.273,354.381 433.928,352.176 437.93,351.992C438.124,351.975 438.321,351.968 438.52,351.968C438.52,351.968 503.968,351.968 503.968,351.968C503.968,351.968 503.968,160.032 503.968,160.032C503.968,160.032 438.509,160.032 438.509,160.032C434.268,160.057 430.337,157.808 428.21,154.136L371.662,56.082L140.729,56.082L83.047,156.015C82.895,156.28 82.728,156.534 82.546,156.774L26.869,256.009L83.005,356.07L78.412,363.914L76.154,360L76,360L17.659,256.009L72.085,159.003C73.265,159.668 74.614,160.032 76.014,160.032L76.013,160.03C74.617,160.017 73.275,159.642 72.104,158.97Z" style="fill:rgb(71,136,199);"/>
+        <path id="path94" d="M235.08,361.89L217.08,393.16C216.772,393.636 216.354,394.031 215.86,394.31L136,256.14L176.6,256.14C176.585,256.688 176.716,257.23 176.98,257.71C176.989,257.772 177.013,257.83 177.05,257.88L235.05,358.46C235.675,359.513 235.687,360.826 235.08,361.89ZM235.15,153.81L177,254.46C176.723,254.962 176.579,255.527 176.58,256.1L136.07,256.1L215.81,117.92C216.302,118.19 216.713,118.587 217,119.07L217.11,119.18L235.19,150.59C235.702,151.605 235.687,152.808 235.15,153.81Z" style="fill:rgb(152,204,253);fill-rule:nonzero;"/>
+        <path id="path98" d="M116.044,290.714L96.97,257.64C96.706,257.16 96.575,256.618 96.59,256.07C96.589,255.497 96.733,254.932 97.01,254.43L116.31,221L175.06,119.26C175.663,118.173 176.817,117.5 178.06,117.51L214.1,117.51C214.695,117.502 215.283,117.643 215.81,117.92L136.07,256.1L136.026,256.176L215.81,394.31C215.287,394.601 214.699,394.755 214.1,394.76L178,394.76C176.757,394.77 175.603,394.097 175,393.01L121.28,300.01L116,290.79L116.044,290.714Z" style="fill:rgb(71,136,199);fill-rule:nonzero;"/>
+        <path id="path102" d="M335.63,256.1C335.624,255.496 335.454,254.905 335.14,254.39L277.14,153.84C276.512,152.774 276.512,151.446 277.14,150.38L295.22,119.08C295.526,118.612 295.932,118.219 296.41,117.93L376.19,256.1L296.39,394.31C295.912,394.021 295.506,393.628 295.2,393.16L295.13,393.06L277,361.72C276.476,360.712 276.476,359.508 277,358.5L335.06,257.85C335.38,257.322 335.549,256.717 335.55,256.1L335.63,256.1Z" style="fill:rgb(71,136,199);fill-rule:nonzero;"/>
+        <path id="path104" d="M415.68,256.1C415.679,256.717 415.51,257.322 415.19,257.85L337.06,393.16C336.436,394.16 335.338,394.769 334.16,394.77L298.16,394.77C297.549,394.766 296.947,394.612 296.41,394.32L376.19,256.1L396.19,221.48L415.19,254.39C415.504,254.905 415.674,255.496 415.68,256.1ZM396.2,221.44L376.2,256.06L296.39,117.92C296.927,117.628 297.529,117.474 298.14,117.47L334.14,117.47C335.318,117.471 336.416,118.08 337.04,119.08L396.2,221.44Z" style="fill:rgb(152,204,253);fill-rule:nonzero;"/>
+    </g>
+</svg>

File diff ditekan karena terlalu besar
+ 4 - 0
ux/public/_assets/icons/ultraviolet-oauth2.svg


+ 7 - 0
ux/public/_assets/icons/ultraviolet-okta.svg

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 175 175" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;">
+    <g transform="matrix(1,0,0,1,2.5,2.5)">
+        <circle cx="85" cy="85" r="63" style="fill:white;stroke:rgb(71,136,199);stroke-width:44px;"/>
+    </g>
+</svg>

+ 11 - 0
ux/public/_assets/icons/ultraviolet-openid.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 100 92" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <g id="path2606" transform="matrix(0.46875,0,0,-0.46875,45,7.32797)">
+        <path d="M0,0L0,-180L32,-164.939L32,15.633L0,0Z" style="fill:rgb(152,204,253);fill-rule:nonzero;stroke:rgb(71,136,199);stroke-width:2.13px;"/>
+    </g>
+    <g id="path2610">
+        <path d="M15,59.59C15,48.961 26.581,40.011 42.367,37.278L42.367,27.751C18.222,30.669 0,43.814 0,59.59C0,75.935 19.559,89.454 45,91.703L45,82.299C27.883,80.154 15,70.802 15,59.59ZM78.143,42.681C73.811,40.172 68.512,38.296 62.633,37.278L62.633,27.755C72.886,28.994 82.073,32.072 89.248,36.419L96.948,32.078L99.018,53.577L70.011,47.265L78.143,42.681Z" style="fill:rgb(223,240,254);fill-rule:nonzero;"/>
+        <path d="M15,59.59C15,48.961 26.581,40.011 42.367,37.278L42.367,27.751C18.222,30.669 0,43.814 0,59.59C0,75.935 19.559,89.454 45,91.703L45,82.299C27.883,80.154 15,70.802 15,59.59ZM13.374,59.59C13.374,64.491 15.584,69.086 19.484,72.962C24.8,78.246 33.317,82.206 43.374,83.717C43.374,83.717 43.374,89.908 43.374,89.908C29.279,88.351 17.172,83.261 9.63,76.126C4.599,71.365 1.626,65.701 1.626,59.59C1.626,53.697 4.392,48.217 9.102,43.564C16.136,36.616 27.447,31.525 40.741,29.606C40.741,29.606 40.741,35.927 40.741,35.927C31.473,37.761 23.725,41.712 18.905,46.815C15.366,50.562 13.374,54.937 13.374,59.59ZM78.143,42.681C73.811,40.172 68.512,38.296 62.633,37.278L62.633,27.755C72.886,28.994 82.073,32.072 89.248,36.419L96.948,32.078L99.018,53.577L70.011,47.265L78.143,42.681ZM78.941,44.097C79.449,43.81 79.765,43.273 79.768,42.69C79.772,42.107 79.462,41.566 78.957,41.274C74.809,38.871 69.805,37.023 64.259,35.926C64.259,35.926 64.259,29.61 64.259,29.61C73.528,30.948 81.834,33.828 88.406,37.81C88.908,38.114 89.535,38.124 90.046,37.835L95.569,34.722C95.569,34.722 97.186,51.514 97.186,51.514C97.186,51.514 74.529,46.585 74.529,46.585C74.529,46.585 78.941,44.097 78.941,44.097Z" style="fill:rgb(71,136,199);"/>
+    </g>
+</svg>

+ 1 - 0
ux/public/_assets/icons/ultraviolet-rename.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"  viewBox="0 0 40 40" width="80px" height="80px"><path fill="#fff" d="M1.5 13.5H38.5V26.5H1.5z"/><path fill="#4788c7" d="M38,14v12H2V14H38 M39,13H1v14h38V13L39,13z"/><path fill="#b6dcfe" d="M5 17H27V23H5z"/><path fill="none" stroke="#4788c7" stroke-linecap="round" stroke-miterlimit="10" d="M34.5 5.5h-1.333c-1.473 0-2.667 1.194-2.667 2.667v23.667c0 1.473 1.194 2.667 2.667 2.667H34.5M26.5 5.5h1.333c1.473 0 2.667 1.194 2.667 2.667v23.667c0 1.473-1.194 2.667-2.667 2.667H26.5"/></svg>

+ 13 - 0
ux/public/_assets/icons/ultraviolet-rocketchat.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <g id="surface100778921">
+        <g transform="matrix(1,0,0,1,1,0)">
+            <path d="M23.5,10C35.366,10 45,16.273 45,24C45,31.727 35.366,38 23.5,38C21.202,38 4,43 4,43L6,34C6,34 2,27.591 2,24C2,16.273 11.634,10 23.5,10Z" style="fill:rgb(223,240,254);"/>
+        </g>
+        <path d="M21.488,24.129C21.555,27.863 27.234,27.82 27.199,24.082L27.199,24.031C27.137,20.273 21.426,20.363 21.488,24.129Z" style="fill:rgb(71,136,199);fill-rule:nonzero;"/>
+        <path d="M12.441,24.129C12.465,25.695 13.77,26.949 15.344,26.926C16.875,26.926 18.164,25.66 18.148,24.078L18.148,24.039C18.098,20.285 12.383,20.363 12.441,24.129Z" style="fill:rgb(71,136,199);fill-rule:nonzero;"/>
+        <path d="M30.539,24.129C30.563,25.695 31.867,26.949 33.438,26.926C35.012,26.918 36.266,25.637 36.246,24.078L36.246,24.039C36.191,20.285 30.48,20.363 30.539,24.129Z" style="fill:rgb(71,136,199);fill-rule:nonzero;"/>
+        <path d="M40.535,11.688C34.121,7.469 25.602,6.512 18.523,7.781C10.605,0.18 1.73,3.672 0,4.68C0,4.68 6.09,9.832 5.102,14.344C-2.109,21.66 1.316,29.809 5.102,33.652C6.094,38.164 0,43.316 0,43.316C1.715,44.328 10.563,47.813 18.527,40.25C25.59,41.516 34.109,40.563 40.535,36.336C50.473,30.02 50.508,18.035 40.535,11.688ZM24.512,36.297C22.031,36.305 19.566,35.984 17.172,35.352L15.523,36.938C14.609,37.82 13.578,38.586 12.477,39.207C11.133,39.879 9.668,40.297 8.164,40.43C8.246,40.285 8.32,40.133 8.395,39.996C10.043,36.953 10.488,34.23 9.73,31.809C7.02,29.688 5.395,26.969 5.395,24C5.395,17.199 13.957,11.688 24.516,11.688C35.07,11.688 43.633,17.199 43.633,24C43.629,30.797 35.066,36.297 24.512,36.297Z" style="fill:rgb(71,136,199);fill-rule:nonzero;"/>
+    </g>
+</svg>

+ 7 - 0
ux/public/_assets/icons/ultraviolet-saml.svg

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 109 105" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
+    <g transform="matrix(1,0,0,1,-100.5,0.5)">
+        <path d="M111.1,101.044C149.869,109.584 202.903,89.9 202.903,89.9C202.903,89.9 192.718,72.842 185.955,64.965C178.736,56.557 159.591,39.452 159.591,39.452C159.591,39.452 191.454,68.807 181.208,83.471C172.443,96.016 154.44,99.138 111.1,101.044ZM135,3C109,33 101,89 101,89C101,89 120.851,88.186 131,86C141.833,83.667 166,75 166,75C166,75 125,89 117,73C110.156,59.311 116,42 135,3ZM208.661,73.549C196.413,35.787 152.603,0 152.603,0C152.603,0 143.042,17.416 139.669,27.234C136.068,37.715 131,62.883 131,62.883C131,62.883 140.201,20.547 158.033,21.966C173.289,23.18 185.09,37.129 208.661,73.549Z" style="fill:rgb(152,204,253);stroke:rgb(71,136,199);stroke-width:1px;"/>
+    </g>
+</svg>

+ 9 - 1
ux/public/_assets/icons/ultraviolet-twitch.svg

@@ -1 +1,9 @@
-<svg xmlns="http://www.w3.org/2000/svg"  viewBox="0 0 40 40" width="40px" height="40px"><path fill="#dff0fe" d="M10,37.5c-4.136,0-7.5-3.364-7.5-7.5V10c0-4.136,3.364-7.5,7.5-7.5h20c4.136,0,7.5,3.364,7.5,7.5v20 c0,4.136-3.364,7.5-7.5,7.5H10z"/><path fill="#4788c7" d="M30,3c3.86,0,7,3.14,7,7v20c0,3.86-3.14,7-7,7H10c-3.86,0-7-3.14-7-7V10c0-3.86,3.14-7,7-7H30 M30,2H10c-4.418,0-8,3.582-8,8v20c0,4.418,3.582,8,8,8h20c4.418,0,8-3.582,8-8V10C38,5.582,34.418,2,30,2L30,2z"/><polygon fill="#fff" points="24,28 12,28 12,10 30,10 30,22"/><path fill="#98ccfd" d="M10.257,9.649L9.064,12.83C9.022,12.942,9,13.061,9,13.181V28c0,0.552,0.448,1,1,1h5v2	c0,0.552,0.448,1,1,1h1.086c0.265,0,0.52-0.105,0.707-0.293L20.5,29h4.086c0.265,0,0.52-0.105,0.707-0.293l5.414-5.414	C30.895,23.105,31,22.851,31,22.586V10c0-0.552-0.448-1-1-1H11.193C10.776,9,10.403,9.259,10.257,9.649z M13,11h16v11l-3,3h-6l-3,3	v-3h-4V11z M18,15v6h2v-6H18z M23,15v6h2v-6H23z"/></svg>
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 96 96" width="96px" height="96px">
+<g id="surface100548424">
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(27.843139%,53.333336%,78.039217%);fill-opacity:1;" d="M 83.507812 55.914062 L 66.597656 75.3125 C 66.21875 75.75 65.667969 76 65.089844 76 L 52.738281 76 C 52.261719 76 51.800781 76.171875 51.4375 76.480469 L 38.5625 87.519531 C 38.199219 87.828125 37.738281 88 37.261719 88 L 34 88 C 32.894531 88 32 87.105469 32 86 L 32 78 C 32 76.894531 31.105469 76 30 76 L 14 76 C 12.894531 76 12 75.105469 12 74 L 12 31.886719 C 12 31.574219 12.070312 31.269531 12.210938 30.988281 L 20.175781 15.105469 C 20.515625 14.429688 21.207031 14 21.960938 14 L 82 14 C 83.105469 14 84 14.894531 84 16 L 84 54.601562 C 84 55.085938 83.824219 55.550781 83.507812 55.914062 Z M 83.507812 55.914062 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.039216%,98.039216%,98.039216%);fill-opacity:1;" d="M 78 52.738281 C 74.664062 56.492188 71.335938 60.246094 68 64 C 63.335938 64 58.664062 64 54 64 C 49.335938 68 44.664062 72 40 76 C 40 72 40 68 40 64 C 34.664062 63.984375 29.335938 63.96875 24 63.953125 C 24 49.300781 24 34.648438 24 20 C 42 20 60 20 78 20 C 78 30.910156 78 41.824219 78 52.738281 Z M 78 52.738281 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(27.843139%,53.333336%,78.039217%);fill-opacity:1;" d="M 42 32 L 48 32 L 48 52 L 42 52 Z M 42 32 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(27.843139%,53.333336%,78.039217%);fill-opacity:1;" d="M 60 32 L 66 32 L 66 52 L 60 52 Z M 60 32 "/>
+</g>
+</svg>

+ 7 - 1
ux/src/i18n/locales/en.json

@@ -1443,5 +1443,11 @@
   "admin.utilities.exportHint": "Export content to tarball for backup / migration.",
   "admin.utilities.importHint": "Import content from a tarball backup or a 2.X backup.",
   "admin.utilities.flushCache": "Flush Cache",
-  "admin.utilities.flushCacheHint": "Pages and Assets are cached to disk for better performance. You can flush the cache to force all content to be fetched from the DB again."
+  "admin.utilities.flushCacheHint": "Pages and Assets are cached to disk for better performance. You can flush the cache to force all content to be fetched from the DB again.",
+  "admin.auth.enabledForced": "This strategy cannot be disabled.",
+  "admin.auth.enabled": "Enabled",
+  "admin.auth.enabledHint": "Should this strategy be available to sites for login.",
+  "admin.auth.vendor": "Vendor",
+  "admin.auth.vendorWebsite": "Website",
+  "admin.auth.status": "Status"
 }

+ 237 - 128
ux/src/pages/AdminAuth.vue

@@ -33,11 +33,11 @@ q-page.admin-mail
           dark
           )
           q-item(
-            v-for='str of state.activeStrategies'
+            v-for='str of combinedActiveStrategies'
             :key='str.key'
             active-class='bg-primary text-white'
-            :active='state.selectedStrategy === str.key'
-            @click='state.selectedStrategy = str.key'
+            :active='state.selectedStrategy === str.id'
+            @click='state.selectedStrategy = str.id'
             clickable
             )
             q-item-section(side)
@@ -54,54 +54,159 @@ q-page.admin-mail
         icon='las la-plus'
         :label='t(`admin.auth.addStrategy`)'
         )
-        q-menu(auto-close)
-          q-list(style='min-width: 350px;')
-            q-item(clickable)
-    .col Doude
+        q-menu(auto-close, fit, max-width='300px')
+          q-list(separator)
+            q-item(
+              v-for='str of availableStrategies'
+              :key='str.key'
+              clickable
+              @click='addStrategy(str)'
+              )
+              q-item-section(avatar)
+                q-avatar(
+                  rounded
+                  color='dark'
+                  text-color='white'
+                  )
+                  q-icon(
+                    :name='`img:` + str.icon'
+                  )
+              q-item-section
+                q-item-label: strong {{str.title}}
+                q-item-label(caption, lines='2') {{str.description}}
+    .col
+      q-card.shadow-1.q-pb-sm
+        q-card-section
+          .text-subtitle1 {{t('admin.storage.contentTypes')}}
+          .text-body2.text-grey {{ t('admin.storage.contentTypesHint') }}
 
-    //- v-flex(lg3, xs12)
-    //-   v-card.animated.fadeInUp
-    //-     v-toolbar(flat, color='teal', dark, dense)
-    //-       .subtitle-1 {{$t('admin.auth.activeStrategies')}}
-    //-     v-list(two-line, dense).py-0
-    //-       draggable(
-    //-         v-model='activeStrategies'
-    //-         handle='.is-handle'
-    //-         direction='vertical'
-    //-         )
-    //-         transition-group
-    //-           v-list-item(
-    //-             v-for='(str, idx) in activeStrategies'
-    //-             :key='str.key'
-    //-             @click='selectedStrategy = str.key'
-    //-             :class='selectedStrategy === str.key ? ($vuetify.theme.dark ? `grey darken-5` : `teal lighten-5`) : ``'
-    //-             )
-    //-             v-list-item-avatar.is-handle(size='24')
-    //-               v-icon(:color='selectedStrategy === str.key ? `teal` : `grey`') mdi-drag-horizontal
-    //-             v-list-item-content
-    //-               v-list-item-title.body-2(:class='selectedStrategy === str.key ? `teal--text` : ``') {{ str.displayName }}
-    //-               v-list-item-subtitle: .caption(:class='selectedStrategy === str.key ? `teal--text ` : ``') {{ str.strategy.title }}
-    //-             v-list-item-avatar(v-if='selectedStrategy === str.key', size='24')
-    //-               v-icon.animated.fadeInLeft(color='teal', large) mdi-chevron-right
-    //-     v-card-chin
-    //-       v-menu(offset-y, bottom, min-width='250px', max-width='550px', max-height='50vh', style='flex: 1 1;', center)
-    //-         template(v-slot:activator='{ on }')
-    //-           v-btn(v-on='on', color='primary', depressed, block)
-    //-             v-icon(left) mdi-plus
-    //-             span {{$t('admin.auth.addStrategy')}}
-    //-         v-list(dense)
-    //-           template(v-for='(str, idx) of strategies')
-    //-             v-list-item(
-    //-               :key='str.key'
-    //-               :disabled='str.isDisabled'
-    //-               @click='addStrategy(str)'
-    //-               )
-    //-               v-list-item-avatar(height='24', width='48', tile)
-    //-                 v-img(:src='str.logo', width='48px', height='24px', contain, :style='str.isDisabled ? `opacity: .25;` : ``')
-    //-               v-list-item-content
-    //-                 v-list-item-title {{str.title}}
-    //-                 v-list-item-subtitle: .caption(:style='str.isDisabled ? `opacity: .4;` : ``') {{str.description}}
-    //-             v-divider(v-if='idx < strategies.length - 1')
+      //- -----------------------
+      //- Configuration
+      //- -----------------------
+      q-card.shadow-1.q-pb-sm.q-mt-md
+        q-card-section
+          .text-subtitle1 {{t('admin.storage.config')}}
+          q-banner.q-mt-md(
+            v-if='!state.strategy.config || Object.keys(state.strategy.config).length < 1'
+            rounded
+            :class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
+            ) {{t('admin.storage.noConfigOption')}}
+        template(
+          v-for='(cfg, cfgKey, idx) in state.strategy.config'
+          )
+          template(
+            v-if='configIfCheck(cfg.if)'
+            )
+            q-separator.q-my-sm(inset, v-if='idx > 0')
+            q-item(v-if='cfg.type === `boolean`', tag='label')
+              blueprint-icon(:icon='cfg.icon', :hue-rotate='cfg.readOnly ? -45 : 0')
+              q-item-section
+                q-item-label {{cfg.title}}
+                q-item-label(caption) {{cfg.hint}}
+              q-item-section(avatar)
+                q-toggle(
+                  v-model='cfg.value'
+                  color='primary'
+                  checked-icon='las la-check'
+                  unchecked-icon='las la-times'
+                  :aria-label='t(`admin.general.allowComments`)'
+                  :disable='cfg.readOnly'
+                  )
+            q-item(v-else)
+              blueprint-icon(:icon='cfg.icon', :hue-rotate='cfg.readOnly ? -45 : 0')
+              q-item-section
+                q-item-label {{cfg.title}}
+                q-item-label(caption) {{cfg.hint}}
+              q-item-section(
+                :style='cfg.type === `number` ? `flex: 0 0 150px;` : ``'
+                :class='{ "col-auto": cfg.enum && cfg.enumDisplay === `buttons` }'
+                )
+                q-btn-toggle(
+                  v-if='cfg.enum && cfg.enumDisplay === `buttons`'
+                  v-model='cfg.value'
+                  push
+                  glossy
+                  no-caps
+                  toggle-color='primary'
+                  :options='cfg.enum'
+                  :disable='cfg.readOnly'
+                )
+                q-select(
+                  v-else-if='cfg.enum'
+                  outlined
+                  v-model='cfg.value'
+                  :options='cfg.enum'
+                  emit-value
+                  map-options
+                  dense
+                  options-dense
+                  :aria-label='cfg.title'
+                  :disable='cfg.readOnly'
+                )
+                q-input(
+                  v-else
+                  outlined
+                  v-model='cfg.value'
+                  dense
+                  :type='cfg.multiline ? `textarea` : `input`'
+                  :aria-label='cfg.title'
+                  :disable='cfg.readOnly'
+                  )
+
+    .col-auto(v-if='state.selectedStrategy && state.strategy')
+      //- -----------------------
+      //- Infobox
+      //- -----------------------
+      q-card.rounded-borders.q-pb-md(style='width: 350px;')
+        q-card-section
+          .text-subtitle1 {{state.strategy.strategy.title}}
+          q-img.q-mt-sm.rounded-borders(
+            :src='state.strategy.strategy.logo'
+            fit='cover'
+            no-spinner
+          )
+          .text-body2.q-mt-md {{state.strategy.strategy.description}}
+        q-separator.q-mb-sm(inset)
+        q-item
+          q-item-section
+            q-item-label.text-grey {{t(`admin.auth.vendor`)}}
+            q-item-label {{state.strategy.strategy.vendor}}
+        q-separator.q-my-sm(inset)
+        q-item
+          q-item-section
+            q-item-label.text-grey {{t(`admin.auth.vendorWebsite`)}}
+            q-item-label: a(:href='state.strategy.strategy.website', target='_blank', rel='noreferrer') {{state.strategy.strategy.website}}
+
+      //- -----------------------
+      //- Status
+      //- -----------------------
+      q-card.rounded-borders.q-pb-md.q-mt-md(style='width: 350px;')
+        q-card-section
+          .text-subtitle1 {{t(`admin.auth.status`)}}
+        q-item(tag='label')
+          q-item-section
+            q-item-label {{t(`admin.auth.enabled`)}}
+            q-item-label(caption) {{t(`admin.auth.enabledHint`)}}
+            q-item-label.text-deep-orange(v-if='state.strategy.strategy.key === `local`', caption) {{t(`admin.auth.enabledForced`)}}
+          q-item-section(avatar)
+            q-toggle(
+              v-model='state.strategy.isEnabled'
+              :disable='state.strategy.strategy.key === `local`'
+              color='primary'
+              checked-icon='las la-check'
+              unchecked-icon='las la-times'
+              :aria-label='t(`admin.auth.enabled`)'
+              )
+        q-separator.q-my-sm(inset)
+        q-item
+          q-item-section
+            q-btn.acrylic-btn(
+              icon='las la-trash-alt'
+              flat
+              color='negative'
+              :disable='state.strategy.strategy.key === `local`'
+              label='Delete Strategy'
+              )
 
     //- v-flex(xs12, lg9)
     //-   v-card.animated.fadeInUp.wait-p2s
@@ -263,7 +368,7 @@ q-page.admin-mail
 
 <script setup>
 import gql from 'graphql-tag'
-import { find, reject } from 'lodash-es'
+import { find, reject, transform } from 'lodash-es'
 import { v4 as uuid } from 'uuid'
 
 import { useI18n } from 'vue-i18n'
@@ -273,8 +378,6 @@ import { computed, onMounted, reactive, watch, nextTick } from 'vue'
 import { useAdminStore } from 'src/stores/admin'
 import { useSiteStore } from 'src/stores/site'
 
-import draggable from 'vuedraggable'
-
 // QUASAR
 
 const $q = useQuasar()
@@ -300,59 +403,7 @@ const state = reactive({
   loading: 0,
   groups: [],
   strategies: [],
-  activeStrategies: [
-    {
-      key: 'local',
-      strategy: {
-        key: 'local',
-        title: 'Username-Password Authentication',
-        description: '',
-        useForm: true,
-        icon: '/_assets/icons/ultraviolet-data-protection.svg',
-        website: ''
-      },
-      config: [],
-      isEnabled: true,
-      displayName: 'Local Database',
-      selfRegistration: false,
-      domainWhitelist: '',
-      autoEnrollGroups: []
-    },
-    {
-      key: 'google',
-      strategy: {
-        key: 'google',
-        title: 'Google',
-        description: '',
-        useForm: true,
-        icon: '/_assets/icons/ultraviolet-google.svg',
-        website: ''
-      },
-      config: [],
-      isEnabled: true,
-      displayName: 'Google',
-      selfRegistration: false,
-      domainWhitelist: '',
-      autoEnrollGroups: []
-    },
-    {
-      key: 'slack',
-      strategy: {
-        key: 'slack',
-        title: 'Slack',
-        description: '',
-        useForm: true,
-        icon: '/_assets/icons/ultraviolet-slack.svg',
-        website: ''
-      },
-      config: [],
-      isEnabled: false,
-      displayName: 'Slack',
-      selfRegistration: false,
-      domainWhitelist: '',
-      autoEnrollGroups: []
-    }
-  ],
+  activeStrategies: [],
   selectedStrategy: '',
   host: '',
   strategy: {
@@ -360,39 +411,93 @@ const state = reactive({
   }
 })
 
+// COMPUTED
+
+const availableStrategies = computed(() => {
+  return state.strategies.filter(str => str.key !== 'local')
+})
+
+const combinedActiveStrategies = computed(() => {
+  return state.activeStrategies.map(str => ({
+    ...str,
+    strategy: find(state.strategies, ['key', str.strategy?.key]) || {}
+  }))
+})
+
 // WATCHERS
 
 watch(() => state.selectedStrategy, (newValue, oldValue) => {
-  state.strategy = find(state.activeStrategies, ['key', newValue]) || {}
+  const str = find(combinedActiveStrategies.value, ['id', newValue]) || {}
+  str.config = transform(str.strategy.props, (cfg, v, k) => {
+    cfg[k] = {
+      ...v,
+      value: str.config[k]
+    }
+  }, {})
+  state.strategy = str
 })
 watch(() => state.activeStrategies, (newValue, oldValue) => {
-  state.selectedStrategy = 'local'
+  state.selectedStrategy = newValue[0]?.id
 })
 
 // METHODS
 
-async function refresh () {
-  await this.$apollo.queries.strategies.refetch()
-  await this.$apollo.queries.activeStrategies.refetch()
-  this.$store.commit('showNotification', {
-    message: this.$t('admin.auth.refreshSuccess'),
-    style: 'success',
-    icon: 'cached'
+async function load () {
+  state.loading++
+  $q.loading.show()
+  const resp = await APOLLO_CLIENT.query({
+    query: gql`
+      query adminFetchAuthStrategies {
+        authStrategies {
+          key
+          props
+          title
+          description
+          isAvailable
+          useForm
+          usernameType
+          logo
+          color
+          vendor
+          website
+          icon
+        }
+        authActiveStrategies {
+          id
+          strategy {
+            key
+          }
+          displayName
+          isEnabled
+          config
+          selfRegistration
+          domainWhitelist
+          autoEnrollGroups
+        }
+      }
+    `,
+    fetchPolicy: 'network-only'
   })
+  state.strategies = resp?.data?.authStrategies || []
+  state.activeStrategies = resp?.data?.authActiveStrategies || []
+  $q.loading.hide()
+  state.loading--
+}
+
+function configIfCheck (ifs) {
+  if (!ifs || ifs.length < 1) { return true }
+  return ifs.every(s => state.strategy.config[s.key]?.value === s.eq)
 }
 
 function addStrategy (str) {
   const newStr = {
-    key: uuid(),
-    strategy: str,
-    config: str.props.map(c => ({
-      key: c.key,
-      value: {
-        ...c,
-        value: c.default
-      }
-    })),
-    order: state.activeStrategies.length,
+    id: uuid(),
+    strategy: {
+      key: str.key
+    },
+    config: transform(str.props, (cfg, v, k) => {
+      cfg[k] = v.default
+    }, {}),
     isEnabled: true,
     displayName: str.title,
     selfRegistration: false,
@@ -401,12 +506,12 @@ function addStrategy (str) {
   }
   state.activeStrategies = [...state.activeStrategies, newStr]
   nextTick(() => {
-    state.selectedStrategy = newStr.key
+    state.selectedStrategy = newStr.id
   })
 }
 
-function deleteStrategy () {
-  state.activeStrategies = reject(state.activeStrategies, ['key', state.strategy.key])
+function deleteStrategy (id) {
+  state.activeStrategies = reject(state.activeStrategies, ['id', id])
 }
 
 async function save () {
@@ -551,4 +656,8 @@ async function save () {
 //       this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-auth-host-refresh')
 //     }
 //   }
+
+onMounted(() => {
+  load()
+})
 </script>

+ 2 - 2
ux/src/pages/AdminStorage.vue

@@ -438,7 +438,7 @@ q-page.admin-storage
       //- -----------------------
       q-card.rounded-borders.q-pb-md.q-mt-md(style='width: 350px;')
         q-card-section
-          .text-subtitle1 Status
+          .text-subtitle1 {{ t('admin.storage.status') }}
         template(v-if='state.target.module !== `db` && !(state.target.setup && state.target.setup.handler && state.target.setup.state !== `configured`)')
           q-item(tag='label')
             q-item-section
@@ -452,7 +452,7 @@ q-page.admin-storage
                 color='primary'
                 checked-icon='las la-check'
                 unchecked-icon='las la-times'
-                :aria-label='t(`admin.general.allowSearch`)'
+                :aria-label='t(`admin.storage.enabled`)'
                 )
           q-separator.q-my-sm(inset)
         q-item

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini