feat: admin api keys

parent 6625267b
...@@ -29,7 +29,7 @@ exports.up = async knex => { ...@@ -29,7 +29,7 @@ exports.up = async knex => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()')) table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('name').notNullable() table.string('name').notNullable()
table.text('key').notNullable() table.text('key').notNullable()
table.string('expiration').notNullable() table.timestamp('expiration').notNullable().defaultTo(knex.fn.now())
table.boolean('isRevoked').notNullable().defaultTo(false) table.boolean('isRevoked').notNullable().defaultTo(false)
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now()) table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now()) table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
......
...@@ -12,6 +12,7 @@ module.exports = { ...@@ -12,6 +12,7 @@ module.exports = {
*/ */
async apiKeys (obj, args, context) { async apiKeys (obj, args, context) {
const keys = await WIKI.models.apiKeys.query().orderBy(['isRevoked', 'name']) const keys = await WIKI.models.apiKeys.query().orderBy(['isRevoked', 'name'])
console.info(keys)
return keys.map(k => ({ return keys.map(k => ({
id: k.id, id: k.id,
name: k.name, name: k.name,
...@@ -78,9 +79,10 @@ module.exports = { ...@@ -78,9 +79,10 @@ module.exports = {
WIKI.events.outbound.emit('reloadApiKeys') WIKI.events.outbound.emit('reloadApiKeys')
return { return {
key, key,
responseResult: graphHelper.generateSuccess('API Key created successfully') operation: graphHelper.generateSuccess('API Key created successfully')
} }
} catch (err) { } catch (err) {
WIKI.logger.warn(err)
return graphHelper.generateError(err) return graphHelper.generateError(err)
} }
}, },
...@@ -165,7 +167,7 @@ module.exports = { ...@@ -165,7 +167,7 @@ module.exports = {
WIKI.config.api.isEnabled = args.enabled WIKI.config.api.isEnabled = args.enabled
await WIKI.configSvc.saveToDb(['api']) await WIKI.configSvc.saveToDb(['api'])
return { return {
responseResult: graphHelper.generateSuccess('API State changed successfully') operation: graphHelper.generateSuccess('API State changed successfully')
} }
} catch (err) { } catch (err) {
return graphHelper.generateError(err) return graphHelper.generateError(err)
...@@ -182,7 +184,7 @@ module.exports = { ...@@ -182,7 +184,7 @@ module.exports = {
await WIKI.auth.reloadApiKeys() await WIKI.auth.reloadApiKeys()
WIKI.events.outbound.emit('reloadApiKeys') WIKI.events.outbound.emit('reloadApiKeys')
return { return {
responseResult: graphHelper.generateSuccess('API Key revoked successfully') operation: graphHelper.generateSuccess('API Key revoked successfully')
} }
} catch (err) { } catch (err) {
return graphHelper.generateError(err) return graphHelper.generateError(err)
......
...@@ -21,8 +21,7 @@ extend type Mutation { ...@@ -21,8 +21,7 @@ extend type Mutation {
createApiKey( createApiKey(
name: String! name: String!
expiration: String! expiration: String!
fullAccess: Boolean! groups: [UUID]!
group: Int
): AuthenticationCreateApiKeyResponse ): AuthenticationCreateApiKeyResponse
login( login(
...@@ -53,7 +52,7 @@ extend type Mutation { ...@@ -53,7 +52,7 @@ extend type Mutation {
): AuthenticationRegisterResponse ): AuthenticationRegisterResponse
revokeApiKey( revokeApiKey(
id: Int! id: UUID!
): DefaultResponse ): DefaultResponse
setApiState( setApiState(
...@@ -135,13 +134,13 @@ input AuthenticationStrategyInput { ...@@ -135,13 +134,13 @@ input AuthenticationStrategyInput {
} }
type AuthenticationApiKey { type AuthenticationApiKey {
id: Int! id: UUID
name: String! name: String
keyShort: String! keyShort: String
expiration: Date! expiration: Date
createdAt: Date! createdAt: Date
updatedAt: Date! updatedAt: Date
isRevoked: Boolean! isRevoked: Boolean
} }
type AuthenticationCreateApiKeyResponse { type AuthenticationCreateApiKeyResponse {
......
/* global WIKI */ /* global WIKI */
const Model = require('objection').Model const Model = require('objection').Model
const moment = require('moment') const { DateTime } = require('luxon')
const ms = require('ms') const ms = require('ms')
const jwt = require('jsonwebtoken') const jwt = require('jsonwebtoken')
...@@ -17,7 +17,7 @@ module.exports = class ApiKey extends Model { ...@@ -17,7 +17,7 @@ module.exports = class ApiKey extends Model {
required: ['name', 'key'], required: ['name', 'key'],
properties: { properties: {
id: {type: 'integer'}, id: {type: 'string'},
name: {type: 'string'}, name: {type: 'string'},
key: {type: 'string'}, key: {type: 'string'},
expiration: {type: 'string'}, expiration: {type: 'string'},
...@@ -31,29 +31,33 @@ module.exports = class ApiKey extends Model { ...@@ -31,29 +31,33 @@ module.exports = class ApiKey extends Model {
async $beforeUpdate(opt, context) { async $beforeUpdate(opt, context) {
await super.$beforeUpdate(opt, context) await super.$beforeUpdate(opt, context)
this.updatedAt = moment.utc().toISOString() this.updatedAt = new Date().toISOString()
} }
async $beforeInsert(context) { async $beforeInsert(context) {
await super.$beforeInsert(context) await super.$beforeInsert(context)
this.createdAt = moment.utc().toISOString() this.createdAt = new Date().toISOString()
this.updatedAt = moment.utc().toISOString() this.updatedAt = new Date().toISOString()
} }
static async createNewKey ({ name, expiration, fullAccess, group }) { static async createNewKey ({ name, expiration, groups }) {
console.info(DateTime.utc().plus(ms(expiration)).toISO())
const entry = await WIKI.models.apiKeys.query().insert({ const entry = await WIKI.models.apiKeys.query().insert({
name, name,
key: 'pending', key: 'pending',
expiration: moment.utc().add(ms(expiration), 'ms').toISOString(), expiration: DateTime.utc().plus(ms(expiration)).toISO(),
isRevoked: true isRevoked: true
}) })
console.info(entry)
const key = jwt.sign({ const key = jwt.sign({
api: entry.id, api: entry.id,
grp: fullAccess ? 1 : group grp: groups
}, { }, {
key: WIKI.config.certs.private, key: WIKI.config.auth.certs.private,
passphrase: WIKI.config.sessionSecret passphrase: WIKI.config.auth.secret
}, { }, {
algorithm: 'RS256', algorithm: 'RS256',
expiresIn: expiration, expiresIn: expiration,
......
...@@ -35,7 +35,7 @@ module.exports = class Authentication extends Model { ...@@ -35,7 +35,7 @@ module.exports = class Authentication extends Model {
} }
static async getStrategies() { static async getStrategies() {
const strategies = await WIKI.models.authentication.query().orderBy('order') const strategies = await WIKI.models.authentication.query()
return strategies.map(str => ({ return strategies.map(str => ({
...str, ...str,
domainWhitelist: _.get(str.domainWhitelist, 'v', []), domainWhitelist: _.get(str.domainWhitelist, 'v', []),
......
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
"@codemirror/tooltip": "0.19.16", "@codemirror/tooltip": "0.19.16",
"@codemirror/view": "6.0.2", "@codemirror/view": "6.0.2",
"@lezer/common": "1.0.0", "@lezer/common": "1.0.0",
"@quasar/extras": "1.14.2", "@quasar/extras": "1.15.0",
"@tiptap/core": "2.0.0-beta.176", "@tiptap/core": "2.0.0-beta.176",
"@tiptap/extension-code-block": "2.0.0-beta.37", "@tiptap/extension-code-block": "2.0.0-beta.37",
"@tiptap/extension-code-block-lowlight": "2.0.0-beta.68", "@tiptap/extension-code-block-lowlight": "2.0.0-beta.68",
...@@ -57,9 +57,8 @@ ...@@ -57,9 +57,8 @@
"@tiptap/extension-typography": "2.0.0-beta.20", "@tiptap/extension-typography": "2.0.0-beta.20",
"@tiptap/starter-kit": "2.0.0-beta.185", "@tiptap/starter-kit": "2.0.0-beta.185",
"@tiptap/vue-3": "2.0.0-beta.91", "@tiptap/vue-3": "2.0.0-beta.91",
"@vue/apollo-option": "4.0.0-alpha.17",
"apollo-upload-client": "17.0.0", "apollo-upload-client": "17.0.0",
"browser-fs-access": "0.30.2", "browser-fs-access": "0.31.0",
"clipboard": "2.0.11", "clipboard": "2.0.11",
"codemirror": "6.0.1", "codemirror": "6.0.1",
"filesize": "9.0.11", "filesize": "9.0.11",
...@@ -69,31 +68,31 @@ ...@@ -69,31 +68,31 @@
"js-cookie": "3.0.1", "js-cookie": "3.0.1",
"jwt-decode": "3.1.2", "jwt-decode": "3.1.2",
"lodash-es": "4.17.21", "lodash-es": "4.17.21",
"luxon": "2.5.0", "luxon": "3.0.1",
"pinia": "2.0.14", "pinia": "2.0.17",
"pug": "3.0.2", "pug": "3.0.2",
"quasar": "2.7.5", "quasar": "2.7.5",
"tippy.js": "6.3.7", "tippy.js": "6.3.7",
"uuid": "8.3.2", "uuid": "8.3.2",
"v-network-graph": "0.6.3", "v-network-graph": "0.6.5",
"vue": "3.2.37", "vue": "3.2.37",
"vue-codemirror": "6.0.0", "vue-codemirror": "6.0.2",
"vue-i18n": "9.1.10", "vue-i18n": "9.1.10",
"vue-router": "4.1.1", "vue-router": "4.1.3",
"vuedraggable": "4.1.0", "vuedraggable": "4.1.0",
"zxcvbn": "4.4.2" "zxcvbn": "4.4.2"
}, },
"devDependencies": { "devDependencies": {
"@intlify/vite-plugin-vue-i18n": "3.4.0", "@intlify/vite-plugin-vue-i18n": "5.0.1",
"@quasar/app-vite": "1.0.5", "@quasar/app-vite": "1.0.5",
"@types/lodash": "4.14.182", "@types/lodash": "4.14.182",
"browserlist": "latest", "browserlist": "latest",
"eslint": "8.19.0", "eslint": "8.20.0",
"eslint-config-standard": "17.0.0", "eslint-config-standard": "17.0.0",
"eslint-plugin-import": "2.26.0", "eslint-plugin-import": "2.26.0",
"eslint-plugin-n": "15.2.4", "eslint-plugin-n": "15.2.4",
"eslint-plugin-promise": "6.0.0", "eslint-plugin-promise": "6.0.0",
"eslint-plugin-vue": "9.2.0" "eslint-plugin-vue": "9.3.0"
}, },
"engines": { "engines": {
"node": "^18 || ^16", "node": "^18 || ^16",
......
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="Gx4Hql1L2mKhvP9291EBWa" x1="19.53" x2="28.032" y1="12.426" y2="32.179" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32bdef"/><stop offset="1" stop-color="#1ea2e4"/></linearGradient><path fill="url(#Gx4Hql1L2mKhvP9291EBWa)" d="M31.19,22H27v-8c0-0.552-0.448-1-1-1h-4c-0.552,0-1,0.448-1,1v8h-4.19 c-0.72,0-1.08,0.87-0.571,1.379l6.701,6.701c0.586,0.586,1.536,0.586,2.121,0l6.701-6.701C32.271,22.87,31.91,22,31.19,22z"/><linearGradient id="Gx4Hql1L2mKhvP9291EBWb" x1="39.761" x2="43.605" y1="31.57" y2="42.462" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32bdef"/><stop offset="1" stop-color="#1ea2e4"/></linearGradient><path fill="url(#Gx4Hql1L2mKhvP9291EBWb)" d="M39,33v10l4.828-4.828c0.75-0.75,1.172-1.768,1.172-2.828V33c0-0.552-0.448-1-1-1h-4 C39.448,32,39,32.448,39,33z"/><linearGradient id="Gx4Hql1L2mKhvP9291EBWc" x1="9" x2="39" y1="40" y2="40" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0362b0"/><stop offset=".112" stop-color="#036abd"/><stop offset=".258" stop-color="#036fc5"/><stop offset=".5" stop-color="#0370c8"/><stop offset=".742" stop-color="#036fc5"/><stop offset=".888" stop-color="#036abd"/><stop offset="1" stop-color="#0362b0"/></linearGradient><rect width="30" height="6" x="9" y="37" fill="url(#Gx4Hql1L2mKhvP9291EBWc)"/><linearGradient id="Gx4Hql1L2mKhvP9291EBWd" x1="332.761" x2="336.605" y1="31.57" y2="42.462" gradientTransform="matrix(-1 0 0 1 341 0)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32bdef"/><stop offset="1" stop-color="#1ea2e4"/></linearGradient><path fill="url(#Gx4Hql1L2mKhvP9291EBWd)" d="M9,33v10l-4.828-4.828C3.421,37.421,3,36.404,3,35.343V33c0-0.552,0.448-1,1-1h4 C8.552,32,9,32.448,9,33z"/><linearGradient id="Gx4Hql1L2mKhvP9291EBWe" x1="23.174" x2="24.956" y1="4.081" y2="8.222" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32bdef"/><stop offset="1" stop-color="#1ea2e4"/></linearGradient><path fill="url(#Gx4Hql1L2mKhvP9291EBWe)" d="M26,7h-4c-0.552,0-1-0.448-1-1v0c0-0.552,0.448-1,1-1h4c0.552,0,1,0.448,1,1v0 C27,6.552,26.552,7,26,7z"/><linearGradient id="Gx4Hql1L2mKhvP9291EBWf" x1="23.174" x2="24.956" y1="8.081" y2="12.222" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32bdef"/><stop offset="1" stop-color="#1ea2e4"/></linearGradient><path fill="url(#Gx4Hql1L2mKhvP9291EBWf)" d="M26,11h-4c-0.552,0-1-0.448-1-1v0c0-0.552,0.448-1,1-1h4c0.552,0,1,0.448,1,1v0 C27,10.552,26.552,11,26,11z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="XED_QU6xiox5XDw0HU_eba" x1="20.958" x2="5.741" y1="26.758" y2="42.622" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#e5a505"/><stop offset=".01" stop-color="#e9a804"/><stop offset=".06" stop-color="#f4b102"/><stop offset=".129" stop-color="#fbb600"/><stop offset=".323" stop-color="#fdb700"/></linearGradient><path fill="url(#XED_QU6xiox5XDw0HU_eba)" d="M12,41.5c0-1.381,1.119-2.5,2.5-2.5c0.156,0,0.307,0.019,0.454,0.046l1.186-1.186 C16.058,37.586,16,37.301,16,37c0-1.657,1.343-3,3-3c0.301,0,0.586,0.058,0.86,0.14L24,30l-6-6L4.586,37.414 C4.211,37.789,4,38.298,4,38.828v1.343c0,0.53,0.211,1.039,0.586,1.414l1.828,1.828C6.789,43.789,7.298,44,7.828,44h1.343 c0.53,0,1.039-0.211,1.414-0.586l1.46-1.46C12.019,41.807,12,41.656,12,41.5z"/><linearGradient id="XED_QU6xiox5XDw0HU_ebb" x1="21.64" x2="36.971" y1="7.073" y2="29.362" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fede00"/><stop offset="1" stop-color="#ffd000"/></linearGradient><path fill="url(#XED_QU6xiox5XDw0HU_ebb)" d="M29.5,5C22.044,5,16,11.044,16,18.5S22.044,32,29.5,32S43,25.956,43,18.5S36.956,5,29.5,5z M33,19c-2.209,0-4-1.791-4-4s1.791-4,4-4s4,1.791,4,4S35.209,19,33,19z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="GCWVriy4rQhfclYQVzRmda" x1="9.812" x2="38.361" y1="9.812" y2="38.361" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f44f5a"/><stop offset=".443" stop-color="#ee3d4a"/><stop offset="1" stop-color="#e52030"/></linearGradient><path fill="url(#GCWVriy4rQhfclYQVzRmda)" d="M24,4C12.955,4,4,12.955,4,24s8.955,20,20,20s20-8.955,20-20C44,12.955,35.045,4,24,4z M24,38 c-7.732,0-14-6.268-14-14s6.268-14,14-14s14,6.268,14,14S31.732,38,24,38z"/><linearGradient id="GCWVriy4rQhfclYQVzRmdb" x1="6.821" x2="41.08" y1="6.321" y2="40.58" gradientTransform="translate(-.146 .354)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f44f5a"/><stop offset=".443" stop-color="#ee3d4a"/><stop offset="1" stop-color="#e52030"/></linearGradient><polygon fill="url(#GCWVriy4rQhfclYQVzRmdb)" points="13.371,38.871 9.129,34.629 34.629,9.129 38.871,13.371"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="80px" height="80px"><path fill="#fff" d="M6.5 37.5L6.5 2.5 24.793 2.5 33.5 11.207 33.5 37.5z"/><path fill="#4788c7" d="M24.586,3L33,11.414V37H7V3H24.586 M25,2H6v36h28V11L25,2L25,2z"/><path fill="#dff0fe" d="M24.5 11.5L24.5 2.5 24.793 2.5 33.5 11.207 33.5 11.5z"/><path fill="#4788c7" d="M25,3.414L32.586,11H25V3.414 M25,2h-1v10h10v-1L25,2L25,2z"/><path fill="none" stroke="#4788c7" stroke-miterlimit="10" d="M13.5 18.5L14.5 18.5 14.5 23M19.5 22.5L19.5 22.5c-.552 0-1-.448-1-1v-2c0-.552.448-1 1-1h0c.552 0 1 .448 1 1v2C20.5 22.052 20.052 22.5 19.5 22.5zM25.5 22.5L25.5 22.5c-.552 0-1-.448-1-1v-2c0-.552.448-1 1-1h0c.552 0 1 .448 1 1v2C26.5 22.052 26.052 22.5 25.5 22.5zM25.5 25.5L26.5 25.5 26.5 30M20.5 29.5L20.5 29.5c-.552 0-1-.448-1-1v-2c0-.552.448-1 1-1h0c.552 0 1 .448 1 1v2C21.5 29.052 21.052 29.5 20.5 29.5zM14.5 29.5L14.5 29.5c-.552 0-1-.448-1-1v-2c0-.552.448-1 1-1h0c.552 0 1 .448 1 1v2C15.5 29.052 15.052 29.5 14.5 29.5z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="80px" height="80px"><path fill="#fff" d="M2.5,37.5V8.985C2.5,5.409,5.409,2.5,8.985,2.5H37.5v35H2.5z"/><path fill="#4788c7" d="M37,3v34H3V8.985C3,5.685,5.685,3,8.985,3H37 M38,2H8.985C5.127,2,2,5.127,2,8.985V38h36V2L38,2z"/><path fill="#b6dcfe" d="M2.522,7.5c0.253-2.8,2.613-5,5.478-5h29.5v5H2.522z"/><path fill="#4788c7" d="M37,3v4H3.1C3.565,4.721,5.585,3,8,3H37 M38,2H8C4.686,2,2,4.686,2,8h36V2L38,2z"/><path fill="#b6dcfe" d="M13 14H15V16H13zM21 14H23V16H21zM25 14H27V16H25zM29 14H31V16H29zM9 18H11V20H9zM13 18H15V20H13zM21 18H23V20H21zM25 18H27V20H25zM29 18H31V20H29zM9 26H11V28H9zM13 26H15V28H13zM21 26H23V28H21zM17 14H19V16H17zM17 18H19V20H17zM9 22H11V24H9zM13 22H15V24H13zM21 22H23V24H21zM25 22H27V24H25zM29 22H31V24H29zM17 22H19V24H17zM17 26H19V28H17zM25 26H27V28H25zM3 34H37V37H3z"/><path fill="#98ccfd" d="M12,23.5C5.659,23.5,0.5,18.341,0.5,12S5.659,0.5,12,0.5S23.5,5.659,23.5,12S18.341,23.5,12,23.5z"/><path fill="#4788c7" d="M12,1c6.065,0,11,4.935,11,11s-4.935,11-11,11S1,18.065,1,12S5.935,1,12,1 M12,0 C5.373,0,0,5.373,0,12s5.373,12,12,12s12-5.373,12-12S18.627,0,12,0L12,0z"/><g><path fill="#fff" d="M12 3A9 9 0 1 0 12 21A9 9 0 1 0 12 3Z"/></g><path fill="none" stroke="#4788c7" stroke-linecap="round" stroke-miterlimit="10" d="M14.5 5.5L12 12 15.5 15.5"/><g><path fill="#4788c7" d="M12 10.667A1.333 1.333 0 1 0 12 13.333A1.333 1.333 0 1 0 12 10.667Z"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="445.34356" height="581.89215" viewBox="0 0 445.34356 581.89215" xmlns:xlink="http://www.w3.org/1999/xlink"><g><g><polygon points="128.92467 552.93374 138.51901 555.79104 154.10605 520.14411 139.9457 515.927 128.92467 552.93374" fill="#a0616a"/><path d="M140.8288,550.50975l-.00948-.0032-10.43597-2.44529-.00948-.0032-2.41704-.56239-8.30729,21.23876-.44694,1.13674,6.81666,2.03656,.56063-1.09837,2.56737-4.98059,.97997,6.0405,.21014,1.31629,18.06517,5.38934c1.074,.32018,2.18446,.18836,3.11913-.29834,1.0011-.49591,1.80138-1.39733,2.14092-2.55981,1.8212-3.33262-10.90289-15.51054-12.20874-18.11479l1.94052-6.50076-2.56557-.59145Z" fill="#2f2e41"/></g><path d="M190.38148,436.50921l-1.11692,2.88136-3.2501,8.36326-34.86355,89.0697-24.50891-6.02604c4.61286-33.65321,7.87162-54.49264,19.51731-72.39887,3.22505-4.96212,7.10411-9.70346,11.8281-14.43122,0,0-18.60782-.05679-40.46153-.50324-1.91794-.04031-3.86751-.07977-5.83331-.1347-18.09197-.42404-37.43081-1.12036-49.85358-2.27547-7.55893-.70184-12.77747-4.31265-16.19828-9.39219-9.79061-14.46282-5.12804-40.86067,.92335-45.88758l40.90617-16.26706,16.4964,29.65811,1.72604,3.09769,61.44197,4.30703c8.96469,.62842,16.58175,6.79079,19.06825,15.42664l4.17858,14.51258Z" fill="#2f2e41"/><path d="M27.34356,0l-3,491,421-73V0H27.34356ZM96.67706,416H29.34356v-67.33301H96.67706v67.33301Zm0-69.33301H29.34356v-67.33398H96.67706v67.33398Zm0-69.33398H29.34356v-67.33301H96.67706v67.33301Zm0-69.33301H29.34356v-67.3335H96.67706v67.3335Zm0-69.3335H29.34356V71.3335H96.67706v67.33301Zm0-69.33301H29.34356v-.00007C29.34356,32.1462,59.48977,2,96.67699,2h.00007V69.3335Zm69.33301,346.6665H98.67706v-67.33301h67.33301v67.33301Zm0-69.33301H98.67706v-67.33398h67.33301v67.33398Zm0-69.33398H98.67706v-67.33301h67.33301v67.33301Zm0-69.33301H98.67706v-67.3335h67.33301v67.3335Zm0-69.3335H98.67706v-.00007c0-37.18696,30.14598-67.33294,67.33294-67.33294h.00007v67.33301Zm0-69.33301H98.67706V2h67.33301V69.3335Zm69.3335,346.6665h-67.3335v-67.33301h67.3335v67.33301Zm0-69.33301h-67.3335v-67.33398h67.3335v67.33398Zm0-69.33398h-67.3335v-67.33301h67.3335v67.33301Zm0-69.33301h-67.3335v-67.3335h67.3335v67.3335Zm0-69.3335h-67.3335V71.3335h67.3335v67.33301Zm0-69.33301h-67.3335V2h67.3335V69.3335Zm69.3335,346.6665h-67.3335v-67.33301h67.3335v67.33301Zm0-69.33301h-67.3335v-67.33398h67.3335v67.33398Zm0-69.33398h-67.3335v-67.33301h67.3335v67.33301Zm0-69.33301h-67.3335v-67.3335h67.3335v67.3335Zm0-69.3335h-67.3335V71.3335h67.3335v67.33301Zm0-69.33301h-67.3335V2h67.3335V69.3335Zm69.33301,346.6665h-.00007c-37.18696,0-67.33294-30.14598-67.33294-67.33294v-.00007h67.33301v67.33301Zm0-69.33301h-67.33301v-67.33398h67.33301v67.33398Zm0-69.33398h-67.33301v-67.33301h67.33301v67.33301Zm0-69.33301h-67.33301v-67.3335h67.33301v67.3335Zm0-69.3335h-67.33301V71.3335h67.33301v67.33301Zm0-69.33301h-67.33301V2h67.33301V69.3335Zm2.00056,346.6665h-.00056v-67.33301h67.3335v.00007c0,37.18696-30.14598,67.33294-67.33294,67.33294Zm67.33294-69.33301h-67.3335v-67.33398h67.3335v67.33398Zm0-69.33398h-67.3335v-67.33301h67.3335v67.33301Zm0-69.33301h-67.3335v-67.3335h67.3335v67.3335Zm0-69.3335h-67.3335v-.00007c0-37.18696,30.14598-67.33294,67.33294-67.33294h.00056v67.33301Zm0-69.33301h-67.3335V2h67.3335V69.3335Z" fill="#f2f2f2"/><g><path d="M143.7444,210.76081l-8.21832,22.88252-13.72683-2.87098,9.54972-23.88366c-.26444-.73975-.42738-1.52969-.46665-2.35514-.21357-4.48952,3.25279-8.30215,7.74231-8.51572,4.48952-.21357,8.30215,3.25279,8.51572,7.74231,.1362,2.86317-1.22794,5.44608-3.39596,7.00067Z" fill="#a0616a"/><path d="M145.10987,216.968l-18.60773,50.03919-2.68918,7.22055c-2.42809,6.53008-9.11185,10.43936-16.00056,9.36736l-43.2153-6.75236-3.19397-19.81033,46.61723,4.10299,7.79503-20.44702,10.34248-27.14067,2.35996,.42301,13.21703,2.38503,3.375,.61223Z" fill="#1976d2"/></g><g><polygon points="64.23966 550.32766 72.28542 556.28428 99.08998 528.08542 87.21519 519.29401 64.23966 550.32766" fill="#a0616a"/><path d="M74.42615,558.9911l4.03844-5.4543-2.21077-1.43683-8.98752-5.86479-2.08328-1.35297-15.04861,17.1392-.80671,.9189,5.71269,4.22975,.9037-.83655,4.10272-3.80625-1.13075,6.00673-.2508,1.31996,15.14552,11.21394c.90448,.66969,1.98926,.92533,3.02758,.78863,1.11791-.13048,2.17554-.70563,2.89561-1.67815,2.84985-2.51225-4.96672-18.29191-5.30781-21.18729Z" fill="#2f2e41"/></g><path d="M237.34356,348c-76.09326,0-138-61.90625-138-138S161.2503,72,237.34356,72s138,61.90674,138,138-61.90674,138-138,138Zm0-274c-74.99072,0-100,58.00928-100,133s25.00928,139,100,139,136-61.00977,136-136-61.00928-136-136-136Z" fill="#3f3d56"/><path d="M302.72351,204.42767l-61.15649-61.15649c-.17749-.23376-.36621-.46167-.57953-.67499-1.28137-1.28137-2.96454-1.91388-4.64392-1.90161-1.67938-.01227-3.36255,.62024-4.64392,1.90161-.21332,.21332-.40204,.44122-.57953,.67499l-61.15649,61.15649c-2.53839,2.53839-2.53839,6.65393,0,9.19238,2.53839,2.53839,6.65399,2.53839,9.19238,0l50.68756-50.68756v107.5675c0,3.58984,2.91016,6.5,6.5,6.5s6.5-2.91016,6.5-6.5v-107.5675l50.68756,50.68756c2.53839,2.53839,6.65399,2.53839,9.19238,0,2.53839-2.53845,2.53839-6.65399,0-9.19238Z" fill="#1976d2"/><polygon points="78.34356 342 88.20183 374.1502 43.34356 379 59.34356 338 78.34356 342" fill="#a0616a"/><path d="M162.34356,466l-2.03003,2.32996-5.89996,6.76001-63.07001,71.91003-21-14c15.78003-30.08002,25.92999-48.57001,42.96997-61.45001,4.72003-3.57001,9.98004-6.71002,16.03003-9.54999,0,0-17.47998-6.38-37.88-14.23004-1.78998-.69-3.60999-1.38995-5.44-2.10999-16.87-6.54999-34.82001-13.77997-46.10999-19.08997-6.87-3.23004-10.54999-8.40002-12.03998-14.34003-4.29004-16.92999,9.06995-40.16998,16.46997-42.83997l44-1.39001,5.42999,33.5,.57001,3.5s16.12,7.78998,33.03998,15.57996c2.54004,1.16003,5.09003,2.33002,7.61005,3.47003,4,1.79999,7.89996,3.53998,11.51996,5.10999,.0827,.0359,.16553,.07177,.24848,.1076,10.49055,4.53143,17.02074,15.14447,16.2606,26.54657l-.67906,10.18587Z" fill="#2f2e41"/><path d="M89.32354,317.12c-1.07001,7.64001-1.32001,14.85999,.02002,18.88,.35629,1.06887,5.69178,8.88274,6,10,2.75418,9.98352-9,12-9,12l-45.09302,14.9706c-2.5966,.86206-4.78235-2.14578-3.12334-4.32137,3.69858-4.85026,7.07707-11.85798,6.83635-21.13922-.37512-14.46322-6.77592-37.46541-10.31388-49.1613-1.4559-4.81296-1.10486-9.98511,.98004-14.56086l5.03386-11.04787,7.32001-16.04999,2.35999-11.69,21,1,2.59003,9.51001,19.53998,29.82996c.78998,1.21002,1.31,2.56,1.53998,3.95001,.31,1.73999,.16003,3.56-.45001,5.27002-2.02997,5.69-4.09998,14.35999-5.23999,22.56Z" fill="#1976d2"/><g><path d="M211.82763,280.34608l-23.93249,4.28793-4.35699-13.32985,25.46422-3.63317c.50933-.59812,1.11277-1.13329,1.80867-1.57897,3.78491-2.42402,8.81827-1.32079,11.24229,2.46412,2.42402,3.78491,1.32079,8.81827-2.46412,11.24229-2.4138,1.5459-5.33291,1.65158-7.76158,.54765Z" fill="#a0616a"/><path d="M51.52867,280.18309l3.03433,3.25994c7.35817,7.90527,17.443,12.72244,28.21635,13.47794l40.24253,2.82209,74.74517-14.05192-4.55133-17.86856-70.65911,12.17742-71.02793,.18309Z" fill="#1976d2"/></g></g><g><path d="M.00957,228.79794c-.2166,9.32008,3.26648,18.34462,8.27021,26.27122,1.4867-4.62743,4.15943-8.83902,6.94682-12.85381-1.45518,8.60015,.13811,17.65509,4.2583,25.34008,.15437,.10796,.29947,.20475,.45585,.29233,4.07857,2.4534,9.24943,3.44712,13.66155,1.67514-9.11615-9.82665-10.2763-26.10791-2.6334-37.13004,2.16385-3.12756,4.96233-5.89365,6.30257-9.45289,2.11609-5.67895,.08194-11.93163-1.20127-17.85254-1.19989-5.51058-1.40992-12.06821,2.28708-16.10676-.7592-1.08524-2.20101-1.8355-3.56069-1.63943-2.27941,.32199-3.93858,2.30324-4.99531,4.34381-1.05672,2.04051-1.75366,4.30209-3.22635,6.06458-2.72722,3.29889-7.37161,4.07886-11.28556,5.81745C5.81066,207.78906,.23679,218.42727,.00957,228.79794Z" fill="#2f2e41"/><path d="M79.79675,223.46911c-.54166-1.89581,3.19176,9.69287,1.39023,9.90208-8.20531,.94132-19.46907,5.84176-25.184,8.12793-.25738-.02991-.51476-.05978-.76452-.09713-.61312-.06726-1.21114-.16439-1.80153-.27643-13.30712-2.51777-18.42436-22.95907-18.42436-23.14584,0-.91893,2.2055-26.15831,5.67986-26.53186,.90077-.09713,1.81669-.14943,2.74772-.14943h8.62162c1.8318-.38851,3.64848-.65746,5.41213-.78446h.01515c17.35676-1.3224,31.04235,9.2567,29.76311,22.12948-.00758,.01491-.02273,.02987-.03026,.04482-.37094,.55286-.69642,1.07584-.98407,1.5764-.28007,.47068-.51472,.91893-.71906,1.34479-1.58204,3.28732,2.48454,7.42377-.21196,8.19585-3.58634,1.02687,9.43221,34.7781-4.76294,34.7781,6.90484,1.96318,.74711-29.88452-.74711-35.11431Z" fill="#2f2e41"/><circle cx="64.30255" cy="222.86019" r="20.41274" fill="#a0616a"/><path d="M87.25214,213.75664c-.34063,.1793-.67369,.34364-1.01433,.50803-.35574,.17182-.71153,.34369-1.06727,.50055-6.83524,3.10054-12.39877,3.44418-15.77477-1.00858-.09083-2.33099-.61312-4.64707-1.55173-6.79876,.17413,2.38329-.47688,4.81141-1.80153,6.79876h-6.79736c-7.00175,4.64702-10.59724,5.13266-5.73766,20.3887,1.35496,4.25107,6.74242,28.98798,4.62298,32.65633-.61312-.06726-7.13605-27.16184-7.72644-27.27388-13.30712-2.51777-21.76218-24.44131-20.67975-25.68149,0-3.64592,.84778-7.0901,2.36924-10.1682,1.96804-3.99708,5.06397-7.35909,8.88653-9.66764,2.25568-1.09828,4.6325-1.98735,7.10015-2.6672,.06057-.01496,.11356-.02991,.17408-.04482,.74937-.20174,1.51389-.38851,2.27841-.54542,1.56688-.32873,3.15645-.57525,4.7839-.72468,.11356-.00748,.22707-.01496,.33306-.01496,.66612,0,1.28682,.18678,1.82422,.5006,.00758,0,.00758,.00743,.01515,.00743,.39363,.2316,.74179,.53795,1.01433,.90402,.4693,.59768,.74179,1.34479,.74179,2.15917h7.56946c.28007,0,.56013,.00748,.8402,.02239,10.54425,.41838,19.02205,8.71136,19.56707,19.0813,.02268,.3586,.03026,.70972,.03026,1.06837Z" fill="#2f2e41"/><path d="M56.06882,193.78318c0,7.42714-6.02089,13.44803-13.44803,13.44803-7.42714,0-13.44803-6.02089-13.44803-13.44803s6.02089-13.44803,13.44803-13.44803c7.42714,0,13.44803,6.02089,13.44803,13.44803Z" fill="#2f2e41"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" width="753" height="480.95111" viewBox="0 0 753 480.95111" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M372.67989,690.09163l-2-.03906a463.83342,463.83342,0,0,1,7.09961-66.28711c8.64844-46.88086,23.02929-77.66992,42.74316-91.51172l1.14844,1.63672C375.61934,566.22444,372.70333,688.85628,372.67989,690.09163Z" transform="translate(-223.5 -209.52444)" fill="#2f2e41"/><path d="M397.67989,689.61311l-2-.03906c.043-2.21484,1.293-54.41406,21.84277-68.8418l1.14844,1.63672C398.9504,636.21468,397.68965,689.08089,397.67989,689.61311Z" transform="translate(-223.5 -209.52444)" fill="#2f2e41"/><circle cx="209.54903" cy="314.54765" r="10.00001" fill="#1976d2"/><circle cx="204.59688" cy="400.54767" r="10" fill="#1976d2"/><path d="M393.01866,540.06667c1.87935,12.004-3.0189,22.7406-3.0189,22.7406s-7.9453-8.72583-9.82465-20.72986,3.01891-22.7406,3.01891-22.7406S391.1393,528.06264,393.01866,540.06667Z" transform="translate(-223.5 -209.52444)" fill="#2f2e41"/><path d="M425.70583,569.22047c-11.493,3.9422-22.91878.98963-22.91878.98963s7.20793-9.34412,18.70088-13.28632,22.9188-.98962,22.9188-.98962S437.19878,565.27828,425.70583,569.22047Z" transform="translate(-223.5 -209.52444)" fill="#2f2e41"/><path d="M426.07508,645.38161a31.13456,31.13456,0,0,1-16.06421.69366,28.37369,28.37369,0,0,1,29.172-10.00628A31.13431,31.13431,0,0,1,426.07508,645.38161Z" transform="translate(-223.5 -209.52444)" fill="#2f2e41"/><polygon points="606.671 467.453 593.531 467.453 587.28 416.768 606.674 416.769 606.671 467.453" fill="#9e616a"/><path d="M833.52257,689.71536l-42.3702-.00157v-.53592a16.49256,16.49256,0,0,1,16.49166-16.4914h.001l25.87827.001Z" transform="translate(-223.5 -209.52444)" fill="#2f2e41"/><polygon points="525.57 467.453 512.429 467.453 506.178 416.768 525.572 416.769 525.57 467.453" fill="#9e616a"/><path d="M752.421,689.71536l-42.3702-.00157v-.53592a16.49256,16.49256,0,0,1,16.49166-16.4914h.00105l25.87827.001Z" transform="translate(-223.5 -209.52444)" fill="#2f2e41"/><path d="M716.28867,393.14081l-18.19929-2.81212-5.87957,9.464-63.27234,16.12848.1713.87221a11.90415,11.90415,0,1,0,2.58765,12.30932L708.321,413.12185Z" transform="translate(-223.5 -209.52444)" fill="#9e616a"/><path d="M898.0541,381.87169a11.85506,11.85506,0,0,0-4.37548.841l.36312-.63329-80.44329-41.58032L802.631,358.229l83.63476,37.12523a11.89949,11.89949,0,1,0,11.78838-13.48252Z" transform="translate(-223.5 -209.52444)" fill="#9e616a"/><circle cx="736.07056" cy="267.73324" r="35.53801" transform="translate(130.38899 741.8887) rotate(-80.78252)" fill="#2f2e41"/><circle cx="512.26421" cy="70.76964" r="22.6708" fill="#a0616a"/><ellipse cx="512.57057" cy="48.4052" rx="24.50896" ry="14.70538" fill="#2f2e41"/><circle cx="515.02148" cy="22.67078" r="14.70537" fill="#2f2e41"/><path d="M718.91431,224.22982A14.70692,14.70692,0,0,1,732.08789,209.604a14.86918,14.86918,0,0,0-1.53183-.07951,14.70539,14.70539,0,0,0,0,29.41076,14.86917,14.86917,0,0,0,1.53183-.0795A14.70693,14.70693,0,0,1,718.91431,224.22982Z" transform="translate(-223.5 -209.52444)" fill="#2f2e41"/><path d="M723.97781,336.57587l1.82839-17.57652s24.80562-16.34735,33.23625-6.68558l50.38786,86.21281s31.323,11.13572,30.21575,53.658l-1.498,205.5398L802.631,661.61826,781.063,501.3681l-19.48674,166.026-41.35039-1.29476,3.72025-109.37556,19.71737-106.02732-.1889-35.18233-8.68389-14.19874s-15.90728-6.39038-16.35213-24.44983l-.34823-25.38571Z" transform="translate(-223.5 -209.52444)" fill="#2f2e41"/><path d="M749.98787,317.13922l.48927-8.23917s75.032,19.772,69.07954,33.90894-17.11318,18.60128-17.11318,18.60128l-43.155-17.11318Z" transform="translate(-223.5 -209.52444)" fill="#2f2e41"/><path d="M730.38083,337.64127l-5.64584-6.02061s-45.03187,63.189-31.41423,70.24916,25.0524,3.35351,25.0524,3.35351l22.22821-40.75684Z" transform="translate(-223.5 -209.52444)" fill="#2f2e41"/><path d="M640.24484,543.397,922.80569,486.993l-23.614-118.29636L616.63089,425.1006Z" transform="translate(-223.5 -209.52444)" fill="#fff"/><path d="M925.11811,488.5359,638.702,545.70941,614.31843,423.5577l286.41613-57.1735ZM641.78762,541.08463l278.70571-55.63437L897.64892,371.009,618.94321,426.64335Z" transform="translate(-223.5 -209.52444)" fill="#e4e4e4"/><rect x="649.55431" y="429.35966" width="233.18398" height="6.07982" transform="translate(-293.32159 -51.18186) rotate(-11.28883)" fill="#e4e4e4"/><rect x="654.1884" y="452.57456" width="233.18398" height="6.07982" transform="translate(-297.77636 -49.82557) rotate(-11.28883)" fill="#e4e4e4"/><rect x="658.84925" y="475.92356" width="233.18398" height="6.07982" transform="translate(-302.25687 -48.46145) rotate(-11.28883)" fill="#e4e4e4"/><path d="M770.62873,443.64449,762.631,445.241a2.24918,2.24918,0,0,1-2.643-1.76342L756.20675,424.535a2.24917,2.24917,0,0,1,1.76341-2.643l7.99772-1.59648a2.24918,2.24918,0,0,1,2.643,1.76342l3.78125,18.94256A2.24917,2.24917,0,0,1,770.62873,443.64449Z" transform="translate(-223.5 -209.52444)" fill="#1976d2"/><path d="M861.72707,449.59966l-7.99771,1.59648a2.24916,2.24916,0,0,1-2.643-1.76342l-3.78126-18.94255a2.24917,2.24917,0,0,1,1.76342-2.643l7.99772-1.59648a2.24917,2.24917,0,0,1,2.643,1.76342l3.78125,18.94255A2.24916,2.24916,0,0,1,861.72707,449.59966Z" transform="translate(-223.5 -209.52444)" fill="#1976d2"/><path d="M812.39337,483.72688l-7.99771,1.59648a2.24916,2.24916,0,0,1-2.643-1.76342l-3.78126-18.94255a2.24917,2.24917,0,0,1,1.76342-2.643l7.99772-1.59648a2.24917,2.24917,0,0,1,2.643,1.76342l3.78125,18.94256A2.24915,2.24915,0,0,1,812.39337,483.72688Z" transform="translate(-223.5 -209.52444)" fill="#1976d2"/><path d="M975.5,690.47556h-751a1,1,0,0,1,0-2h751a1,1,0,0,1,0,2Z" transform="translate(-223.5 -209.52444)" fill="#cacaca"/></svg>
\ No newline at end of file
import { boot } from 'quasar/wrappers' import { boot } from 'quasar/wrappers'
import { createApolloProvider } from '@vue/apollo-option'
import { ApolloClient, InMemoryCache } from '@apollo/client/core' import { ApolloClient, InMemoryCache } from '@apollo/client/core'
import { setContext } from '@apollo/client/link/context' import { setContext } from '@apollo/client/link/context'
import { createUploadLink } from 'apollo-upload-client' import { createUploadLink } from 'apollo-upload-client'
...@@ -41,16 +40,9 @@ export default boot(({ app }) => { ...@@ -41,16 +40,9 @@ export default boot(({ app }) => {
ssrForceFetchDelay: 100 ssrForceFetchDelay: 100
}) })
// Init Vue Apollo
const apolloProvider = createApolloProvider({
defaultClient: client
})
if (import.meta.env.SSR) { if (import.meta.env.SSR) {
global.APOLLO_CLIENT = client global.APOLLO_CLIENT = client
} else { } else {
window.APOLLO_CLIENT = client window.APOLLO_CLIENT = client
} }
app.use(apolloProvider)
}) })
<template lang="pug">
q-dialog(ref='dialogRef', @hide='onDialogHide', persistent)
q-card(style='min-width: 600px;')
q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-key-2.svg', left, size='sm')
span {{t(`admin.api.copyKeyTitle`)}}
q-card-section.card-negative
i18n-t(tag='span', keypath='admin.api.newKeyCopyWarn')
template(#bold)
strong {{t('admin.api.newKeyCopyWarnBold')}}
q-form.q-py-sm
q-item
blueprint-icon.self-start(icon='binary-file')
q-item-section
q-input(
type='textarea'
outlined
v-model='props.keyValue'
dense
hide-bottom-space
:label='t(`admin.api.key`)'
:aria-label='t(`admin.api.key`)'
autofocus
)
q-card-actions.card-actions
q-space
q-btn(
unelevated
:label='t(`common.actions.close`)'
color='primary'
padding='xs md'
@click='onDialogOK'
)
</template>
<script setup>
import { useI18n } from 'vue-i18n'
import { useDialogPluginComponent, useQuasar } from 'quasar'
// PROPS
const props = defineProps({
keyValue: {
type: String,
required: true
}
})
// EMITS
defineEmits([
...useDialogPluginComponent.emits
])
// QUASAR
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent()
const $q = useQuasar()
// I18N
const { t } = useI18n()
</script>
<template lang="pug">
q-dialog(ref='dialogRef', @hide='onDialogHide')
q-card(style='min-width: 650px;')
q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-plus-plus.svg', left, size='sm')
span {{t(`admin.api.newKeyTitle`)}}
q-form.q-py-sm(ref='createKeyForm', @submit='create')
q-item
blueprint-icon.self-start(icon='grand-master-key')
q-item-section
q-input(
outlined
v-model='state.keyName'
dense
:rules='keyNameValidation'
hide-bottom-space
:label='t(`admin.api.newKeyName`)'
:aria-label='t(`admin.api.newKeyName`)'
:hint='t(`admin.api.newKeyNameHint`)'
lazy-rules='ondemand'
autofocus
ref='iptName'
)
q-item
blueprint-icon.self-start(icon='schedule')
q-item-section
q-select(
outlined
:options='expirations'
v-model='state.keyExpiration'
multiple
map-options
option-value='value'
option-label='text'
emit-value
options-dense
dense
hide-bottom-space
:label='t(`admin.api.newKeyExpiration`)'
:aria-label='t(`admin.api.newKeyExpiration`)'
:hint='t(`admin.api.newKeyExpirationHint`)'
)
q-item
blueprint-icon.self-start(icon='access')
q-item-section
q-select(
outlined
:options='state.groups'
v-model='state.keyGroups'
multiple
map-options
emit-value
option-value='id'
option-label='name'
options-dense
dense
:rules='keyGroupsValidation'
hide-bottom-space
:label='t(`admin.api.permissionGroups`)'
:aria-label='t(`admin.api.permissionGroups`)'
:hint='t(`admin.api.newKeyGroupHint`)'
lazy-rules='ondemand'
:loading='state.loadingGroups'
)
template(v-slot:selected)
.text-caption(v-if='state.keyGroups.length > 1')
i18n-t(keypath='admin.api.groupsSelected')
template(#count)
strong {{ state.keyGroups.length }}
.text-caption(v-else-if='state.keyGroups.length === 1')
i18n-t(keypath='admin.api.groupSelected')
template(#group)
strong {{ selectedGroupName }}
span(v-else)
template(v-slot:option='{ itemProps, opt, selected, toggleOption }')
q-item(
v-bind='itemProps'
)
q-item-section(side)
q-checkbox(
size='sm'
:model-value='selected'
@update:model-value='toggleOption(opt)'
)
q-item-section
q-item-label {{opt.name}}
q-card-actions.card-actions
q-space
q-btn.acrylic-btn(
flat
:label='t(`common.actions.cancel`)'
color='grey'
padding='xs md'
@click='onDialogCancel'
)
q-btn(
unelevated
:label='t(`common.actions.create`)'
color='primary'
padding='xs md'
@click='create'
:loading='state.loading > 0'
)
</template>
<script setup>
import gql from 'graphql-tag'
import { cloneDeep, sampleSize } from 'lodash-es'
import { useI18n } from 'vue-i18n'
import { useDialogPluginComponent, useQuasar } from 'quasar'
import { computed, onMounted, reactive, ref } from 'vue'
import ApiKeyCopyDialog from './ApiKeyCopyDialog.vue'
// EMITS
defineEmits([
...useDialogPluginComponent.emits
])
// QUASAR
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
const $q = useQuasar()
// I18N
const { t } = useI18n()
// DATA
const state = reactive({
keyName: '',
keyExpiration: '90d',
keyGroups: [],
groups: [],
loadingGroups: false,
loading: false
})
const expirations = [
{ value: '30d', text: t('admin.api.expiration30d') },
{ value: '90d', text: t('admin.api.expiration90d') },
{ value: '180d', text: t('admin.api.expiration180d') },
{ value: '1y', text: t('admin.api.expiration1y') },
{ value: '3y', text: t('admin.api.expiration3y') }
]
// REFS
const createKeyForm = ref(null)
const iptName = ref(null)
// COMPUTED
const selectedGroupName = computed(() => {
return state.groups.filter(g => g.id === state.keyGroups[0])[0]?.name
})
// VALIDATION RULES
const keyNameValidation = [
val => val.length > 0 || t('admin.api.nameMissing'),
val => /^[^<>"]+$/.test(val) || t('admin.api.nameInvalidChars')
]
const keyGroupsValidation = [
val => val.length > 0 || t('admin.api.groupsMissing')
]
// METHODS
async function loadGroups () {
state.loading++
state.loadingGroups = true
const resp = await APOLLO_CLIENT.query({
query: gql`
query getGroupsForCreateApiKey {
groups {
id
name
}
}
`,
fetchPolicy: 'network-only'
})
state.groups = cloneDeep(resp?.data?.groups?.filter(g => g.id !== '10000000-0000-4000-8000-000000000001') ?? [])
state.loadingGroups = false
state.loading--
}
async function create () {
state.loading++
try {
const isFormValid = await createKeyForm.value.validate(true)
if (!isFormValid) {
throw new Error(t('admin.api.createInvalidData'))
}
const resp = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation createApiKey (
$name: String!
$expiration: String!
$groups: [UUID]!
) {
createApiKey (
name: $name
expiration: $expiration
groups: $groups
) {
operation {
succeeded
message
}
key
}
}
`,
variables: {
name: state.keyName,
expiration: state.keyExpiration,
groups: state.keyGroups
}
})
if (resp?.data?.createApiKey?.operation?.succeeded) {
$q.notify({
type: 'positive',
message: t('admin.api.createSuccess')
})
$q.dialog({
component: ApiKeyCopyDialog,
componentProps: {
keyValue: resp?.data?.createApiKey?.key || 'ERROR'
}
}).onDismiss(() => {
onDialogOK()
})
} else {
throw new Error(resp?.data?.createApiKey?.operation?.message || 'An unexpected error occured.')
}
} catch (err) {
$q.notify({
type: 'negative',
message: err.message
})
}
state.loading--
}
// MOUNTED
onMounted(loadGroups)
</script>
<template lang="pug">
q-dialog(ref='dialogRef', @hide='onDialogHide')
q-card(style='min-width: 350px; max-width: 450px;')
q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-unavailable.svg', left, size='sm')
span {{t(`admin.api.revokeConfirm`)}}
q-card-section
.text-body2
i18n-t(keypath='admin.api.revokeConfirmText')
template(#name)
strong {{apiKey.name}}
q-card-actions.card-actions
q-space
q-btn.acrylic-btn(
flat
:label='t(`common.actions.cancel`)'
color='grey'
padding='xs md'
@click='onDialogCancel'
)
q-btn(
unelevated
:label='t(`admin.api.revoke`)'
color='negative'
padding='xs md'
@click='confirm'
:loading='state.isLoading'
)
</template>
<script setup>
import gql from 'graphql-tag'
import { useI18n } from 'vue-i18n'
import { useDialogPluginComponent, useQuasar } from 'quasar'
import { reactive } from 'vue'
// PROPS
const props = defineProps({
apiKey: {
type: Object,
required: true
}
})
// EMITS
defineEmits([
...useDialogPluginComponent.emits
])
// QUASAR
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
const $q = useQuasar()
// I18N
const { t } = useI18n()
// DATA
const state = reactive({
isLoading: false
})
// METHODS
async function confirm () {
state.isLoading = true
try {
const resp = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation revokeApiKey ($id: UUID!) {
revokeApiKey (id: $id) {
operation {
succeeded
message
}
}
}
`,
variables: {
id: props.apiKey.id
}
})
if (resp?.data?.revokeApiKey?.operation?.succeeded) {
$q.notify({
type: 'positive',
message: t('admin.api.revokeSuccess')
})
onDialogOK()
} else {
throw new Error(resp?.data?.revokeApiKey?.operation?.message || 'An unexpected error occured.')
}
} catch (err) {
$q.notify({
type: 'negative',
message: err.message
})
}
state.isLoading = false
}
</script>
<template lang="pug">
q-dialog(ref='dialogRef', @hide='onDialogHide')
q-card(style='min-width: 350px; max-width: 450px;')
q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-downloading-updates.svg', left, size='sm')
span {{t(`admin.system.checkingForUpdates`)}}
q-card-section
.q-pa-md.text-center
img(src='/_assets/illustrations/undraw_going_up.svg', style='width: 150px;')
q-linear-progress(
indeterminate
size='lg'
rounded
)
.q-mt-sm.text-center.text-caption Fetching latest version info...
q-card-actions.card-actions
q-space
q-btn.acrylic-btn(
flat
:label='t(`common.actions.cancel`)'
color='grey'
padding='xs md'
@click='onDialogCancel'
)
q-btn(
v-if='state.canUpgrade'
unelevated
:label='t(`admin.system.upgrade`)'
color='primary'
padding='xs md'
@click='upgrade'
:loading='state.isLoading'
)
</template>
<script setup>
import gql from 'graphql-tag'
import { useI18n } from 'vue-i18n'
import { useDialogPluginComponent, useQuasar } from 'quasar'
import { reactive } from 'vue'
// EMITS
defineEmits([
...useDialogPluginComponent.emits
])
// QUASAR
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
const $q = useQuasar()
// I18N
const { t } = useI18n()
// DATA
const state = reactive({
isLoading: false,
canUpgrade: false
})
// METHODS
async function upgrade () {
state.isLoading = true
try {
const resp = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation deleteHook ($id: UUID!) {
deleteHook(id: $id) {
operation {
succeeded
message
}
}
}
`,
variables: {
id: 0
}
})
if (resp?.data?.deleteHook?.operation?.succeeded) {
$q.notify({
type: 'positive',
message: t('admin.webhooks.deleteSuccess')
})
onDialogOK()
} else {
throw new Error(resp?.data?.deleteHook?.operation?.message || 'An unexpected error occured.')
}
} catch (err) {
$q.notify({
type: 'negative',
message: err.message
})
}
state.isLoading = false
}
</script>
...@@ -152,6 +152,27 @@ body::-webkit-scrollbar-thumb { ...@@ -152,6 +152,27 @@ body::-webkit-scrollbar-thumb {
} }
} }
.card-negative {
display: flex;
align-items: center;
font-size: .9rem;
@at-root .body--light & {
background-color: $red-1;
background-image: radial-gradient(at bottom center, lighten($red-1, 2%), lighten($red-2, 2%));
border-bottom: 1px solid $red-3;
text-shadow: 0 0 4px #FFF;
color: $red-9;
}
@at-root .body--dark & {
background-color: $red-9;
background-image: radial-gradient(at bottom center, $red-7, $red-9);
border-bottom: 1px solid $red-7;
text-shadow: 0 0 4px darken($red-9, 10%);
color: #FFF;
}
}
.card-actions { .card-actions {
@at-root .body--light & { @at-root .body--light & {
background-color: #FAFAFA; background-color: #FAFAFA;
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
"admin.api.headerRevoke": "Revoke", "admin.api.headerRevoke": "Revoke",
"admin.api.newKeyButton": "New API Key", "admin.api.newKeyButton": "New API Key",
"admin.api.newKeyCopyWarn": "Copy the key shown below as {bold}", "admin.api.newKeyCopyWarn": "Copy the key shown below as {bold}",
"admin.api.newKeyCopyWarnBold": "it will NOT be shown again", "admin.api.newKeyCopyWarnBold": "it will NOT be shown again.",
"admin.api.newKeyExpiration": "Expiration", "admin.api.newKeyExpiration": "Expiration",
"admin.api.newKeyExpirationHint": "You can still revoke a key anytime regardless of the expiration.", "admin.api.newKeyExpirationHint": "You can still revoke a key anytime regardless of the expiration.",
"admin.api.newKeyFullAccess": "Full Access", "admin.api.newKeyFullAccess": "Full Access",
...@@ -1449,5 +1449,22 @@ ...@@ -1449,5 +1449,22 @@
"admin.auth.enabledHint": "Should this strategy be available to sites for login.", "admin.auth.enabledHint": "Should this strategy be available to sites for login.",
"admin.auth.vendor": "Vendor", "admin.auth.vendor": "Vendor",
"admin.auth.vendorWebsite": "Website", "admin.auth.vendorWebsite": "Website",
"admin.auth.status": "Status" "admin.auth.status": "Status",
"admin.system.checkingForUpdates": "Checking for Updates...",
"admin.system.upgrade": "Upgrade",
"admin.system.checkForUpdates": "Check",
"admin.system.checkUpdate": "Check / Upgrade",
"admin.api.none": "There are no API keys yet.",
"admin.api.groupsMissing": "You must select at least 1 group for this key.",
"admin.api.nameInvalidChars": "Key name has invalid characters.",
"admin.api.nameMissing": "Key name is missing.",
"admin.api.permissionGroups": "Group Permissions",
"admin.api.groupSelected": "Use {group} group permissions",
"admin.api.groupsSelected": "Use permissions from {count} groups",
"admin.api.createInvalidData": "Some fields are missing or have invalid data.",
"admin.api.copyKeyTitle": "Copy API Key",
"admin.api.key": "API Key",
"admin.api.createSuccess": "API Key created successfully.",
"admin.api.revoked": "Revoked",
"admin.api.revokedHint": "This key has been revoked and can no longer be used."
} }
<template lang="pug">
div
v-dialog(v-model='isShown', max-width='650', persistent)
v-card
.dialog-header.is-short
v-icon.mr-3(color='white') mdi-plus
span {{$t('admin.api.newKeyTitle')}}
v-card-text.pt-5
v-text-field(
outlined
prepend-icon='mdi-format-title'
v-model='name'
:label='$t(`admin.api.newKeyName`)'
persistent-hint
ref='keyNameInput'
:hint='$t(`admin.api.newKeyNameHint`)'
counter='255'
)
v-select.mt-3(
:items='expirations'
outlined
prepend-icon='mdi-clock'
v-model='expiration'
:label='$t(`admin.api.newKeyExpiration`)'
:hint='$t(`admin.api.newKeyExpirationHint`)'
persistent-hint
)
v-divider.mt-4
v-subheader.pl-2: strong.indigo--text {{$t('admin.api.newKeyPermissionScopes')}}
v-list.pl-8(nav)
v-list-item-group(v-model='fullAccess')
v-list-item(
:value='true'
active-class='indigo--text'
)
template(v-slot:default='{ active, toggle }')
v-list-item-action
v-checkbox(
:input-value='active'
:true-value='true'
color='indigo'
@click='toggle'
)
v-list-item-content
v-list-item-title {{$t('admin.api.newKeyFullAccess')}}
v-divider.mt-3
v-subheader.caption.indigo--text {{$t('admin.api.newKeyGroupPermissions')}}
v-list-item
v-select(
:disabled='fullAccess'
:items='groups'
item-text='name'
item-value='id'
outlined
color='indigo'
v-model='group'
:label='$t(`admin.api.newKeyGroup`)'
:hint='$t(`admin.api.newKeyGroupHint`)'
persistent-hint
)
v-card-chin
v-spacer
v-btn(text, @click='isShown = false', :disabled='loading') {{$t('common.actions.cancel')}}
v-btn.px-3(depressed, color='primary', @click='generate', :loading='loading')
v-icon(left) mdi-chevron-right
span {{$t('common.actions.generate')}}
v-dialog(
v-model='isCopyKeyDialogShown'
max-width='750'
persistent
overlay-color='blue darken-5'
overlay-opacity='.9'
)
v-card
v-toolbar(dense, flat, color='primary', dark) {{$t('admin.api.newKeyTitle')}}
v-card-text.pt-5
.body-2.text-center
i18next(tag='span', path='admin.api.newKeyCopyWarn')
strong(place='bold') {{$t('admin.api.newKeyCopyWarnBold')}}
v-textarea.mt-3(
ref='keyContentsIpt'
filled
no-resize
readonly
v-model='key'
:rows='10'
hide-details
)
v-card-chin
v-spacer
v-btn.px-3(depressed, dark, color='primary', @click='isCopyKeyDialogShown = false') {{$t('common.actions.close')}}
</template>
<script>
import _ from 'lodash'
import gql from 'graphql-tag'
import groupsQuery from 'gql/admin/users/users-query-groups.gql'
export default {
props: {
value: {
type: Boolean,
default: false
}
},
data() {
return {
loading: false,
name: '',
expiration: '1y',
fullAccess: true,
groups: [],
group: null,
isCopyKeyDialogShown: false,
key: ''
}
},
computed: {
isShown: {
get() { return this.value },
set(val) { this.$emit('input', val) }
},
expirations() {
return [
{ value: '30d', text: this.$t('admin.api.expiration30d') },
{ value: '90d', text: this.$t('admin.api.expiration90d') },
{ value: '180d', text: this.$t('admin.api.expiration180d') },
{ value: '1y', text: this.$t('admin.api.expiration1y') },
{ value: '3y', text: this.$t('admin.api.expiration3y') }
]
}
},
watch: {
value (newValue, oldValue) {
if (newValue) {
setTimeout(() => {
this.$refs.keyNameInput.focus()
}, 400)
}
}
},
methods: {
async generate () {
try {
if (_.trim(this.name).length < 2 || this.name.length > 255) {
throw new Error(this.$t('admin.api.newKeyNameError'))
} else if (!this.fullAccess && !this.group) {
throw new Error(this.$t('admin.api.newKeyGroupError'))
} else if (!this.fullAccess && this.group === 2) {
throw new Error(this.$t('admin.api.newKeyGuestGroupError'))
}
} catch (err) {
return this.$store.commit('showNotification', {
style: 'red',
message: err,
icon: 'alert'
})
}
this.loading = true
try {
const resp = await this.$apollo.mutate({
mutation: gql`
mutation ($name: String!, $expiration: String!, $fullAccess: Boolean!, $group: Int) {
authentication {
createApiKey (name: $name, expiration: $expiration, fullAccess: $fullAccess, group: $group) {
key
responseResult {
succeeded
errorCode
slug
message
}
}
}
}
`,
variables: {
name: this.name,
expiration: this.expiration,
fullAccess: (this.fullAccess === true),
group: this.group
},
watchLoading (isLoading) {
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-api-create')
}
})
if (_.get(resp, 'data.authentication.createApiKey.responseResult.succeeded', false)) {
this.$store.commit('showNotification', {
style: 'success',
message: this.$t('admin.api.newKeySuccess'),
icon: 'check'
})
this.name = ''
this.expiration = '1y'
this.fullAccess = true
this.group = null
this.isShown = false
this.$emit('refresh')
this.key = _.get(resp, 'data.authentication.createApiKey.key', '???')
this.isCopyKeyDialogShown = true
setTimeout(() => {
this.$refs.keyContentsIpt.$refs.input.select()
}, 400)
} else {
this.$store.commit('showNotification', {
style: 'red',
message: _.get(resp, 'data.authentication.createApiKey.responseResult.message', 'An unexpected error occurred.'),
icon: 'alert'
})
}
} catch (err) {
this.$store.commit('pushGraphError', err)
}
this.loading = false
}
},
apollo: {
groups: {
query: groupsQuery,
fetchPolicy: 'network-only',
update: (data) => data.groups.list,
watchLoading (isLoading) {
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-api-groups-refresh')
}
}
}
}
</script>
...@@ -162,8 +162,9 @@ q-page.admin-mail ...@@ -162,8 +162,9 @@ q-page.admin-mail
.text-subtitle1 {{state.strategy.strategy.title}} .text-subtitle1 {{state.strategy.strategy.title}}
q-img.q-mt-sm.rounded-borders( q-img.q-mt-sm.rounded-borders(
:src='state.strategy.strategy.logo' :src='state.strategy.strategy.logo'
fit='cover' fit='contain'
no-spinner no-spinner
style='height: 100px;'
) )
.text-body2.q-mt-md {{state.strategy.strategy.description}} .text-body2.q-mt-md {{state.strategy.strategy.description}}
q-separator.q-mb-sm(inset) q-separator.q-mb-sm(inset)
......
...@@ -97,12 +97,13 @@ q-page.admin-dashboard ...@@ -97,12 +97,13 @@ q-page.admin-dashboard
template(#action) template(#action)
q-btn( q-btn(
flat flat
label='Check' :label='t(`admin.system.checkForUpdates`)'
@click='checkForUpdates'
) )
q-separator.q-mx-sm(vertical, dark) q-separator.q-mx-sm(vertical, dark)
q-btn( q-btn(
flat flat
label='System Info' :label='t(`admin.system.title`)'
to='/_admin/system' to='/_admin/system'
) )
.col-12 .col-12
...@@ -224,6 +225,7 @@ import { useAdminStore } from '../stores/admin' ...@@ -224,6 +225,7 @@ import { useAdminStore } from '../stores/admin'
// COMPONENTS // COMPONENTS
import CheckUpdateDialog from '../components/CheckUpdateDialog.vue'
import SiteCreateDialog from '../components/SiteCreateDialog.vue' import SiteCreateDialog from '../components/SiteCreateDialog.vue'
import UserCreateDialog from '../components/UserCreateDialog.vue' import UserCreateDialog from '../components/UserCreateDialog.vue'
...@@ -265,6 +267,11 @@ function newUser () { ...@@ -265,6 +267,11 @@ function newUser () {
router.push('/_admin/users') router.push('/_admin/users')
}) })
} }
function checkForUpdates () {
$q.dialog({
component: CheckUpdateDialog
})
}
</script> </script>
......
...@@ -77,6 +77,10 @@ q-page.admin-flags ...@@ -77,6 +77,10 @@ q-page.admin-flags
unchecked-icon='las la-times' unchecked-icon='las la-times'
:aria-label='t(`admin.flags.hidedonatebtn.label`)' :aria-label='t(`admin.flags.hidedonatebtn.label`)'
) )
.col-12.col-lg-5.gt-md
.q-pa-md.text-center
img(src='/_assets/illustrations/undraw_settings.svg', style='width: 80%;')
</template> </template>
<script setup> <script setup>
......
...@@ -117,6 +117,9 @@ q-page.admin-locale ...@@ -117,6 +117,9 @@ q-page.admin-locale
:aria-label='lc.name' :aria-label='lc.name'
) )
.q-pa-md.text-center.gt-md(v-else)
img(src='/_assets/illustrations/undraw_world.svg', style='width: 80%;')
//- q-separator.q-my-sm(inset) //- q-separator.q-my-sm(inset)
//- q-item //- q-item
//- blueprint-icon(icon='test-passed') //- blueprint-icon(icon='test-passed')
......
...@@ -54,7 +54,16 @@ q-page.admin-system ...@@ -54,7 +54,16 @@ q-page.admin-system
q-item-label {{ t('admin.system.latestVersion') }} q-item-label {{ t('admin.system.latestVersion') }}
q-item-label(caption) {{t('admin.system.latestVersionHint')}} q-item-label(caption) {{t('admin.system.latestVersionHint')}}
q-item-section q-item-section
q-item-label.dark-value(caption) {{ state.info.latestVersion }} .row.q-col-gutter-sm
.col
.dark-value(caption) {{ state.info.latestVersion }}
.col-auto
q-btn.acrylic-btn(
flat
color='purple'
@click='checkForUpdates'
:label='t(`admin.system.checkUpdate`)'
)
//- ----------------------- //- -----------------------
//- CLIENT //- CLIENT
...@@ -234,6 +243,8 @@ import { useMeta, useQuasar } from 'quasar' ...@@ -234,6 +243,8 @@ import { useMeta, useQuasar } from 'quasar'
import { computed, onMounted, reactive, ref, watch } from 'vue' import { computed, onMounted, reactive, ref, watch } from 'vue'
import ClipboardJS from 'clipboard' import ClipboardJS from 'clipboard'
import CheckUpdateDialog from '../components/CheckUpdateDialog.vue'
// QUASAR // QUASAR
const $q = useQuasar() const $q = useQuasar()
...@@ -340,6 +351,12 @@ async function load () { ...@@ -340,6 +351,12 @@ async function load () {
state.loading-- state.loading--
} }
function checkForUpdates () {
$q.dialog({
component: CheckUpdateDialog
})
}
// async function performUpgrade () { // async function performUpgrade () {
// state.isUpgrading = true // state.isUpgrading = true
// state.isUpgradingStarted = false // state.isUpgradingStarted = false
......
This diff was suppressed by a .gitattributes entry.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment