Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wiki-js
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
wiki-js
Commits
5a4a9df4
Unverified
Commit
5a4a9df4
authored
Jun 18, 2022
by
Nicolas Giard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(admin): migrate webhooks to vue 3 composable
parent
cc506a08
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
373 additions
and
153 deletions
+373
-153
hooks.js
server/graph/resolvers/hooks.js
+85
-0
hooks.graphql
server/graph/schemas/hooks.graphql
+70
-0
hooks.js
server/models/hooks.js
+44
-0
WebhookDeleteDialog.vue
ux/src/components/WebhookDeleteDialog.vue
+71
-57
WebhookEditDialog.vue
ux/src/components/WebhookEditDialog.vue
+0
-0
AdminLocale.vue
ux/src/pages/AdminLocale.vue
+2
-2
AdminWebhooks.vue
ux/src/pages/AdminWebhooks.vue
+100
-93
routes.js
ux/src/router/routes.js
+1
-1
No files found.
server/graph/resolvers/hooks.js
0 → 100644
View file @
5a4a9df4
const
graphHelper
=
require
(
'../../helpers/graph'
)
const
_
=
require
(
'lodash'
)
/* global WIKI */
module
.
exports
=
{
Query
:
{
async
hooks
()
{
return
WIKI
.
models
.
hooks
.
query
().
orderBy
(
'name'
)
},
async
hookById
(
obj
,
args
)
{
return
WIKI
.
models
.
hooks
.
query
().
findById
(
args
.
id
)
}
},
Mutation
:
{
/**
* CREATE HOOK
*/
async
createHook
(
obj
,
args
)
{
try
{
// -> Validate inputs
if
(
!
args
.
name
||
args
.
name
.
length
<
1
)
{
throw
WIKI
.
ERROR
(
new
Error
(
'Invalid Hook Name'
),
'HookCreateInvalidName'
)
}
if
(
!
args
.
events
||
args
.
events
.
length
<
1
)
{
throw
WIKI
.
ERROR
(
new
Error
(
'Invalid Hook Events'
),
'HookCreateInvalidEvents'
)
}
if
(
!
args
.
url
||
args
.
url
.
length
<
8
||
!
args
.
url
.
startsWith
(
'http'
))
{
throw
WIKI
.
ERROR
(
new
Error
(
'Invalid Hook URL'
),
'HookCreateInvalidURL'
)
}
// -> Create hook
const
newHook
=
await
WIKI
.
models
.
hooks
.
createHook
(
args
)
return
{
operation
:
graphHelper
.
generateSuccess
(
'Hook created successfully'
),
hook
:
newHook
}
}
catch
(
err
)
{
return
graphHelper
.
generateError
(
err
)
}
},
/**
* UPDATE HOOK
*/
async
updateHook
(
obj
,
args
)
{
try
{
// -> Load hook
const
hook
=
await
WIKI
.
models
.
hooks
.
query
().
findById
(
args
.
id
)
if
(
!
hook
)
{
throw
WIKI
.
ERROR
(
new
Error
(
'Invalid Hook ID'
),
'HookInvalidId'
)
}
// -> Check for bad input
if
(
_
.
has
(
args
.
patch
,
'name'
)
&&
args
.
patch
.
name
.
length
<
1
)
{
throw
WIKI
.
ERROR
(
new
Error
(
'Invalid Hook Name'
),
'HookCreateInvalidName'
)
}
if
(
_
.
has
(
args
.
patch
,
'events'
)
&&
args
.
patch
.
events
.
length
<
1
)
{
throw
WIKI
.
ERROR
(
new
Error
(
'Invalid Hook Events'
),
'HookCreateInvalidEvents'
)
}
if
(
_
.
has
(
args
.
patch
,
'url'
)
&&
(
_
.
trim
(
args
.
patch
.
url
).
length
<
8
||
!
args
.
patch
.
url
.
startsWith
(
'http'
)))
{
throw
WIKI
.
ERROR
(
new
Error
(
'URL is invalid.'
),
'HookInvalidURL'
)
}
// -> Update hook
await
WIKI
.
models
.
hooks
.
query
().
findById
(
args
.
id
).
patch
(
args
.
patch
)
return
{
operation
:
graphHelper
.
generateSuccess
(
'Hook updated successfully'
)
}
}
catch
(
err
)
{
return
graphHelper
.
generateError
(
err
)
}
},
/**
* DELETE HOOK
*/
async
deleteHook
(
obj
,
args
)
{
try
{
await
WIKI
.
models
.
hooks
.
deleteHook
(
args
.
id
)
return
{
operation
:
graphHelper
.
generateSuccess
(
'Hook deleted successfully'
)
}
}
catch
(
err
)
{
return
graphHelper
.
generateError
(
err
)
}
}
}
}
server/graph/schemas/hooks.graphql
0 → 100644
View file @
5a4a9df4
# ===============================================
# WEBHOOKS
# ===============================================
extend
type
Query
{
hooks
:
[
Hook
]
hookById
(
id
:
UUID
!
):
Hook
}
extend
type
Mutation
{
createHook
(
name
:
String
!
events
:
[
String
]!
url
:
String
!
includeMetadata
:
Boolean
!
includeContent
:
Boolean
!
acceptUntrusted
:
Boolean
!
authHeader
:
String
):
HookCreateResponse
updateHook
(
id
:
UUID
!
patch
:
HookUpdateInput
!
):
DefaultResponse
deleteHook
(
id
:
UUID
!
):
DefaultResponse
}
# -----------------------------------------------
# TYPES
# -----------------------------------------------
type
Hook
{
id
:
UUID
name
:
String
events
:
[
String
]
url
:
String
includeMetadata
:
Boolean
includeContent
:
Boolean
acceptUntrusted
:
Boolean
authHeader
:
String
state
:
HookState
lastErrorMessage
:
String
}
input
HookUpdateInput
{
name
:
String
events
:
[
String
]
url
:
String
includeMetadata
:
Boolean
includeContent
:
Boolean
acceptUntrusted
:
Boolean
authHeader
:
String
}
enum
HookState
{
pending
error
success
}
type
HookCreateResponse
{
operation
:
Operation
hook
:
Hook
}
server/models/hooks.js
0 → 100644
View file @
5a4a9df4
const
Model
=
require
(
'objection'
).
Model
/* global WIKI */
/**
* Hook model
*/
module
.
exports
=
class
Hook
extends
Model
{
static
get
tableName
()
{
return
'hooks'
}
static
get
jsonAttributes
()
{
return
[
'events'
]
}
$beforeUpdate
()
{
this
.
updatedAt
=
new
Date
()
}
static
async
createHook
(
data
)
{
return
WIKI
.
models
.
hooks
.
query
().
insertAndFetch
({
name
:
data
.
name
,
events
:
data
.
events
,
url
:
data
.
url
,
includeMetadata
:
data
.
includeMetadata
,
includeContent
:
data
.
includeContent
,
acceptUntrusted
:
data
.
acceptUntrusted
,
authHeader
:
data
.
authHeader
,
state
:
'pending'
,
lastErrorMessage
:
null
})
}
static
async
updateHook
(
id
,
patch
)
{
return
WIKI
.
models
.
hooks
.
query
().
findById
(
id
).
patch
({
...
patch
,
state
:
'pending'
,
lastErrorMessage
:
null
})
}
static
async
deleteHook
(
id
)
{
return
WIKI
.
models
.
hooks
.
query
().
deleteById
(
id
)
}
}
ux/src/components/WebhookDeleteDialog.vue
View file @
5a4a9df4
<
template
lang=
"pug"
>
q-dialog(ref='dialog', @hide='onDialogHide')
q-dialog(ref='dialog
Ref
', @hide='onDialogHide')
q-card(style='min-width: 350px; max-width: 450px;')
q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-delete-bin.svg', left, size='sm')
span
{{
$
t
(
`admin.webhooks.delete`
)
}}
span
{{
t
(
`admin.webhooks.delete`
)
}}
q-card-section
.text-body2
i18n-t(keypath='admin.webhooks.deleteConfirm')
template(v-slot:name)
strong
{{
hook
.
name
}}
.text-body2.q-mt-md
strong.text-negative
{{
$
t
(
`admin.webhooks.deleteConfirmWarn`
)
}}
strong.text-negative
{{
t
(
`admin.webhooks.deleteConfirmWarn`
)
}}
q-card-actions.card-actions
q-space
q-btn.acrylic-btn(
flat
:label='
$
t(`common.actions.cancel`)'
:label='t(`common.actions.cancel`)'
color='grey'
padding='xs md'
@click='
hide
'
@click='
onDialogCancel
'
)
q-btn(
unelevated
:label='
$
t(`common.actions.delete`)'
:label='t(`common.actions.delete`)'
color='negative'
padding='xs md'
@click='confirm'
:loading='state.isLoading'
)
</
template
>
<
script
>
<
script
setup
>
import
gql
from
'graphql-tag'
import
{
useI18n
}
from
'vue-i18n'
import
{
useDialogPluginComponent
,
useQuasar
}
from
'quasar'
import
{
reactive
}
from
'vue'
export
default
{
props
:
{
hook
:
{
type
:
Object
}
},
emits
:
[
'ok'
,
'hide'
],
data
()
{
return
{
}
},
methods
:
{
show
()
{
this
.
$refs
.
dialog
.
show
()
},
hide
()
{
this
.
$refs
.
dialog
.
hide
()
},
onDialogHide
()
{
this
.
$emit
(
'hide'
)
},
async
confirm
()
{
try
{
const
resp
=
await
this
.
$apollo
.
mutate
({
mutation
:
gql
`
mutation deleteHook ($id: UUID!) {
deleteHook(id: $id) {
status {
succeeded
message
}
}
// PROPS
const
props
=
defineProps
({
hook
:
{
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 deleteHook ($id: UUID!) {
deleteHook(id: $id) {
operation {
succeeded
message
}
`
,
variables
:
{
id
:
this
.
hook
.
id
}
})
if
(
resp
?.
data
?.
deleteHook
?.
status
?.
succeeded
)
{
this
.
$q
.
notify
({
type
:
'positive'
,
message
:
this
.
$t
(
'admin.webhooks.deleteSuccess'
)
})
this
.
$emit
(
'ok'
)
this
.
hide
()
}
else
{
throw
new
Error
(
resp
?.
data
?.
deleteHook
?.
status
?.
message
||
'An unexpected error occured.'
)
}
}
catch
(
err
)
{
this
.
$q
.
notify
({
type
:
'negative'
,
message
:
err
.
message
})
`
,
variables
:
{
id
:
props
.
hook
.
id
}
})
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
>
ux/src/components/WebhookEditDialog.vue
View file @
5a4a9df4
This diff is collapsed.
Click to expand it.
ux/src/pages/AdminLocale.vue
View file @
5a4a9df4
...
...
@@ -41,7 +41,7 @@ q-page.admin-locale
)
q-separator(inset)
.row.q-pa-md.q-col-gutter-md
.col-7
.col-
12.col-lg-
7
//- -----------------------
//- Locale Options
//- -----------------------
...
...
@@ -89,7 +89,7 @@ q-page.admin-locale
span
{{
t
(
'admin.locale.namespacingPrefixWarning.title'
,
{
langCode
:
state
.
selectedLocale
}
)
}}
.
text
-
caption
.
text
-
yellow
-
1
{{
t
(
'admin.locale.namespacingPrefixWarning.subtitle'
)
}}
.
col
-
5
.
col
-
12
.
col
-
lg
-
5
//- -----------------------
//- Namespacing
//- -----------------------
...
...
ux/src/pages/AdminWebhooks.vue
View file @
5a4a9df4
...
...
@@ -4,14 +4,9 @@ q-page.admin-webhooks
.col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-lightning-bolt.svg')
.col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft
{{
$
t
(
'admin.webhooks.title'
)
}}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s
{{
$
t
(
'admin.webhooks.subtitle'
)
}}
.text-h5.text-primary.animated.fadeInLeft
{{
t
(
'admin.webhooks.title'
)
}}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s
{{
t
(
'admin.webhooks.subtitle'
)
}}
.col-auto
q-spinner-tail.q-mr-md(
v-show='loading'
color='accent'
size='sm'
)
q-btn.q-mr-sm.acrylic-btn(
icon='las la-question-circle'
flat
...
...
@@ -24,19 +19,19 @@ q-page.admin-webhooks
icon='las la-redo-alt'
flat
color='secondary'
:loading='loading > 0'
:loading='
state.
loading > 0'
@click='load'
)
q-btn(
unelevated
icon='las la-plus'
:label='
$
t(`admin.webhooks.new`)'
:label='t(`admin.webhooks.new`)'
color='primary'
@click='createHook'
)
q-separator(inset)
.row.q-pa-md.q-col-gutter-md
.col-12(v-if='hooks.length < 1')
.col-12(v-if='
state.
hooks.length < 1')
q-card.rounded-borders(
flat
:class='$q.dark.isActive ? `bg-dark-5 text-white` : `bg-grey-3 text-dark`'
...
...
@@ -44,11 +39,11 @@ q-page.admin-webhooks
q-card-section.items-center(horizontal)
q-card-section.col-auto.q-pr-none
q-icon(name='las la-info-circle', size='sm')
q-card-section.text-caption
{{
$
t
(
'admin.webhooks.none'
)
}}
q-card-section.text-caption
{{
t
(
'admin.webhooks.none'
)
}}
.col-12(v-else)
q-card
q-list(separator)
q-item(v-for='hook of hooks', :key='hook.id')
q-item(v-for='hook of
state.
hooks', :key='hook.id')
q-item-section(side)
q-icon(name='las la-bolt', color='primary')
q-item-section
...
...
@@ -60,23 +55,23 @@ q-page.admin-webhooks
color='indigo'
size='xs'
)
.text-caption.text-indigo
{{
$
t
(
'admin.webhooks.statePending'
)
}}
q-tooltip(anchor='center left', self='center right')
{{
$
t
(
'admin.webhooks.statePendingHint'
)
}}
.text-caption.text-indigo
{{
t
(
'admin.webhooks.statePending'
)
}}
q-tooltip(anchor='center left', self='center right')
{{
t
(
'admin.webhooks.statePendingHint'
)
}}
template(v-else-if='hook.state === `success`')
q-spinner-infinity.q-mr-sm(
color='positive'
size='xs'
)
.text-caption.text-positive
{{
$
t
(
'admin.webhooks.stateSuccess'
)
}}
q-tooltip(anchor='center left', self='center right')
{{
$
t
(
'admin.webhooks.stateSuccessHint'
)
}}
.text-caption.text-positive
{{
t
(
'admin.webhooks.stateSuccess'
)
}}
q-tooltip(anchor='center left', self='center right')
{{
t
(
'admin.webhooks.stateSuccessHint'
)
}}
template(v-else-if='hook.state === `error`')
q-icon.q-mr-sm(
color='negative'
size='xs'
name='las la-exclamation-triangle'
)
.text-caption.text-negative
{{
$
t
(
'admin.webhooks.stateError'
)
}}
q-tooltip(anchor='center left', self='center right')
{{
$
t
(
'admin.webhooks.stateErrorHint'
)
}}
.text-caption.text-negative
{{
t
(
'admin.webhooks.stateError'
)
}}
q-tooltip(anchor='center left', self='center right')
{{
t
(
'admin.webhooks.stateErrorHint'
)
}}
q-separator.q-ml-md(vertical)
q-item-section(side, style='flex-direction: row; align-items: center;')
q-btn.acrylic-btn.q-mr-sm(
...
...
@@ -96,88 +91,100 @@ q-page.admin-webhooks
</
template
>
<
script
>
<
script
setup
>
import
cloneDeep
from
'lodash/cloneDeep'
import
gql
from
'graphql-tag'
import
{
createMetaMixin
,
QSpinnerClock
,
QSpinnerInfinity
}
from
'quasar'
import
WebhookDeleteDialog
from
'../components/WebhookDeleteDialog.vue'
import
WebhookEditDialog
from
'../components/WebhookEditDialog.vue'
export
default
{
components
:
{
QSpinnerClock
,
QSpinnerInfinity
},
mixins
:
[
createMetaMixin
(
function
()
{
return
{
title
:
this
.
$t
(
'admin.webhooks.title'
)
import
{
useI18n
}
from
'vue-i18n'
import
{
useMeta
,
useQuasar
}
from
'quasar'
import
{
onMounted
,
reactive
}
from
'vue'
import
WebhookEditDialog
from
'src/components/WebhookEditDialog.vue'
import
WebhookDeleteDialog
from
'src/components/WebhookDeleteDialog.vue'
// QUASAR
const
$q
=
useQuasar
()
// I18N
const
{
t
}
=
useI18n
()
// META
useMeta
({
title
:
t
(
'admin.webhooks.title'
)
})
// DATA
const
state
=
reactive
({
hooks
:
[],
loading
:
0
})
// METHODS
async
function
load
()
{
state
.
loading
++
$q
.
loading
.
show
()
const
resp
=
await
APOLLO_CLIENT
.
query
({
query
:
gql
`
query getHooks {
hooks {
id
name
url
state
}
}
})
],
data
()
{
return
{
hooks
:
[],
loading
:
0
`
,
fetchPolicy
:
'network-only'
})
state
.
hooks
=
cloneDeep
(
resp
?.
data
?.
hooks
)
??
[]
$q
.
loading
.
hide
()
state
.
loading
--
}
function
createHook
()
{
$q
.
dialog
({
component
:
WebhookEditDialog
,
componentProps
:
{
hookId
:
null
}
},
mounted
()
{
this
.
load
()
},
methods
:
{
async
load
()
{
this
.
loading
++
this
.
$q
.
loading
.
show
()
const
resp
=
await
this
.
$apollo
.
query
({
query
:
gql
`
query getHooks {
hooks {
id
name
url
state
}
}
`
,
fetchPolicy
:
'network-only'
})
this
.
config
=
cloneDeep
(
resp
?.
data
?.
hooks
)
??
[]
this
.
$q
.
loading
.
hide
()
this
.
loading
--
},
createHook
()
{
this
.
$q
.
dialog
({
component
:
WebhookEditDialog
,
componentProps
:
{
hookId
:
null
}
}).
onOk
(()
=>
{
this
.
load
()
})
},
editHook
(
id
)
{
this
.
$q
.
dialog
({
component
:
WebhookEditDialog
,
componentProps
:
{
hookId
:
id
}
}).
onOk
(()
=>
{
this
.
load
()
})
},
deleteHook
(
hook
)
{
this
.
$q
.
dialog
({
component
:
WebhookDeleteDialog
,
componentProps
:
{
hook
}
}).
onOk
(()
=>
{
this
.
load
()
})
}).
onOk
(()
=>
{
load
()
})
}
function
editHook
(
id
)
{
$q
.
dialog
({
component
:
WebhookEditDialog
,
componentProps
:
{
hookId
:
id
}
}
}).
onOk
(()
=>
{
load
()
})
}
function
deleteHook
(
hook
)
{
$q
.
dialog
({
component
:
WebhookDeleteDialog
,
componentProps
:
{
hook
}
}).
onOk
(()
=>
{
load
()
})
}
// MOUNTED
onMounted
(()
=>
{
load
()
})
</
script
>
<
style
lang=
'scss'
>
...
...
ux/src/router/routes.js
View file @
5a4a9df4
...
...
@@ -50,7 +50,7 @@ const routes = [
{
path
:
'security'
,
component
:
()
=>
import
(
'../pages/AdminSecurity.vue'
)
},
{
path
:
'system'
,
component
:
()
=>
import
(
'../pages/AdminSystem.vue'
)
},
// { path: 'utilities', component: () => import('../pages/AdminUtilities.vue') },
//
{ path: 'webhooks', component: () => import('../pages/AdminWebhooks.vue') },
{
path
:
'webhooks'
,
component
:
()
=>
import
(
'../pages/AdminWebhooks.vue'
)
},
{
path
:
'flags'
,
component
:
()
=>
import
(
'../pages/AdminFlags.vue'
)
}
]
},
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment