Add warning for unsaved changes before leaving edit resource pages
This commit is contained in:
@@ -58,6 +58,7 @@
|
||||
data-test-id="file-logo-upload"
|
||||
accept="image/*"
|
||||
:placeholder="$t('logo.placeholder')"
|
||||
@change="$emit('change-detected')"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ export default {
|
||||
this.$set(this.members, i, { ...this.members[i], label: label, dirty: true })
|
||||
}
|
||||
|
||||
this.memberUsers.push({ value: member.userID, label })
|
||||
this.memberUsers.push(member)
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
</b-container>
|
||||
</template>
|
||||
<script>
|
||||
import { isEqual, cloneDeep } from 'lodash'
|
||||
import editorHelpers from 'corteza-webapp-admin/src/mixins/editorHelpers'
|
||||
import CWorkflowEditorInfo from 'corteza-webapp-admin/src/components/Workflow/CWorkflowEditorInfo'
|
||||
import CWorkflowEditorTriggers from 'corteza-webapp-admin/src/components/Workflow/CWorkflowEditorTriggers'
|
||||
@@ -79,6 +80,7 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
workflow: undefined,
|
||||
initialWorkflowState: undefined,
|
||||
triggers: [],
|
||||
|
||||
info: {
|
||||
@@ -113,6 +115,14 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
watch: {
|
||||
workflowID: {
|
||||
immediate: true,
|
||||
@@ -129,6 +139,8 @@ export default {
|
||||
name: '',
|
||||
},
|
||||
}
|
||||
|
||||
this.initialWorkflowState = cloneDeep(this.workflow)
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -218,6 +230,17 @@ export default {
|
||||
|
||||
prepare (workflow = {}) {
|
||||
this.workflow = workflow
|
||||
this.initialWorkflowState = cloneDeep(this.workflow)
|
||||
},
|
||||
|
||||
checkUnsavedChanges (next, to) {
|
||||
const isNewPage = this.$route.path.includes('/new') && to.name.includes('edit')
|
||||
|
||||
if (isNewPage) {
|
||||
next(true)
|
||||
} else if (!to.name.includes('edit')) {
|
||||
next(!isEqual(this.workflow, this.initialWorkflowState) ? window.confirm(this.$t('general:editor.unsavedChanges')) : true)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isEqual, cloneDeep } from 'lodash'
|
||||
import editorHelpers from 'corteza-webapp-admin/src/mixins/editorHelpers'
|
||||
import CComposeEditorBasic from 'corteza-webapp-admin/src/components/Settings/Compose/CComposeEditorBasic'
|
||||
import CComposeEditorUI from 'corteza-webapp-admin/src/components/Settings/Compose/CComposeEditorUI'
|
||||
@@ -51,6 +52,7 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
settings: {},
|
||||
initialSettingsState: {},
|
||||
|
||||
basic: {
|
||||
processing: false,
|
||||
@@ -64,6 +66,14 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
can: 'rbac/can',
|
||||
@@ -104,6 +114,7 @@ export default {
|
||||
.then(settings => {
|
||||
settings.forEach(({ name, value }) => {
|
||||
this.$set(this.settings, name, value)
|
||||
this.$set(this.initialSettingsState, name, cloneDeep(value))
|
||||
})
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t('notification:settings.compose.fetch.error')))
|
||||
@@ -111,6 +122,16 @@ export default {
|
||||
this.decLoader()
|
||||
})
|
||||
},
|
||||
|
||||
checkUnsavedChanges (next, to) {
|
||||
const isNewPage = this.$route.path.includes('/new') && to.name.includes('edit')
|
||||
|
||||
if (isNewPage) {
|
||||
next(true)
|
||||
} else if (!to.name.includes('edit')) {
|
||||
next(!isEqual(this.settings, this.initialSettingsState) ? window.confirm(this.$t('general:editor.unsavedChanges')) : true)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -131,6 +131,7 @@ import { mapGetters } from 'vuex'
|
||||
import editorHelpers from 'corteza-webapp-admin/src/mixins/editorHelpers'
|
||||
import CFederationEditorInfo from 'corteza-webapp-admin/src/components/Federation/CFederationEditorInfo'
|
||||
import CSubmitButton from 'corteza-webapp-admin/src/components/CSubmitButton'
|
||||
import { cloneDeep, isEqual } from 'lodash'
|
||||
|
||||
export default {
|
||||
i18nOptions: {
|
||||
@@ -158,6 +159,7 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
node: {},
|
||||
initialNodeState: {},
|
||||
|
||||
// Processing and success flags for each form
|
||||
info: {
|
||||
@@ -175,6 +177,14 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
can: 'rbac/can',
|
||||
@@ -201,7 +211,17 @@ export default {
|
||||
this.fetchNode()
|
||||
this.fetchGeneratedUrl()
|
||||
} else {
|
||||
this.node = {}
|
||||
this.node = {
|
||||
name: '',
|
||||
baseURL: '',
|
||||
contact: '',
|
||||
}
|
||||
|
||||
this.initialNodeState = {
|
||||
name: '',
|
||||
baseURL: '',
|
||||
contact: '',
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -214,6 +234,7 @@ export default {
|
||||
this.$FederationAPI.nodeRead({ nodeID: this.nodeID })
|
||||
.then(node => {
|
||||
this.node = node // new federation.Node(node)
|
||||
this.initialNodeState = cloneDeep(node)
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t('notification:federation.fetch.error')))
|
||||
.finally(() => {
|
||||
@@ -250,6 +271,7 @@ export default {
|
||||
this.$FederationAPI.nodeUpdate(payload)
|
||||
.then(node => {
|
||||
this.node = node
|
||||
this.initialNodeState = cloneDeep(node)
|
||||
|
||||
this.animateSuccess('info')
|
||||
this.toastSuccess(this.$t('notification:federation.update.success'))
|
||||
@@ -343,6 +365,16 @@ export default {
|
||||
copyUrl () {
|
||||
navigator.clipboard.writeText(this.generate.url)
|
||||
},
|
||||
|
||||
checkUnsavedChanges (next, to) {
|
||||
const isNewPage = this.$route.path.includes('/new') && to.name.includes('edit')
|
||||
|
||||
if (isNewPage) {
|
||||
next(true)
|
||||
} else if (!to.name.includes('edit')) {
|
||||
next(!isEqual(this.node, this.initialNodeState) ? window.confirm(this.$t('general:editor.unsavedChanges')) : true)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
</b-container>
|
||||
</template>
|
||||
<script>
|
||||
import { isEqual, cloneDeep } from 'lodash'
|
||||
import editorHelpers from 'corteza-webapp-admin/src/mixins/editorHelpers'
|
||||
import CRouteEditorInfo from 'corteza-webapp-admin/src/components/Apigw/CRouteEditorInfo'
|
||||
import CFiltersStepper from 'corteza-webapp-admin/src/components/Apigw/CFiltersStepper'
|
||||
@@ -85,6 +86,7 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
route: {},
|
||||
initialRouteState: {},
|
||||
routeEndpoint: undefined,
|
||||
|
||||
info: {
|
||||
@@ -98,6 +100,7 @@ export default {
|
||||
},
|
||||
|
||||
filters: [],
|
||||
initialFiltersState: [],
|
||||
availableFilters: [],
|
||||
steps: [],
|
||||
}
|
||||
@@ -121,6 +124,14 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
watch: {
|
||||
routeID: {
|
||||
immediate: true,
|
||||
@@ -133,8 +144,11 @@ export default {
|
||||
this.fetchFilters()
|
||||
} else {
|
||||
this.route = {
|
||||
endpoint: '',
|
||||
method: 'GET',
|
||||
}
|
||||
|
||||
this.initialRouteState = cloneDeep(this.route)
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -146,6 +160,7 @@ export default {
|
||||
this.$SystemAPI.apigwRouteRead({ routeID: this.routeID, incFlags: 1 })
|
||||
.then((api) => {
|
||||
this.route = api
|
||||
this.initialRouteState = cloneDeep(api)
|
||||
this.routeEndpoint = btoa(api.endpoint)
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t('notification:gateway.fetch.error')))
|
||||
@@ -286,6 +301,7 @@ export default {
|
||||
f.enabled = !!filter.enabled
|
||||
return { ...f }
|
||||
})
|
||||
this.initialFiltersState = cloneDeep(this.filters)
|
||||
})
|
||||
},
|
||||
|
||||
@@ -325,6 +341,19 @@ export default {
|
||||
fetchSteps () {
|
||||
this.steps = ['prefilter', 'processer', 'postfilter']
|
||||
},
|
||||
|
||||
checkUnsavedChanges (next, to) {
|
||||
const isNewPage = this.$route.path.includes('/new') && to.name.includes('edit')
|
||||
|
||||
if (isNewPage) {
|
||||
next(true)
|
||||
} else if (!to.name.includes('edit')) {
|
||||
const routeState = !isEqual(this.route, this.initialRouteState)
|
||||
const filtersState = !isEqual(this.filters, this.initialFiltersState)
|
||||
|
||||
next((routeState || filtersState) ? window.confirm(this.$t('general:editor.unsavedChanges')) : true)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
:title="title"
|
||||
>
|
||||
<span
|
||||
v-if="applicationID"
|
||||
class="text-nowrap"
|
||||
>
|
||||
<b-button
|
||||
v-if="applicationID && canCreate"
|
||||
v-if="canCreate"
|
||||
data-test-id="button-new-application"
|
||||
variant="primary"
|
||||
:to="{ name: 'system.application.new' }"
|
||||
@@ -18,7 +19,7 @@
|
||||
{{ $t('new') }}
|
||||
</b-button>
|
||||
<c-permissions-button
|
||||
v-if="applicationID && canGrant"
|
||||
v-if="canGrant"
|
||||
:title="application.name || applicationID"
|
||||
:target="application.name || applicationID"
|
||||
:resource="`corteza::system:application/${applicationID}`"
|
||||
@@ -41,21 +42,24 @@
|
||||
/>
|
||||
|
||||
<c-application-editor-unify
|
||||
v-if="application.unify && application.applicationID"
|
||||
v-if="applicationID && application.unify && application.applicationID"
|
||||
class="mt-3"
|
||||
:unify="application.unify"
|
||||
:application="application"
|
||||
:can-pin="canPin"
|
||||
:processing="unify.processing"
|
||||
:success="unify.success"
|
||||
@change-detected="unifyAssetStateChange = true"
|
||||
@submit="onUnifySubmit"
|
||||
/>
|
||||
</b-container>
|
||||
</template>
|
||||
<script>
|
||||
import { isEqual } from 'lodash'
|
||||
import editorHelpers from 'corteza-webapp-admin/src/mixins/editorHelpers'
|
||||
import CApplicationEditorInfo from 'corteza-webapp-admin/src/components/Application/CApplicationEditorInfo'
|
||||
import CApplicationEditorUnify from 'corteza-webapp-admin/src/components/Application/CApplicationEditorUnify'
|
||||
import { system } from '@cortezaproject/corteza-js'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
@@ -84,6 +88,7 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
application: undefined,
|
||||
initialApplicationState: undefined,
|
||||
|
||||
info: {
|
||||
processing: false,
|
||||
@@ -93,6 +98,7 @@ export default {
|
||||
processing: false,
|
||||
success: false,
|
||||
},
|
||||
unifyAssetStateChange: false,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -118,6 +124,14 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
watch: {
|
||||
applicationID: {
|
||||
immediate: true,
|
||||
@@ -125,7 +139,9 @@ export default {
|
||||
if (this.applicationID) {
|
||||
this.fetchApplication()
|
||||
} else {
|
||||
this.application = {}
|
||||
this.application = new system.Application()
|
||||
|
||||
this.initialApplicationState = this.application.clone()
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -136,7 +152,25 @@ export default {
|
||||
this.incLoader()
|
||||
|
||||
this.$SystemAPI.applicationRead({ applicationID: this.applicationID, incFlags: 1 })
|
||||
.then(this.prepare)
|
||||
.then((application = {}) => {
|
||||
if (!application.unify) {
|
||||
application.unify = {
|
||||
listed: true,
|
||||
pinned: false,
|
||||
name: this.application.name,
|
||||
config: '',
|
||||
icon: '',
|
||||
logo: '',
|
||||
url: '',
|
||||
}
|
||||
}
|
||||
|
||||
application.unify.pinned = (application.flags || []).includes('pinned')
|
||||
application.unify.name = application.unify.name ? application.unify.name : application.name
|
||||
|
||||
this.application = new system.Application(application)
|
||||
this.initialApplicationState = this.application.clone()
|
||||
},)
|
||||
.catch(this.toastErrorHandler(this.$t('notification:application.fetch.error')))
|
||||
.finally(() => {
|
||||
this.decLoader()
|
||||
@@ -205,6 +239,7 @@ export default {
|
||||
|
||||
return this.$SystemAPI.applicationUpdate({ ...this.application, unify })
|
||||
.then(() => {
|
||||
this.unifyAssetStateChange = false
|
||||
this.fetchApplication()
|
||||
|
||||
this.toastSuccess(this.$t('notification:application.update.success'))
|
||||
@@ -284,22 +319,14 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
prepare (application = {}) {
|
||||
if (!application.unify) {
|
||||
application.unify = {
|
||||
listed: true,
|
||||
pinned: false,
|
||||
name: this.application.name,
|
||||
config: '',
|
||||
icon: '',
|
||||
logo: '',
|
||||
url: '',
|
||||
}
|
||||
checkUnsavedChanges (next, to) {
|
||||
const isNewPage = this.$route.path.includes('/new') && to.name.includes('edit')
|
||||
|
||||
if (isNewPage) {
|
||||
next(true)
|
||||
} else if (!to.name.includes('edit')) {
|
||||
next(!isEqual(this.application, this.initialApplicationState) || this.unifyAssetStateChange ? window.confirm(this.$t('general:editor.unsavedChanges')) : true)
|
||||
}
|
||||
|
||||
application.unify.pinned = (application.flags || []).includes('pinned')
|
||||
|
||||
this.application = application
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -47,37 +47,12 @@
|
||||
</b-container>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import { isEqual } from 'lodash'
|
||||
import editorHelpers from 'corteza-webapp-admin/src/mixins/editorHelpers'
|
||||
import CAuthclientEditorInfo from 'corteza-webapp-admin/src/components/Authclient/CAuthclientEditorInfo'
|
||||
import { system } from '@cortezaproject/corteza-js'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
const defSecurity = {
|
||||
impersonateUser: '0',
|
||||
permittedRoles: [],
|
||||
prohibitedRoles: [],
|
||||
forcedRoles: [],
|
||||
}
|
||||
// @todo move this to corteza-js and follow the pattern we use with other resource types
|
||||
const makeNewAuthClient = () => JSON.parse(JSON.stringify({
|
||||
scope: 'profile api',
|
||||
enabled: true,
|
||||
validGrant: 'authorization_code',
|
||||
|
||||
trusted: false,
|
||||
handle: '',
|
||||
meta: {
|
||||
name: '',
|
||||
description: '',
|
||||
},
|
||||
|
||||
redirectURI: '',
|
||||
|
||||
security: {
|
||||
...defSecurity,
|
||||
},
|
||||
}))
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CAuthclientEditorInfo,
|
||||
@@ -103,6 +78,7 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
authclient: undefined,
|
||||
initialAuthclientState: undefined,
|
||||
secret: '',
|
||||
|
||||
info: {
|
||||
@@ -130,6 +106,14 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
watch: {
|
||||
authClientID: {
|
||||
immediate: true,
|
||||
@@ -137,7 +121,8 @@ export default {
|
||||
if (this.authClientID) {
|
||||
this.fetchAuthclient()
|
||||
} else {
|
||||
this.authclient = makeNewAuthClient()
|
||||
this.authclient = new system.AuthClient()
|
||||
this.initialAuthclientState = this.authclient.clone()
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -149,7 +134,8 @@ export default {
|
||||
|
||||
this.$SystemAPI.authClientRead({ clientID: this.authClientID })
|
||||
.then(ac => {
|
||||
this.authclient = ac
|
||||
this.authclient = new system.AuthClient(ac)
|
||||
this.initialAuthclientState = this.authclient.clone()
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t('notification:authclient.fetch.error')))
|
||||
.finally(() => {
|
||||
@@ -166,7 +152,8 @@ export default {
|
||||
|
||||
this.$SystemAPI.authClientUpdate({ clientID, ...authclient })
|
||||
.then(ac => {
|
||||
this.authclient = ac
|
||||
this.authclient = new system.AuthClient(ac)
|
||||
this.initialAuthclientState = this.authclient.clone()
|
||||
|
||||
this.toastSuccess(this.$t('notification:authclient.update.success'))
|
||||
})
|
||||
@@ -177,7 +164,8 @@ export default {
|
||||
} else {
|
||||
this.$SystemAPI.authClientCreate({ ...authclient })
|
||||
.then((ac) => {
|
||||
this.authclient = ac
|
||||
this.authclient = new system.AuthClient(ac)
|
||||
this.initialAuthclientState = this.authclient.clone()
|
||||
const { authClientID } = ac
|
||||
this.animateSuccess('info')
|
||||
this.toastSuccess(this.$t('notification:authclient.create.success'))
|
||||
@@ -229,6 +217,16 @@ export default {
|
||||
.authClientRegenerateSecret(({ clientID }))
|
||||
.then(newSecret => { this.secret = newSecret })
|
||||
},
|
||||
|
||||
checkUnsavedChanges (next, to) {
|
||||
const isNewPage = this.$route.path.includes('/new') && to.name.includes('edit')
|
||||
|
||||
if (isNewPage) {
|
||||
next(true)
|
||||
} else if (!to.name.includes('edit')) {
|
||||
next(!isEqual(this.authclient, this.initialAuthclientState) ? window.confirm(this.$t('general:editor.unsavedChanges')) : true)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isEqual } from 'lodash'
|
||||
import { system, NoID } from '@cortezaproject/corteza-js'
|
||||
import { handle } from '@cortezaproject/corteza-vue'
|
||||
import editorHelpers from 'corteza-webapp-admin/src/mixins/editorHelpers'
|
||||
@@ -102,6 +103,7 @@ export default {
|
||||
return {
|
||||
processing: false,
|
||||
connection: undefined,
|
||||
initialConnectionState: undefined,
|
||||
|
||||
sensitivityLevels: undefined,
|
||||
}
|
||||
@@ -146,6 +148,14 @@ export default {
|
||||
|
||||
},
|
||||
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
watch: {
|
||||
connectionID: {
|
||||
immediate: true,
|
||||
@@ -154,6 +164,7 @@ export default {
|
||||
this.fetchConnection(connectionID)
|
||||
} else {
|
||||
this.connection = new system.DalConnection()
|
||||
this.initialConnectionState = this.connection.clone()
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -168,6 +179,7 @@ export default {
|
||||
this.incLoader()
|
||||
return this.$SystemAPI.dalConnectionRead({ connectionID }).then(connection => {
|
||||
this.connection = new system.DalConnection(connection)
|
||||
this.initialConnectionState = this.connection.clone()
|
||||
}).catch(this.toastErrorHandler(this.$t('notification:connection.fetch.error')))
|
||||
.finally(async () => {
|
||||
this.decLoader()
|
||||
@@ -204,6 +216,7 @@ export default {
|
||||
this.$router.push({ name: `system.connection.edit`, params: { connectionID } })
|
||||
} else {
|
||||
this.connection = new system.DalConnection(connection)
|
||||
this.initialConnectionState = this.connection.clone()
|
||||
}
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t(`notification:connection.${op}.error`)))
|
||||
@@ -239,6 +252,16 @@ export default {
|
||||
this.processing = false
|
||||
})
|
||||
},
|
||||
|
||||
checkUnsavedChanges (next, to) {
|
||||
const isNewPage = this.$route.path.includes('/new') && to.name.includes('edit')
|
||||
|
||||
if (isNewPage) {
|
||||
next(true)
|
||||
} else if (!to.name.includes('edit')) {
|
||||
next(!isEqual(this.connection, this.initialConnectionState) ? window.confirm(this.$t('general:editor.unsavedChanges')) : true)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isEqual, cloneDeep } from 'lodash'
|
||||
import editorHelpers from 'corteza-webapp-admin/src/mixins/editorHelpers'
|
||||
import CQueueEditorInfo from 'corteza-webapp-admin/src/components/Queues/CQueueEditorInfo'
|
||||
import { mapGetters } from 'vuex'
|
||||
@@ -63,6 +64,7 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
queue: undefined,
|
||||
initialQueueState: undefined,
|
||||
|
||||
consumers: [],
|
||||
|
||||
@@ -87,6 +89,14 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
watch: {
|
||||
queueID: {
|
||||
immediate: true,
|
||||
@@ -102,6 +112,16 @@ export default {
|
||||
poll_delay: '',
|
||||
dispatch_events: false,
|
||||
},
|
||||
queue: '',
|
||||
}
|
||||
|
||||
this.initialQueueState = {
|
||||
consumer: 'corteza',
|
||||
meta: {
|
||||
poll_delay: '',
|
||||
dispatch_events: false,
|
||||
},
|
||||
queue: '',
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -113,7 +133,10 @@ export default {
|
||||
this.incLoader()
|
||||
|
||||
this.$SystemAPI.queuesRead({ queueID: this.queueID })
|
||||
.then(q => { this.queue = q })
|
||||
.then(q => {
|
||||
this.queue = q
|
||||
this.initialQueueState = cloneDeep(q)
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t('notification:queue.fetch.error')))
|
||||
.finally(() => {
|
||||
this.decLoader()
|
||||
@@ -139,6 +162,7 @@ export default {
|
||||
this.$SystemAPI.queuesUpdate(queue)
|
||||
.then(queue => {
|
||||
this.queue = queue
|
||||
this.initialQueueState = cloneDeep(queue)
|
||||
|
||||
this.animateSuccess('info')
|
||||
this.toastSuccess(this.$t('notification:queue.update.success'))
|
||||
@@ -179,6 +203,16 @@ export default {
|
||||
this.decLoader()
|
||||
})
|
||||
},
|
||||
|
||||
checkUnsavedChanges (next, to) {
|
||||
const isNewPage = this.$route.path.includes('/new') && to.name.includes('edit')
|
||||
|
||||
if (isNewPage) {
|
||||
next(true)
|
||||
} else if (!to.name.includes('edit')) {
|
||||
next(!isEqual(this.queue, this.initialQueueState) ? window.confirm(this.$t('general:editor.unsavedChanges')) : true)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isEqual } from 'lodash'
|
||||
import { system } from '@cortezaproject/corteza-js'
|
||||
import editorHelpers from 'corteza-webapp-admin/src/mixins/editorHelpers'
|
||||
import CRoleEditorInfo from 'corteza-webapp-admin/src/components/Role/CRoleEditorInfo'
|
||||
@@ -104,9 +105,10 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
role: undefined,
|
||||
initialRoleState: undefined,
|
||||
isContext: false,
|
||||
|
||||
roleMembers: null,
|
||||
roleMembers: [],
|
||||
|
||||
info: {
|
||||
processing: false,
|
||||
@@ -119,6 +121,14 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
can: 'rbac/can',
|
||||
@@ -154,6 +164,7 @@ export default {
|
||||
this.fetchRole()
|
||||
} else {
|
||||
this.role = new system.Role()
|
||||
this.initialRoleState = this.role.clone()
|
||||
this.isContext = false
|
||||
}
|
||||
},
|
||||
@@ -181,6 +192,8 @@ export default {
|
||||
this.$SystemAPI.roleRead({ roleID: this.roleID })
|
||||
.then(r => {
|
||||
this.role = new system.Role(r)
|
||||
this.initialRoleState = this.role.clone()
|
||||
|
||||
this.isContext = !!this.role.isContext
|
||||
|
||||
if (this.role.canManageMembersOnRole && !this.role.isContext && !this.role.isClosed) {
|
||||
@@ -315,6 +328,17 @@ export default {
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
checkUnsavedChanges (next, to) {
|
||||
const isNewPage = this.$route.path.includes('/new') && to.name.includes('edit')
|
||||
|
||||
if (isNewPage) {
|
||||
next(true)
|
||||
} else if (!to.name.includes('edit')) {
|
||||
const isDirty = this.roleMembers.some(m => m.dirty !== m.current) || !isEqual(this.role, this.initialRoleState)
|
||||
next(isDirty ? window.confirm(this.$t('general:editor.unsavedChanges')) : true)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
</b-container>
|
||||
</template>
|
||||
<script>
|
||||
import { isEqual, cloneDeep } from 'lodash'
|
||||
import editorHelpers from 'corteza-webapp-admin/src/mixins/editorHelpers'
|
||||
import CSensitivityLevelEditorInfo from 'corteza-webapp-admin/src/components/SensitivityLevel/CSensitivityLevelEditorInfo'
|
||||
import { mapGetters } from 'vuex'
|
||||
@@ -61,6 +62,7 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
sensitivityLevel: undefined,
|
||||
initialSensitivityLevelState: undefined,
|
||||
|
||||
info: {
|
||||
processing: false,
|
||||
@@ -87,6 +89,14 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
watch: {
|
||||
sensitivityLevelID: {
|
||||
immediate: true,
|
||||
@@ -102,6 +112,8 @@ export default {
|
||||
description: '',
|
||||
},
|
||||
}
|
||||
|
||||
this.initialSensitivityLevelState = cloneDeep(this.sensitivityLevel)
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -114,6 +126,7 @@ export default {
|
||||
this.$SystemAPI.dalSensitivityLevelRead({ sensitivityLevelID })
|
||||
.then(sensitivityLevel => {
|
||||
this.sensitivityLevel = sensitivityLevel
|
||||
this.initialSensitivityLevelState = cloneDeep(sensitivityLevel)
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t('notification:sensitivityLevel.fetch.error')))
|
||||
.finally(() => {
|
||||
@@ -128,6 +141,7 @@ export default {
|
||||
this.$SystemAPI.dalSensitivityLevelUpdate(sensitivityLevel)
|
||||
.then(sensitivityLevel => {
|
||||
this.sensitivityLevel = sensitivityLevel
|
||||
this.initialSensitivityLevelState = cloneDeep(sensitivityLevel)
|
||||
|
||||
this.toastSuccess(this.$t('notification:sensitivityLevel.update.success'))
|
||||
})
|
||||
@@ -139,6 +153,8 @@ export default {
|
||||
this.$SystemAPI.dalSensitivityLevelCreate(sensitivityLevel)
|
||||
.then(sensitivityLevel => {
|
||||
this.sensitivityLevel = sensitivityLevel
|
||||
this.initialSensitivityLevelState = cloneDeep(sensitivityLevel)
|
||||
|
||||
const { sensitivityLevelID } = sensitivityLevel
|
||||
this.animateSuccess('info')
|
||||
this.toastSuccess(this.$t('notification:sensitivityLevel.create.success'))
|
||||
@@ -178,6 +194,16 @@ export default {
|
||||
.finally(() => this.decLoader())
|
||||
}
|
||||
},
|
||||
|
||||
checkUnsavedChanges (next, to) {
|
||||
const isNewPage = this.$route.path.includes('/new') && to.name.includes('edit')
|
||||
|
||||
if (isNewPage) {
|
||||
next(true)
|
||||
} else if (!to.name.includes('edit')) {
|
||||
next(!isEqual(this.sensitivityLevel, this.initialSensitivityLevelState) ? window.confirm(this.$t('general:editor.unsavedChanges')) : true)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isEqual } from 'lodash'
|
||||
import editorHelpers from 'corteza-webapp-admin/src/mixins/editorHelpers'
|
||||
import CTemplateEditorInfo from 'corteza-webapp-admin/src/components/Template/CTemplateEditorInfo'
|
||||
import CTemplateEditorContent from 'corteza-webapp-admin/src/components/Template/CTemplateEditorContent/Index'
|
||||
@@ -94,6 +95,7 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
template: undefined,
|
||||
initialTemplateState: undefined,
|
||||
|
||||
info: {
|
||||
processing: false,
|
||||
@@ -131,11 +133,20 @@ export default {
|
||||
this.fetchTemplate()
|
||||
} else {
|
||||
this.template = new system.Template()
|
||||
this.initialTemplateState = this.template.clone()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchTemplate () {
|
||||
this.incLoader()
|
||||
@@ -143,6 +154,7 @@ export default {
|
||||
this.$SystemAPI.templateRead({ templateID: this.templateID })
|
||||
.then(t => {
|
||||
this.template = new system.Template(t)
|
||||
this.initialTemplateState = this.template.clone()
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t('notification:template.fetch.error')))
|
||||
.finally(() => {
|
||||
@@ -198,7 +210,8 @@ export default {
|
||||
if (this.templateID) {
|
||||
this.$SystemAPI.templateUpdate(template)
|
||||
.then(template => {
|
||||
this.template = template
|
||||
this.template = new system.Template(template)
|
||||
this.initialTemplateState = this.template.clone()
|
||||
|
||||
this.toastSuccess(this.$t('notification:template.update.success'))
|
||||
})
|
||||
@@ -222,6 +235,16 @@ export default {
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
checkUnsavedChanges (next, to) {
|
||||
const isNewPage = this.$route.path.includes('/new') && to.name.includes('edit')
|
||||
|
||||
if (isNewPage) {
|
||||
next(true)
|
||||
} else if (!to.name.includes('edit')) {
|
||||
next(!isEqual(this.template, this.initialTemplateState) ? window.confirm(this.$t('general:editor.unsavedChanges')) : true)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isEqual } from 'lodash'
|
||||
import { NoID, system } from '@cortezaproject/corteza-js'
|
||||
import editorHelpers from 'corteza-webapp-admin/src/mixins/editorHelpers'
|
||||
import CUserEditorInfo from 'corteza-webapp-admin/src/components/User/CUserEditorInfo'
|
||||
@@ -140,10 +141,11 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
user: undefined,
|
||||
initialUserState: undefined,
|
||||
|
||||
membership: {
|
||||
active: [],
|
||||
original: [],
|
||||
initial: [],
|
||||
},
|
||||
|
||||
externalAuthProviders: [],
|
||||
@@ -200,11 +202,20 @@ export default {
|
||||
this.fetchExternalAuthProviders()
|
||||
} else {
|
||||
this.user = new system.User()
|
||||
this.initialUserState = this.user.clone()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.checkUnsavedChanges(next, to)
|
||||
},
|
||||
|
||||
methods: {
|
||||
makeEvent (res) {
|
||||
return system.UserEvent(res)
|
||||
@@ -216,6 +227,7 @@ export default {
|
||||
return this.$SystemAPI.userRead({ userID: this.userID })
|
||||
.then(user => {
|
||||
this.user = new system.User(user)
|
||||
this.initialUserState = this.user.clone()
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t('notification:user.fetch.error')))
|
||||
.finally(() => {
|
||||
@@ -227,7 +239,7 @@ export default {
|
||||
this.incLoader()
|
||||
return this.$SystemAPI.userMembershipList({ userID: this.userID })
|
||||
.then((set = []) => {
|
||||
this.membership = { active: [...set], original: [...set] }
|
||||
this.membership = { active: [...set], initial: [...set] }
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t('notification:user.roles.error')))
|
||||
.finally(() => {
|
||||
@@ -264,6 +276,7 @@ export default {
|
||||
this.$SystemAPI.userUpdate(payload)
|
||||
.then(user => {
|
||||
this.user = new system.User(user)
|
||||
this.initialUserState = this.user.clone()
|
||||
|
||||
this.animateSuccess('info')
|
||||
this.toastSuccess(this.$t('notification:user.update.success'))
|
||||
@@ -404,15 +417,15 @@ export default {
|
||||
|
||||
const userID = this.userID
|
||||
|
||||
const { active, original } = this.membership
|
||||
const { active, initial } = this.membership
|
||||
|
||||
Promise.all([
|
||||
// all removed memberships
|
||||
...original.filter(roleID => !active.includes(roleID)).map(roleID => {
|
||||
...initial.filter(roleID => !active.includes(roleID)).map(roleID => {
|
||||
return this.$SystemAPI.userMembershipRemove({ roleID, userID })
|
||||
}),
|
||||
// all new memberships
|
||||
...active.filter(roleID => !original.includes(roleID)).map(roleID => {
|
||||
...active.filter(roleID => !initial.includes(roleID)).map(roleID => {
|
||||
return this.$SystemAPI.userMembershipAdd({ roleID, userID })
|
||||
}),
|
||||
])
|
||||
@@ -499,6 +512,19 @@ export default {
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t('notification:user.avatarDelete.error')))
|
||||
},
|
||||
|
||||
checkUnsavedChanges (next, to) {
|
||||
const isNewPage = this.$route.path.includes('/new') && to.name.includes('edit')
|
||||
|
||||
if (isNewPage) {
|
||||
next(true)
|
||||
} else if (!to.name.includes('edit')) {
|
||||
let userChangesStatus = !isEqual(this.user, this.initialUserState)
|
||||
let membershipChangesStatus = !isEqual(this.membership.initial, this.membership.active)
|
||||
|
||||
next((userChangesStatus || membershipChangesStatus) ? window.confirm(this.$t('general:editor.unsavedChanges')) : true)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<b-table-simple
|
||||
v-if="rule.constraints.length > 0"
|
||||
v-if="rule.constraints && rule.constraints.length > 0"
|
||||
borderless
|
||||
>
|
||||
<thead>
|
||||
@@ -145,12 +145,6 @@ export default {
|
||||
computed: {
|
||||
rules: {
|
||||
get () {
|
||||
this.module.config.recordDeDup.rules.forEach(rule => {
|
||||
if (rule.constraints === null) {
|
||||
rule.constraints = []
|
||||
}
|
||||
})
|
||||
|
||||
return this.module.config.recordDeDup.rules
|
||||
},
|
||||
set (value) {
|
||||
@@ -185,6 +179,10 @@ export default {
|
||||
},
|
||||
|
||||
updateRuleConstraint (rule) {
|
||||
if (!rule.constraints) {
|
||||
rule.constraints = []
|
||||
}
|
||||
|
||||
rule.constraints.push({
|
||||
attribute: rule.currentField.name,
|
||||
modifier: 'case-sensitive',
|
||||
@@ -197,7 +195,7 @@ export default {
|
||||
},
|
||||
|
||||
filterFieldOptions (rule) {
|
||||
const selectedFields = rule.constraints.map(({ attribute }) => attribute)
|
||||
const selectedFields = rule.constraints ? rule.constraints.map(({ attribute }) => attribute) : []
|
||||
return this.module.fields.filter(({ name }) => !selectedFields.includes(name))
|
||||
},
|
||||
|
||||
|
||||
@@ -311,6 +311,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { isEqual, debounce, cloneDeep } from 'lodash'
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import EditorToolbar from 'corteza-webapp-compose/src/components/Admin/EditorToolbar'
|
||||
import { compose, NoID, shared } from '@cortezaproject/corteza-js'
|
||||
@@ -323,7 +324,7 @@ import Reports from 'corteza-webapp-compose/src/components/Chart/Report'
|
||||
import { chartConstructor } from 'corteza-webapp-compose/src/lib/charts'
|
||||
import VueSelect from 'vue-select'
|
||||
import { evaluatePrefilter } from 'corteza-webapp-compose/src/lib/record-filter'
|
||||
import { debounce } from 'lodash'
|
||||
|
||||
const { CInputCheckbox } = components
|
||||
const { colorschemes } = shared
|
||||
|
||||
@@ -370,6 +371,7 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
chart: undefined,
|
||||
initialChartState: undefined,
|
||||
processing: false,
|
||||
|
||||
editReportIndex: undefined,
|
||||
@@ -519,6 +521,8 @@ export default {
|
||||
immediate: true,
|
||||
handler (chartID) {
|
||||
this.chart = undefined
|
||||
this.initialChartState = undefined
|
||||
|
||||
const { namespaceID } = this.namespace
|
||||
|
||||
if (chartID === NoID) {
|
||||
@@ -537,11 +541,13 @@ export default {
|
||||
break
|
||||
}
|
||||
this.chart = c
|
||||
this.initialChartState = cloneDeep(c)
|
||||
this.onEditReport(0)
|
||||
} else {
|
||||
this.findChartByID({ namespaceID, chartID, force: true }).then((chart) => {
|
||||
// Make a copy so that we do not change store item by ref
|
||||
this.chart = chartConstructor(chart)
|
||||
this.initialChartState = cloneDeep(chartConstructor(chart))
|
||||
this.onEditReport(0)
|
||||
}).catch(this.toastErrorHandler(this.$t('notification:chart.loadFailed')))
|
||||
}
|
||||
@@ -558,6 +564,14 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.checkUnsavedChart(next)
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.checkUnsavedChart(next)
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions({
|
||||
findChartByID: 'chart/findByID',
|
||||
@@ -608,6 +622,7 @@ export default {
|
||||
if (this.chart.chartID === NoID) {
|
||||
this.createChart(c).then(({ chartID }) => {
|
||||
this.toastSuccess(this.$t('notification:chart.saved'))
|
||||
this.initialChartState = cloneDeep(chartConstructor(this.chart))
|
||||
if (closeOnSuccess) {
|
||||
this.redirect()
|
||||
} else {
|
||||
@@ -617,6 +632,7 @@ export default {
|
||||
} else {
|
||||
this.updateChart(c).then((chart) => {
|
||||
this.chart = chartConstructor(chart)
|
||||
this.initialChartState = cloneDeep(chartConstructor(chart))
|
||||
this.toastSuccess(this.$t('notification:chart.saved'))
|
||||
if (closeOnSuccess) {
|
||||
this.redirect()
|
||||
@@ -654,6 +670,10 @@ export default {
|
||||
getOptionKey ({ value }) {
|
||||
return value
|
||||
},
|
||||
|
||||
checkUnsavedChart (next) {
|
||||
next(!isEqual(this.chart, this.initialChartState) ? window.confirm(this.$t('notification.unsavedChanges')) : true)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -452,6 +452,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isEqual } from 'lodash'
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import draggable from 'vuedraggable'
|
||||
import FieldConfigurator from 'corteza-webapp-compose/src/components/ModuleFields/Configurator'
|
||||
@@ -516,6 +517,7 @@ export default {
|
||||
|
||||
updateField: null,
|
||||
module: undefined,
|
||||
initialModuleState: undefined,
|
||||
hasRecords: true,
|
||||
processing: false,
|
||||
|
||||
@@ -643,6 +645,7 @@ export default {
|
||||
immediate: true,
|
||||
handler (moduleID) {
|
||||
this.module = undefined
|
||||
this.initialModuleState = undefined
|
||||
|
||||
/**
|
||||
* Every time module changes we switch to the 1st tab
|
||||
@@ -654,6 +657,7 @@ export default {
|
||||
{ fields: [new compose.ModuleFieldString({ fieldID: NoID, name: this.$t('general.placeholder.sample') })] },
|
||||
this.namespace,
|
||||
)
|
||||
this.initialModuleState = this.module.clone()
|
||||
} else {
|
||||
const params = {
|
||||
// make sure module is loaded from the API every time!
|
||||
@@ -665,6 +669,7 @@ export default {
|
||||
this.findModuleByID(params).then((module) => {
|
||||
// Make a copy so that we do not change store item by ref
|
||||
this.module = module.clone()
|
||||
this.initialModuleState = module.clone()
|
||||
|
||||
const { moduleID, namespaceID, issues = [] } = this.module
|
||||
|
||||
@@ -692,6 +697,14 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.checkUnsavedModule(next)
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.checkUnsavedModule(next)
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions({
|
||||
findModuleByID: 'module/findByID',
|
||||
@@ -702,6 +715,10 @@ export default {
|
||||
deletePage: 'page/delete',
|
||||
}),
|
||||
|
||||
checkUnsavedModule (next) {
|
||||
next(!isEqual(this.module.clone(), this.initialModuleState.clone()) ? window.confirm(this.$t('general.unsavedChanges')) : true)
|
||||
},
|
||||
|
||||
handleNewField () {
|
||||
this.module.fields.push(new compose.ModuleFieldString())
|
||||
},
|
||||
@@ -766,6 +783,7 @@ export default {
|
||||
}
|
||||
|
||||
this.module = new compose.Module({ ...module }, this.namespace)
|
||||
this.initialModuleState = this.module.clone()
|
||||
|
||||
this.toastSuccess(this.$t('notification:module.created'))
|
||||
if (closeOnSuccess) {
|
||||
@@ -780,6 +798,8 @@ export default {
|
||||
} else {
|
||||
this.updateModule({ ...this.module, resourceTranslationLanguage }).then(module => {
|
||||
this.module = new compose.Module({ ...module }, this.namespace)
|
||||
this.initialModuleState = this.module.clone()
|
||||
|
||||
this.toastSuccess(this.$t('notification:module.saved'))
|
||||
if (closeOnSuccess) {
|
||||
this.$router.push({ name: 'admin.modules' })
|
||||
@@ -812,6 +832,7 @@ export default {
|
||||
this.$SystemAPI.dalConnectionRead({ connectionID })
|
||||
.then(connection => {
|
||||
this.connection = connection
|
||||
this.initialModuleState.config.dal.connectionID = connection.connectionID
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t('notification:connection.read-failed')))
|
||||
.finally(() => {
|
||||
|
||||
@@ -882,7 +882,6 @@ export default {
|
||||
}
|
||||
} catch (error) {
|
||||
this.toastWarning(this.$t('notification:page.invalidBlock'))
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -755,6 +755,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isEqual } from 'lodash'
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import EditorToolbar from 'corteza-webapp-compose/src/components/Admin/EditorToolbar'
|
||||
import PageTranslator from 'corteza-webapp-compose/src/components/Admin/Page/PageTranslator'
|
||||
@@ -803,6 +804,7 @@ export default {
|
||||
processing: false,
|
||||
|
||||
page: new compose.Page(),
|
||||
initialPageState: new compose.Page(),
|
||||
|
||||
showIconModal: false,
|
||||
attachments: [],
|
||||
@@ -949,6 +951,7 @@ export default {
|
||||
immediate: true,
|
||||
handler (pageID) {
|
||||
this.page = undefined
|
||||
this.initialPageState = undefined
|
||||
this.layouts = []
|
||||
|
||||
this.removedLayouts = new Set()
|
||||
@@ -959,6 +962,7 @@ export default {
|
||||
const { namespaceID } = this.namespace
|
||||
this.findPageByID({ namespaceID, pageID, force: true }).then((page) => {
|
||||
this.page = page.clone()
|
||||
this.initialPageState = page.clone()
|
||||
return this.fetchAttachments()
|
||||
}).then(this.fetchLayouts)
|
||||
.finally(() => {
|
||||
@@ -969,6 +973,14 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.checkUnsavedComposePage(next)
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.checkUnsavedComposePage(next)
|
||||
},
|
||||
|
||||
created () {
|
||||
this.fetchRoles()
|
||||
},
|
||||
@@ -989,7 +1001,7 @@ export default {
|
||||
async fetchLayouts () {
|
||||
const { namespaceID } = this.namespace
|
||||
return this.findLayoutsByPageID({ namespaceID, pageID: this.pageID, force: true }).then(layouts => {
|
||||
this.layouts = layouts
|
||||
this.layouts = layouts.map((layout) => new compose.PageLayout(layout))
|
||||
})
|
||||
},
|
||||
|
||||
@@ -1065,6 +1077,7 @@ export default {
|
||||
return this.updatePage({ namespaceID, ...this.page, resourceTranslationLanguage })
|
||||
}).then(page => {
|
||||
this.page = page.clone()
|
||||
this.initialPageState = page.clone()
|
||||
return this.handleSaveLayouts()
|
||||
}).then(this.handlePageLayoutReorder)
|
||||
.then(() => {
|
||||
@@ -1161,6 +1174,13 @@ export default {
|
||||
layoutHandleState (layoutHandle) {
|
||||
return handle.handleState(layoutHandle)
|
||||
},
|
||||
|
||||
checkUnsavedComposePage (next) {
|
||||
const layoutsStateChange = this.layouts.some((layout) => layout.meta.updated)
|
||||
const pageStateChange = !isEqual(this.page, this.initialPageState)
|
||||
|
||||
next((layoutsStateChange || pageStateChange) ? window.confirm(this.$t('unsavedChanges')) : true)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -318,6 +318,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isEqual } from 'lodash'
|
||||
import { compose, NoID } from '@cortezaproject/corteza-js'
|
||||
import { url, handle } from '@cortezaproject/corteza-vue'
|
||||
import EditorToolbar from 'corteza-webapp-compose/src/components/Admin/EditorToolbar'
|
||||
@@ -342,14 +343,20 @@ export default {
|
||||
processing: false,
|
||||
|
||||
namespace: new compose.Namespace({ enabled: true }),
|
||||
initialNamespaceState: new compose.Namespace({ enabled: true }),
|
||||
namespaceAssets: {
|
||||
logo: undefined,
|
||||
icon: undefined,
|
||||
},
|
||||
namespaceAssetsInitialState: {
|
||||
logo: undefined,
|
||||
icon: undefined,
|
||||
},
|
||||
namespaceEnabled: false,
|
||||
|
||||
application: undefined,
|
||||
isApplication: false,
|
||||
isApplicationInitialState: false,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -436,6 +443,14 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.checkUnsavedNamespace(next)
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.checkUnsavedNamespace(next)
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions({
|
||||
updateNamespace: 'namespace/update',
|
||||
@@ -452,6 +467,7 @@ export default {
|
||||
|
||||
this.application = undefined
|
||||
this.isApplication = false
|
||||
this.isApplicationInitialState = this.isApplication
|
||||
|
||||
if (namespaceID) {
|
||||
await this.findNamespace({ namespaceID })
|
||||
@@ -477,6 +493,8 @@ export default {
|
||||
...this.namespace.meta,
|
||||
}
|
||||
|
||||
this.initialNamespaceState = this.namespace.clone()
|
||||
|
||||
this.processing = false
|
||||
this.loaded = true
|
||||
},
|
||||
@@ -504,6 +522,7 @@ export default {
|
||||
if (set.length) {
|
||||
this.application = set[0]
|
||||
this.isApplication = this.application.enabled
|
||||
this.isApplicationInitialState = this.isApplication
|
||||
}
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t('notification:namespace.application.fetchFailed')))
|
||||
@@ -525,6 +544,7 @@ export default {
|
||||
try {
|
||||
assets = await this.uploadAssets()
|
||||
meta = { ...meta, ...assets }
|
||||
this.namespaceAssetsInitialState = this.namespaceAssets
|
||||
} catch (e) {
|
||||
const error = JSON.stringify(e) === '{}' ? '' : e
|
||||
this.toastErrorHandler(this.$t('notification:namespace.assetUploadFailed'))(error)
|
||||
@@ -587,6 +607,7 @@ export default {
|
||||
if (closeOnSuccess) {
|
||||
this.$router.push({ name: 'namespace.manage' })
|
||||
} else if (!this.isEdit || this.isClone) {
|
||||
this.initialNamespaceState = this.namespace.clone()
|
||||
this.$router.push({ name: 'namespace.edit', params: { namespaceID: this.namespace.namespaceID } })
|
||||
}
|
||||
|
||||
@@ -596,6 +617,8 @@ export default {
|
||||
hideSidebar: false,
|
||||
...this.namespace.meta,
|
||||
}
|
||||
|
||||
this.initialNamespaceState = this.namespace.clone()
|
||||
},
|
||||
|
||||
handleDelete () {
|
||||
@@ -705,6 +728,14 @@ export default {
|
||||
this.namespace.meta.logo = undefined
|
||||
this.namespace.meta.logoID = undefined
|
||||
},
|
||||
|
||||
checkUnsavedNamespace (next) {
|
||||
const namespaceState = !isEqual(JSON.stringify(this.namespace), JSON.stringify(this.initialNamespaceState))
|
||||
const isApplicationState = !(this.isApplication === this.isApplicationInitialState)
|
||||
const namespaceAssetsState = !isEqual(this.namespaceAssets, this.namespaceAssetsInitialState)
|
||||
|
||||
next((namespaceState || isApplicationState || namespaceAssetsState) ? window.confirm(this.$t('manage.unsavedChanges')) : true)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -165,6 +165,8 @@ export default {
|
||||
sizes.forEach((size, index) => {
|
||||
this.block.elements[index].meta.size = size
|
||||
})
|
||||
|
||||
this.$emit('item-updated', this.index)
|
||||
},
|
||||
|
||||
getScenarioDefinition (element) {
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
:y="item.y"
|
||||
:class="{ 'editable-grid-item': editable }"
|
||||
drag-ignore-from=".gutter"
|
||||
@moved="onBlockUpdated(index)"
|
||||
@resized="onBlockUpdated(index)"
|
||||
>
|
||||
<slot
|
||||
:block="blocks[item.i]"
|
||||
@@ -110,6 +112,12 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onBlockUpdated (index) {
|
||||
this.$emit('item-updated', index)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export default {
|
||||
return this.$SystemAPI.reportRead({ reportID })
|
||||
.then(report => {
|
||||
this.report = new system.Report(report)
|
||||
this.initialReportState = this.report.clone()
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t('notification:report.fetchFailed')))
|
||||
.finally(() => {
|
||||
@@ -44,6 +45,8 @@ export default {
|
||||
return this.$SystemAPI.reportCreate(report)
|
||||
.then(report => {
|
||||
this.report = new system.Report(report)
|
||||
this.initialReportState = this.report.clone()
|
||||
this.detectStateChange = false
|
||||
this.toastSuccess(this.$t('notification:report.created'))
|
||||
this.$router.push({ name: 'report.edit', params: { reportID: report.reportID } })
|
||||
})
|
||||
@@ -55,6 +58,8 @@ export default {
|
||||
return this.$SystemAPI.reportUpdate(report)
|
||||
.then(report => {
|
||||
this.report = new system.Report(report)
|
||||
this.initialReportState = this.report.clone()
|
||||
this.detectStateChange = false
|
||||
this.toastSuccess(this.$t('notification:report.updated'))
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t('notification:report.updateFailed')))
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
v-if="report && canRead && showReport"
|
||||
:blocks.sync="reportBlocks"
|
||||
editable
|
||||
@item-updated="onBlockUpdated"
|
||||
>
|
||||
<template
|
||||
slot-scope="{ block, index }"
|
||||
@@ -95,6 +96,17 @@
|
||||
<div
|
||||
class="toolbox border-0 p-2 m-0 text-light text-center"
|
||||
>
|
||||
<div
|
||||
v-if="unsavedBlocks.has(index)"
|
||||
:title="$t('tooltip.unsavedChanges')"
|
||||
class="btn border-0"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'exclamation-triangle']"
|
||||
class="text-warning"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<b-button-group>
|
||||
<b-button
|
||||
:title="$t('builder:tooltip.add.displayElement')"
|
||||
@@ -134,6 +146,7 @@
|
||||
:block="block"
|
||||
:scenario="currentSelectedScenario"
|
||||
:report-i-d="reportID"
|
||||
@item-updated="onBlockUpdated"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -411,6 +424,8 @@ export default {
|
||||
|
||||
report: undefined,
|
||||
|
||||
unsavedBlocks: new Set(),
|
||||
|
||||
dataframes: [],
|
||||
|
||||
blocks: {
|
||||
@@ -630,10 +645,19 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.checkUnsavedBlocks(next)
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.checkUnsavedBlocks(next)
|
||||
},
|
||||
|
||||
watch: {
|
||||
reportID: {
|
||||
immediate: true,
|
||||
handler (reportID) {
|
||||
this.unsavedBlocks.clear()
|
||||
this.scenarios.selected = undefined
|
||||
|
||||
if (reportID) {
|
||||
@@ -814,6 +838,7 @@ export default {
|
||||
.then(() => {
|
||||
this.mapBlocks()
|
||||
this.refreshReport()
|
||||
this.unsavedBlocks.clear()
|
||||
})
|
||||
},
|
||||
|
||||
@@ -842,6 +867,8 @@ export default {
|
||||
},
|
||||
|
||||
updateBlock () {
|
||||
this.unsavedBlocks.add(this.blocks.currentIndex)
|
||||
|
||||
if (this.currentBlock) {
|
||||
const elements = this.currentBlock.elements
|
||||
|
||||
@@ -860,6 +887,7 @@ export default {
|
||||
|
||||
deleteBlock (index = undefined) {
|
||||
this.reindexBlocks(this.reportBlocks.filter((p, i) => index !== i))
|
||||
this.unsavedBlocks.add(index)
|
||||
},
|
||||
|
||||
// Display elements
|
||||
@@ -887,6 +915,8 @@ export default {
|
||||
|
||||
this.editBlock(this.blocks.currentIndex)
|
||||
this.setCurrentDisplayElement(this.currentBlock.elements.length - 1)
|
||||
|
||||
this.updateBlock()
|
||||
},
|
||||
|
||||
// Scenarios
|
||||
@@ -924,6 +954,15 @@ export default {
|
||||
getOptionKey (scenario) {
|
||||
return scenario
|
||||
},
|
||||
|
||||
// Trigger browser dialog on page leave to prevent unsaved changes
|
||||
checkUnsavedBlocks (next) {
|
||||
next(!this.unsavedBlocks.size || window.confirm(this.$t('builder:unsaved-changes')))
|
||||
},
|
||||
|
||||
onBlockUpdated (index) {
|
||||
this.unsavedBlocks.add(index)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -103,6 +103,7 @@
|
||||
:placeholder="$t('name')"
|
||||
required
|
||||
:state="nameState"
|
||||
@input="handleDetectStateChange"
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
@@ -121,6 +122,7 @@
|
||||
:placeholder="$t('placeholder-handle')"
|
||||
required
|
||||
:state="handleState"
|
||||
@input="handleDetectStateChange"
|
||||
/>
|
||||
<b-form-invalid-feedback
|
||||
data-test-id="input-handle-invalid-state"
|
||||
@@ -141,6 +143,7 @@
|
||||
data-test-id="input-description"
|
||||
:placeholder="$t('report.description')"
|
||||
rows="5"
|
||||
@input="handleDetectStateChange"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
@@ -178,6 +181,7 @@ import { handle } from '@cortezaproject/corteza-vue'
|
||||
import report from 'corteza-webapp-reporter/src/mixins/report'
|
||||
import EditorToolbar from 'corteza-webapp-reporter/src/components/EditorToolbar'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { isEqual } from 'lodash'
|
||||
|
||||
export default {
|
||||
name: 'EditReport',
|
||||
@@ -197,6 +201,9 @@ export default {
|
||||
processing: false,
|
||||
|
||||
report: undefined,
|
||||
initialReportState: undefined,
|
||||
|
||||
detectStateChange: false,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -268,9 +275,44 @@ export default {
|
||||
this.fetchReport(reportID)
|
||||
} else {
|
||||
this.report = new system.Report()
|
||||
this.initialReportState = new system.Report()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.checkUnsavedChart(next)
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.checkUnsavedChart(next)
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleDetectStateChange () {
|
||||
this.detectStateChange = true
|
||||
},
|
||||
|
||||
checkUnsavedChart (next) {
|
||||
const reportState = {
|
||||
handle: this.report.handle,
|
||||
meta: {
|
||||
name: this.report.meta.name,
|
||||
description: this.report.meta.description,
|
||||
},
|
||||
}
|
||||
|
||||
const initialReportState = {
|
||||
handle: this.initialReportState.handle,
|
||||
meta: {
|
||||
name: this.initialReportState.meta.name,
|
||||
description: this.initialReportState.meta.description,
|
||||
},
|
||||
}
|
||||
|
||||
next(!isEqual(reportState, initialReportState) ? window.confirm(this.$t('unsavedChanges')) : true)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -121,7 +121,23 @@ export class ModuleField {
|
||||
applyOptions (o?: Partial<Options>): void {
|
||||
if (!o) return
|
||||
|
||||
Apply(this.options, o, Object, 'description', 'hint')
|
||||
if (o.description) {
|
||||
this.options.description = {
|
||||
...this.options.description,
|
||||
...o.description,
|
||||
}
|
||||
|
||||
this.options.description.edit = this.options.description.edit || undefined
|
||||
}
|
||||
|
||||
if (o.hint) {
|
||||
this.options.hint = {
|
||||
...this.options.hint,
|
||||
...o.hint,
|
||||
}
|
||||
|
||||
this.options.hint.edit = this.options.hint.edit || undefined
|
||||
}
|
||||
}
|
||||
|
||||
clone (): ModuleField {
|
||||
|
||||
@@ -6,6 +6,7 @@ export { Reminder } from './types/reminder'
|
||||
export { Template } from './types/template'
|
||||
export { Report } from './types/report'
|
||||
export { DalConnection } from './types/dalConnection'
|
||||
export { AuthClient } from './types/authClient'
|
||||
export {
|
||||
SystemEvent,
|
||||
RoleEvent,
|
||||
|
||||
@@ -1,4 +1,71 @@
|
||||
import { Apply, CortezaID, ISO8601Date, NoID } from '../../cast'
|
||||
import { IsOf } from '../../guards'
|
||||
|
||||
interface PartialApplication extends Partial<Omit<Application, 'createdAt' | 'updatedAt' | 'deletedAt' | 'lastUsedAt'>> {
|
||||
createdAt?: string|number|Date;
|
||||
updatedAt?: string|number|Date;
|
||||
}
|
||||
|
||||
interface Unify {
|
||||
name: string;
|
||||
listed: boolean,
|
||||
url: string,
|
||||
config: string,
|
||||
iconID: string,
|
||||
logoID: string
|
||||
}
|
||||
|
||||
export class Application {
|
||||
// @todo port application class here
|
||||
[_: string]: unknown
|
||||
public applicationID = undefined
|
||||
public name = ''
|
||||
public ownerID?: number = 0;
|
||||
public enabled = false
|
||||
public weight?: number = 0;
|
||||
|
||||
public unify?: Unify = {
|
||||
name: '',
|
||||
listed: false,
|
||||
url: '',
|
||||
config: '',
|
||||
iconID: NoID,
|
||||
logoID: NoID,
|
||||
};
|
||||
|
||||
public canGrant: boolean = true;
|
||||
public canUpdateApplication: boolean = true;
|
||||
public canDeleteApplication: boolean = true;
|
||||
|
||||
constructor (r?: PartialApplication) {
|
||||
this.apply(r)
|
||||
}
|
||||
|
||||
apply (r?: PartialApplication): void {
|
||||
Apply(this, r, CortezaID, 'applicationID')
|
||||
Apply(this, r, String, 'name')
|
||||
Apply(this, r, Number, 'weight', 'ownerID')
|
||||
Apply(this, r, Boolean, 'enabled', 'canGrant', 'canUpdateApplication', 'canDeleteApplication')
|
||||
|
||||
|
||||
if (r && IsOf(r, 'unify')) {
|
||||
this.unify = r.unify
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns resource ID
|
||||
*/
|
||||
get resourceID (): string {
|
||||
return `${this.resourceType}:${this.applicationID}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Resource type
|
||||
*/
|
||||
get resourceType (): string {
|
||||
return 'system:application'
|
||||
}
|
||||
|
||||
clone (): Application {
|
||||
return new Application(JSON.parse(JSON.stringify(this)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Apply, CortezaID, ISO8601Date, NoID } from "../../cast";
|
||||
import { IsOf } from "../../guards";
|
||||
|
||||
interface PartialAuthClient
|
||||
extends Partial<
|
||||
Omit<AuthClient, "createdAt" | "updatedAt" | "deletedAt" | "lastUsedAt">
|
||||
> {
|
||||
createdAt?: string | number | Date;
|
||||
updatedAt?: string | number | Date;
|
||||
deletedAt?: string | number | Date;
|
||||
lastUsedAt?: string | number | Date;
|
||||
}
|
||||
|
||||
interface AuthClientMeta {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface DefSecurity {
|
||||
impersonateUser: string;
|
||||
permittedRoles: Array<string>;
|
||||
prohibitedRoles: Array<string>;
|
||||
forcedRoles: Array<string>;
|
||||
}
|
||||
|
||||
export class AuthClient {
|
||||
public authClientID = NoID;
|
||||
public handle = "";
|
||||
public scope = "profile api";
|
||||
public redirectURI = "";
|
||||
public validGrant = 'authorization_code';
|
||||
|
||||
public meta: AuthClientMeta = {
|
||||
name: "",
|
||||
description: "",
|
||||
};
|
||||
|
||||
public security: DefSecurity = {
|
||||
impersonateUser: "0",
|
||||
permittedRoles: [],
|
||||
prohibitedRoles: [],
|
||||
forcedRoles: [],
|
||||
};
|
||||
|
||||
public enabled = true;
|
||||
public trusted = false;
|
||||
|
||||
public createdAt?: Date = undefined;
|
||||
public updatedAt?: Date = undefined;
|
||||
public deletedAt?: Date = undefined;
|
||||
|
||||
public createdBy = NoID;
|
||||
public updatedBy = NoID;
|
||||
public deletedBy = NoID;
|
||||
|
||||
public canDeleteAuthClient = false;
|
||||
public canGrant = false;
|
||||
public canUpdateAuthClient = false;
|
||||
|
||||
constructor (o?: PartialAuthClient) {
|
||||
this.apply(o)
|
||||
}
|
||||
|
||||
apply (o?: PartialAuthClient): void {
|
||||
Apply(this, o, CortezaID, 'authClientID')
|
||||
Apply(this, o, ISO8601Date, 'createdAt', 'updatedAt', 'deletedAt');
|
||||
Apply(this, o, String, 'handle', 'scope', 'redirectURI', 'validGrant');
|
||||
Apply(this, o, Boolean, 'enabled', 'trusted', 'canDeleteAuthClient', 'canGrant', 'canUpdateAuthClient');
|
||||
|
||||
|
||||
if (IsOf(o, 'meta')) {
|
||||
this.meta = { ...o.meta }
|
||||
}
|
||||
|
||||
if (IsOf(o, 'security')) {
|
||||
this.security = {
|
||||
...this.security,
|
||||
...o.security
|
||||
}
|
||||
}
|
||||
|
||||
Apply(this, o, CortezaID, 'createdBy', 'updatedBy', 'deletedBy');
|
||||
}
|
||||
|
||||
clone(): AuthClient {
|
||||
return new AuthClient(JSON.parse(JSON.stringify(this)));
|
||||
}
|
||||
}
|
||||
@@ -89,9 +89,9 @@ export class DalConnection {
|
||||
public updatedAt?: Date = undefined
|
||||
public deletedAt?: Date = undefined
|
||||
|
||||
public createdBy = ''
|
||||
public updatedBy = ''
|
||||
public deletedBy = ''
|
||||
public createdBy = NoID
|
||||
public updatedBy = NoID
|
||||
public deletedBy = NoID
|
||||
|
||||
public canDeleteConnection = false
|
||||
public canManageDalConfig = false
|
||||
@@ -112,7 +112,7 @@ export class DalConnection {
|
||||
}
|
||||
|
||||
if (IsOf(dc, 'config')) {
|
||||
this.config = { ...dc.config.privacy }
|
||||
this.config = { ...dc.config }
|
||||
|
||||
if (this.connectionID !== NoID && this.canManageDalConfig) {
|
||||
this.config = {
|
||||
@@ -124,6 +124,12 @@ export class DalConnection {
|
||||
},
|
||||
...dc.config,
|
||||
}
|
||||
|
||||
if (!this.config.privacy.sensitivityLevelID) {
|
||||
this.config.privacy = {
|
||||
sensitivityLevelID: NoID,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,4 +147,8 @@ export class DalConnection {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clone (): DalConnection {
|
||||
return new DalConnection(JSON.parse(JSON.stringify(this)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,10 @@ interface ReportScenario {
|
||||
export class Report {
|
||||
public reportID = NoID
|
||||
public handle = ''
|
||||
public meta: Meta = {}
|
||||
public meta: Meta = {
|
||||
name: '',
|
||||
description: '',
|
||||
}
|
||||
public sources: Array<ReportDataSource> = []
|
||||
public blocks: Array<Block> = []
|
||||
public scenarios: Array<ReportScenario> = []
|
||||
@@ -102,4 +105,8 @@ export class Report {
|
||||
'canRunReport',
|
||||
)
|
||||
}
|
||||
|
||||
clone (): Report {
|
||||
return new Report(JSON.parse(JSON.stringify(this)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,4 +100,8 @@ export class Role {
|
||||
get isContext (): boolean {
|
||||
return this.meta?.context?.expr?.length > 0
|
||||
}
|
||||
|
||||
clone (): Role {
|
||||
return new Role(JSON.parse(JSON.stringify(this)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,10 @@ export class Template {
|
||||
public language = ''
|
||||
public type = 'text/html'
|
||||
public partial = false
|
||||
public meta: Meta = {}
|
||||
public meta: Meta = {
|
||||
short: '',
|
||||
description: '',
|
||||
}
|
||||
public template = ''
|
||||
public labels: object = {}
|
||||
public ownerID = NoID
|
||||
@@ -63,4 +66,8 @@ export class Template {
|
||||
get resourceType (): string {
|
||||
return 'system:template'
|
||||
}
|
||||
|
||||
clone (): Template {
|
||||
return new Template(JSON.parse(JSON.stringify(this)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,4 +106,8 @@ export class User {
|
||||
this.userID,
|
||||
].join(' ').toLocaleLowerCase()
|
||||
}
|
||||
|
||||
clone (): User {
|
||||
return new User(JSON.parse(JSON.stringify(this)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,4 +47,5 @@ label:
|
||||
delete: Delete
|
||||
undelete: Undelete
|
||||
fileTypeNotAllowed: File type not allowed
|
||||
|
||||
editor:
|
||||
unsavedChanges: Unsaved changes will be lost. Do you wish to leave the page?
|
||||
|
||||
@@ -220,6 +220,7 @@ name: Chart name *
|
||||
handle: Handle
|
||||
generalSettings: General settings
|
||||
notification:
|
||||
unsavedChanges: Unsaved changes will be lost. Do you wish to leave the page?
|
||||
loadFailed: Could not load chart
|
||||
saveFailed: Could not save this chart
|
||||
saved: Chart saved
|
||||
|
||||
@@ -193,6 +193,7 @@ forModule:
|
||||
recordPage: Record page for module
|
||||
general:
|
||||
fields: Module fields
|
||||
unsavedChanges: Unsaved changes will be lost. Do you wish to leave the page?
|
||||
label:
|
||||
attributes: Attributes
|
||||
handle: Handle
|
||||
|
||||
@@ -58,6 +58,7 @@ manage:
|
||||
title: Manage namespaces
|
||||
list-view: Back to namespace list
|
||||
delete: Delete
|
||||
unsavedChanges: Unsaved changes will be lost. Do you wish to leave the page?
|
||||
|
||||
disabled: Disabled
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ moduleEdit: Edit module
|
||||
navigation:
|
||||
page: Pages
|
||||
viewPage: View Page
|
||||
unsavedChanges: Unsaved changes will be lost. Do you wish to leave the page?
|
||||
newPlaceholder: Page title
|
||||
noBlock: No block added yet
|
||||
noPages: No pages
|
||||
|
||||
@@ -73,6 +73,7 @@ select-all: Select all
|
||||
unselect-all: Unselect all
|
||||
value: Value
|
||||
name: Name
|
||||
unsaved-changes: Unsaved changes will be lost. Do you wish to leave the page?
|
||||
filter:
|
||||
operators:
|
||||
like: Like
|
||||
|
||||
@@ -5,6 +5,7 @@ name: Name
|
||||
name-with-star: 'Name*'
|
||||
new-report: New Report
|
||||
permissions: Permissions
|
||||
unsavedChanges: Unsaved changes will be lost. Do you wish to leave the page?
|
||||
report:
|
||||
create: Create Report
|
||||
builder: Report Builder
|
||||
|
||||
Reference in New Issue
Block a user