Flesh out Cobalt, LDAP groups, &c.
This commit is contained in:
198
app/assets/app/cobalt/Form.component.js
Normal file
198
app/assets/app/cobalt/Form.component.js
Normal file
@@ -0,0 +1,198 @@
|
||||
import { Component } from '../../lib/vues6/vues6.js'
|
||||
import { utility } from '../service/Utility.service.js'
|
||||
import { location_service } from '../service/Location.service.js'
|
||||
import { resource_service } from '../service/Resource.service.js'
|
||||
import { action_service } from '../service/Action.service.js'
|
||||
|
||||
const template = `
|
||||
<div class="card col-12 col-sm-10 offset-sm-1 col-md-8 offset-md-2 col-xl-6 offset-xl-3">
|
||||
<h3 class="card-title mb-4 mt-4">
|
||||
<button class="btn-lg btn" @click="back"><i class="fa fa-arrow-left"></i></button> {{ title }}
|
||||
</h3>
|
||||
<form v-on:submit.prevent="do_nothing" v-if="is_ready">
|
||||
<div class="form-group" v-for="field of definition.fields">
|
||||
<span v-if="field.type.startsWith('select')">
|
||||
<label :for="uuid+field.field">{{ field.name }}</label>
|
||||
<select
|
||||
:id="uuid+field.field"
|
||||
class="form-control"
|
||||
v-model="data[field.field]"
|
||||
:required="field.required"
|
||||
:readonly="mode === 'view'"
|
||||
:multiple="!!field.type.endsWith('.multiple')"
|
||||
>
|
||||
<option v-for="option of field.options" :value="option.value">{{ typeof option.display === 'function' ? option.display(option) : option.display }}</option>
|
||||
</select>
|
||||
<small class="form-text" style="color: darkred;" v-if="field.error">{{ field.error }}</small>
|
||||
</span>
|
||||
<span v-if="field.type === 'text'">
|
||||
<label :for="uuid+field.field">{{ field.name }}</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
:id="uuid+field.field"
|
||||
v-model="data[field.field]"
|
||||
:required="field.required"
|
||||
:placeholder="field.placeholder"
|
||||
:readonly="mode === 'view'"
|
||||
ref="input"
|
||||
>
|
||||
<small class="form-text" style="color: darkred;" v-if="field.error">{{ field.error }}</small>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<div class="col-12 text-right mb-4 mr-0 mt-2">
|
||||
<span style="color: darkred;" class="font-italic mr-3" v-if="error_message">{{ error_message }}</span>
|
||||
<span class="font-italic mr-3 text-muted" v-if="other_message">{{ other_message }}</span>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
type="button"
|
||||
v-if="mode !== 'view'"
|
||||
@click="save_click"
|
||||
>Save</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
const title_map = {
|
||||
insert: 'Create',
|
||||
update: 'Edit',
|
||||
view: 'View',
|
||||
}
|
||||
|
||||
export default class FormComponent extends Component {
|
||||
static get selector() {
|
||||
return 'cobalt-form'
|
||||
}
|
||||
|
||||
static get template() {
|
||||
return template
|
||||
}
|
||||
|
||||
static get props() {
|
||||
return ['resource', 'form_id', 'initial_mode']
|
||||
}
|
||||
|
||||
definition = {}
|
||||
data = {}
|
||||
uuid = ''
|
||||
title = ''
|
||||
error_message = ''
|
||||
other_message = ''
|
||||
|
||||
is_ready = false
|
||||
mode = ''
|
||||
id = ''
|
||||
|
||||
reset() {
|
||||
this.definition = {}
|
||||
this.data = {}
|
||||
this.uuid = ''
|
||||
this.title = ''
|
||||
this.error_message = ''
|
||||
this.other_message = ''
|
||||
this.is_ready = false
|
||||
}
|
||||
|
||||
async vue_on_create(internal = false) {
|
||||
if ( !internal ) {
|
||||
this.mode = this.initial_mode
|
||||
this.id = this.form_id
|
||||
this.resource_class = await resource_service.get(this.resource)
|
||||
} else {
|
||||
this.reset()
|
||||
}
|
||||
|
||||
this.uuid = utility.uuid()
|
||||
await this.load()
|
||||
await this.init()
|
||||
}
|
||||
|
||||
async init() {
|
||||
for ( const field of this.definition.fields ) {
|
||||
if ( field.type.startsWith('select.dynamic') ) {
|
||||
field._options = field._options || field.options
|
||||
const rsc = await resource_service.get(field._options.resource)
|
||||
|
||||
field.options = (await rsc.list()).map(item => {
|
||||
return {
|
||||
display: typeof field._options.display === 'function' ? field._options.display(item) : item[field._options.display || 'display'],
|
||||
value: typeof field._options.value === 'function' ? field._options.value(item) : item[field._options.value || 'display'],
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if ( field.type.endsWith('.multiple') ) {
|
||||
this.data[field.field] = []
|
||||
}
|
||||
}
|
||||
|
||||
this.is_ready = true
|
||||
this.$nextTick(() => {
|
||||
if ( this.mode !== 'view' ) this.$refs.input[0].focus()
|
||||
})
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.definition = this.resource_class.form_definition
|
||||
if (this.mode !== 'insert') {
|
||||
this.data = await this.resource_class.get(this.id)
|
||||
}
|
||||
|
||||
this.title = title_map[this.mode] + ' ' + this.resource_class.item
|
||||
}
|
||||
|
||||
async on_create() {
|
||||
this.id = this.data.id
|
||||
this.mode = 'update'
|
||||
await this.vue_on_create(true)
|
||||
location_service.set_query(`mode=update&id=${this.id}`)
|
||||
}
|
||||
|
||||
async save_click() {
|
||||
if ( !this.validate() ) return
|
||||
try {
|
||||
if (this.mode === 'insert') {
|
||||
this.data = await this.resource_class.create(this.data)
|
||||
await this.on_create()
|
||||
} else if (this.mode === 'update') {
|
||||
await this.resource_class.update(this.id, this.data)
|
||||
}
|
||||
|
||||
this.error_message = ''
|
||||
this.other_message = `The ${this.resource_class.item} was saved.`
|
||||
} catch (e) {
|
||||
if ( e.response && e.response.data && e.response.data.message )
|
||||
this.error_message = e.response.data.message
|
||||
else this.error_message = 'An unknown error occurred while saving the form.'
|
||||
}
|
||||
}
|
||||
|
||||
validate() {
|
||||
let valid = true
|
||||
for ( const field of this.definition.fields ) {
|
||||
if ( field.required && (!(field.field in this.data) || !this.data[field.field]) ) {
|
||||
field.error = 'This field is required.'
|
||||
valid = false
|
||||
} else {
|
||||
field.error = ''
|
||||
}
|
||||
}
|
||||
|
||||
this.$forceUpdate()
|
||||
|
||||
return valid
|
||||
}
|
||||
|
||||
back() {
|
||||
return action_service.perform({
|
||||
text: '',
|
||||
type: 'resource',
|
||||
resource: this.resource,
|
||||
action: 'list',
|
||||
})
|
||||
}
|
||||
|
||||
do_nothing() {}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,45 @@
|
||||
import { Component } from '../../lib/vues6/vues6.js'
|
||||
import { action_service } from '../service/Action.service.js'
|
||||
import { message_service } from '../service/Message.service.js'
|
||||
|
||||
const template = `
|
||||
<div>
|
||||
<h2 class="mb-4" v-if="definition.title">{{ definition.title }}</h2>
|
||||
<div class="row mb-4">
|
||||
<div class="col-10"><h3>{{ resource_class.plural }}</h3></div>
|
||||
<div class="col-2 text-right" v-if="definition.actions">
|
||||
<button
|
||||
:class="['btn', 'btn-'+(action.color || 'secondary'), 'btn-sm']"
|
||||
type="button"
|
||||
v-for="action of definition.actions"
|
||||
@click="perform($event, action)"
|
||||
v-if="action.position === 'main'"
|
||||
>{{ action.text }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col" v-for="col of definition.columns">{{ col.name }}</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, index) of definition.data">
|
||||
<tr v-for="(row, index) of data">
|
||||
<th scope="row">{{ index + 1 }}</th>
|
||||
<td v-for="col of definition.columns">
|
||||
<span v-if="col.renderer === 'boolean'">{{ col.field ? 'Yes' : 'No' }}</span>
|
||||
<span v-if="col.renderer !== 'boolean'">{{ col.field in row ? row[col.field] : '-' }}</span>
|
||||
<span v-if="typeof col.renderer === 'function'">{{ col.renderer(row[col.field]) }}</span>
|
||||
<span v-if="col.renderer === 'boolean'">{{ row[col.field] ? 'Yes' : 'No' }}</span>
|
||||
<span v-if="col.renderer !== 'boolean' && typeof col.renderer !== 'function'">{{ col.field in row ? row[col.field] : '-' }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
:class="['mr-2', 'btn', 'btn-sm', 'btn-'+(action.color || 'secondary')]"
|
||||
v-for="action of definition.actions"
|
||||
v-if="action.position === 'row'"
|
||||
@click="perform($event, action, row)"
|
||||
><i :class="action.icon" v-if="action.icon"></i> {{ action.text }}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -26,5 +50,55 @@ const template = `
|
||||
export default class ListingComponent extends Component {
|
||||
static get selector() { return 'cobalt-listing' }
|
||||
static get template() { return template }
|
||||
static get props() { return ['definition'] }
|
||||
static get props() { return ['resource'] }
|
||||
|
||||
definition = {}
|
||||
data = []
|
||||
resource_class = {}
|
||||
|
||||
async vue_on_create() {
|
||||
// Load the resource
|
||||
const resource_mod = await import(`../resource/${this.resource}.resource.js`)
|
||||
if ( !resource_mod )
|
||||
throw new Error('Unable to load Cobalt listing resource.')
|
||||
|
||||
const rsc_name = this.resource.toLowerCase().replace(/\//g, '_')
|
||||
if ( !resource_mod[rsc_name] )
|
||||
throw new Error('Unable to extract resource object from module.')
|
||||
|
||||
this.resource_class = resource_mod[rsc_name]
|
||||
await this.load()
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.definition = this.resource_class.listing_definition
|
||||
this.data = await this.resource_class.list()
|
||||
}
|
||||
|
||||
async perform($event, action, row = undefined) {
|
||||
if ( action.confirm ) {
|
||||
message_service.modal({
|
||||
title: 'Are you sure?',
|
||||
message: `You are about to ${action.action}${row ? ' this '+this.resource_class.item : ''}. Do you want to continue?`,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Cancel',
|
||||
type: 'close',
|
||||
},
|
||||
{
|
||||
text: 'Continue',
|
||||
class: ['btn', 'btn-primary'],
|
||||
type: 'close',
|
||||
on_click: async () => {
|
||||
await action_service.perform({...action, resource: this.resource, ...(row ? {id: row.id} : {})})
|
||||
await this.load()
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
} else {
|
||||
await action_service.perform({...action, resource: this.resource, ...(row ? {id: row.id} : {})})
|
||||
await this.load()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import EditProfileComponent from './dash/profile/EditProfile.component.js'
|
||||
import AppPasswordFormComponent from './dash/profile/form/AppPassword.component.js'
|
||||
|
||||
import ListingComponent from './cobalt/Listing.component.js'
|
||||
import FormComponent from './cobalt/Form.component.js'
|
||||
|
||||
const dash_components = {
|
||||
SideBarComponent,
|
||||
@@ -14,6 +15,7 @@ const dash_components = {
|
||||
AppPasswordFormComponent,
|
||||
|
||||
ListingComponent,
|
||||
FormComponent,
|
||||
}
|
||||
|
||||
export { dash_components }
|
||||
|
||||
@@ -37,17 +37,17 @@ export default class SideBarComponent extends Component {
|
||||
{
|
||||
text: 'Groups',
|
||||
action: 'redirect',
|
||||
next: '/dash/groups',
|
||||
next: '/dash/c/listing/ldap/Group',
|
||||
},
|
||||
{
|
||||
text: 'LDAP Clients',
|
||||
action: 'redirect',
|
||||
next: '/dash/ldap/clients',
|
||||
next: '/dash/c/listing/ldap/Client',
|
||||
},
|
||||
{
|
||||
text: 'SAML Service Providers',
|
||||
action: 'redirect',
|
||||
next: '/dash/saml/service-providers',
|
||||
next: '/dash/c/listing/saml/Provider',
|
||||
},
|
||||
{
|
||||
text: 'Settings',
|
||||
|
||||
@@ -26,7 +26,7 @@ const template = `
|
||||
aria-hidden="true"
|
||||
ref="modal"
|
||||
>
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{{ modal.title }}</h5>
|
||||
|
||||
5
app/assets/app/resource/APIParseError.js
Normal file
5
app/assets/app/resource/APIParseError.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export default class APIParseError extends Error {
|
||||
constructor(msg = 'An unknown error occurred while parsing the API response.') {
|
||||
super(msg)
|
||||
}
|
||||
}
|
||||
55
app/assets/app/resource/CRUDBase.js
Normal file
55
app/assets/app/resource/CRUDBase.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import APIParseError from './APIParseError.js'
|
||||
|
||||
export default class CRUDBase {
|
||||
endpoint = '/api/v1'
|
||||
required_fields = []
|
||||
|
||||
listing_definition = {}
|
||||
form_definition = {}
|
||||
|
||||
item = ''
|
||||
plural = ''
|
||||
|
||||
async list() {
|
||||
const results = await axios.get(this._endpoint())
|
||||
if ( results && results.data && Array.isArray(results.data.data) ) return results.data.data
|
||||
else throw new APIParseError()
|
||||
}
|
||||
|
||||
async get(id) {
|
||||
const results = await axios.get(this._endpoint(id))
|
||||
if ( results && results.data && results.data.data ) return results.data.data
|
||||
else throw new APIParseError()
|
||||
}
|
||||
|
||||
async create(properties) {
|
||||
for ( const field of this.required_fields ) {
|
||||
if ( !properties[field] ) throw new Error(`Missing required field: ${field}`)
|
||||
}
|
||||
|
||||
const results = await axios.post(this._endpoint(), properties)
|
||||
if ( results && results.data && results.data.data ) return results.data.data
|
||||
else throw new APIParseError()
|
||||
}
|
||||
|
||||
async update(id, properties) {
|
||||
for ( const field of this.required_fields ) {
|
||||
if ( !properties[field] ) throw new Error(`Missing required field: ${field}`)
|
||||
}
|
||||
|
||||
await axios.patch(this._endpoint(id), properties)
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
await axios.delete(this._endpoint(id))
|
||||
}
|
||||
|
||||
_endpoint(sub = '/') {
|
||||
let first = this.endpoint
|
||||
if ( !first.startsWith('/') ) first = `/${first}`
|
||||
if ( first.endsWith('/') ) first = first.slice(0, -1)
|
||||
|
||||
if ( !sub.startsWith('/') ) sub = `/${sub}`
|
||||
return `${first}${sub}`
|
||||
}
|
||||
}
|
||||
12
app/assets/app/resource/auth/Role.resource.js
Normal file
12
app/assets/app/resource/auth/Role.resource.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import CRUDBase from '../CRUDBase.js'
|
||||
|
||||
class RoleResource extends CRUDBase {
|
||||
endpoint = '/api/v1/auth/roles'
|
||||
required_fields = ['role', 'permissions']
|
||||
|
||||
item = 'Role'
|
||||
plural = 'Roles'
|
||||
}
|
||||
|
||||
const auth_role = new RoleResource()
|
||||
export { auth_role }
|
||||
12
app/assets/app/resource/auth/User.resource.js
Normal file
12
app/assets/app/resource/auth/User.resource.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import CRUDBase from '../CRUDBase.js'
|
||||
|
||||
class UserResource extends CRUDBase {
|
||||
endpoint = '/api/v1/auth/users'
|
||||
required_fields = ['uid', 'first_name', 'last_name', 'email', 'password']
|
||||
|
||||
item = 'User'
|
||||
plural = 'Users'
|
||||
}
|
||||
|
||||
const auth_user = new UserResource()
|
||||
export { auth_user }
|
||||
66
app/assets/app/resource/ldap/Client.resource.js
Normal file
66
app/assets/app/resource/ldap/Client.resource.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import CRUDBase from '../CRUDBase.js'
|
||||
|
||||
class ClientResource extends CRUDBase {
|
||||
endpoint = '/api/v1/ldap/clients'
|
||||
required_fields = ['name', 'uid', 'password']
|
||||
|
||||
item = 'LDAP Client'
|
||||
plural = 'LDAP Clients'
|
||||
|
||||
listing_definition = {
|
||||
columns: [
|
||||
{
|
||||
name: 'Client Name',
|
||||
field: 'name',
|
||||
},
|
||||
{
|
||||
name: 'User ID',
|
||||
field: 'uid',
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
type: 'resource',
|
||||
position: 'main',
|
||||
action: 'insert',
|
||||
text: 'Create New',
|
||||
color: 'success',
|
||||
},
|
||||
{
|
||||
type: 'resource',
|
||||
position: 'row',
|
||||
action: 'update',
|
||||
icon: 'fa fa-edit',
|
||||
color: 'primary',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
form_definition = {
|
||||
fields: [
|
||||
{
|
||||
name: 'Provider Name',
|
||||
field: 'name',
|
||||
placeholder: 'Awesome External App',
|
||||
required: true,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'User ID',
|
||||
field: 'uid',
|
||||
placeholder: 'some_username',
|
||||
required: true,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'Password',
|
||||
field: 'password',
|
||||
required: ['insert'],
|
||||
type: 'password',
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
const ldap_client = new ClientResource()
|
||||
export { ldap_client }
|
||||
98
app/assets/app/resource/ldap/Group.resource.js
Normal file
98
app/assets/app/resource/ldap/Group.resource.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import CRUDBase from '../CRUDBase.js'
|
||||
|
||||
class GroupResource extends CRUDBase {
|
||||
endpoint = '/api/v1/ldap/groups'
|
||||
required_fields = ['name', 'role']
|
||||
|
||||
item = 'LDAP Group'
|
||||
plural = 'LDAP Groups'
|
||||
|
||||
listing_definition = {
|
||||
columns: [
|
||||
{
|
||||
name: 'Group Name',
|
||||
field: 'name',
|
||||
},
|
||||
{
|
||||
name: 'Role',
|
||||
field: 'role',
|
||||
},
|
||||
{
|
||||
name: '# of Users',
|
||||
field: 'user_ids',
|
||||
renderer: (user_ids) => Array.isArray(user_ids) ? user_ids.length : 0,
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
type: 'resource',
|
||||
position: 'main',
|
||||
action: 'insert',
|
||||
text: 'Create New',
|
||||
color: 'success',
|
||||
},
|
||||
{
|
||||
type: 'resource',
|
||||
position: 'row',
|
||||
action: 'update',
|
||||
icon: 'fa fa-edit',
|
||||
color: 'primary',
|
||||
},
|
||||
{
|
||||
type: 'resource',
|
||||
position: 'row',
|
||||
action: 'delete',
|
||||
icon: 'fa fa-times',
|
||||
color: 'danger',
|
||||
confirm: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
form_definition = {
|
||||
// back_action: {
|
||||
// text: 'Back',
|
||||
// action: 'back',
|
||||
// },
|
||||
fields: [
|
||||
{
|
||||
name: 'Group Name',
|
||||
field: 'name',
|
||||
placeholder: 'External App Users',
|
||||
required: true,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'Role',
|
||||
field: 'role',
|
||||
placeholder: 'external_app',
|
||||
required: true,
|
||||
type: 'select.dynamic',
|
||||
options: {
|
||||
resource: 'auth/Role',
|
||||
display: 'role',
|
||||
value: 'role',
|
||||
},
|
||||
// options: [
|
||||
// { value: 1, display: 'One' },
|
||||
// { value: 2, display: 'Two' },
|
||||
// { value: 3, display: 'Three' },
|
||||
// ],
|
||||
},
|
||||
{
|
||||
name: 'Users',
|
||||
field: 'user_ids',
|
||||
placeholder: 'John Doe',
|
||||
type: 'select.dynamic.multiple',
|
||||
options: {
|
||||
resource: 'auth/User',
|
||||
display: (user) => `${user.last_name}, ${user.first_name} (${user.uid})`,
|
||||
value: 'id',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
const ldap_group = new GroupResource()
|
||||
export { ldap_group }
|
||||
82
app/assets/app/resource/saml/Provider.resource.js
Normal file
82
app/assets/app/resource/saml/Provider.resource.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import CRUDBase from '../CRUDBase.js'
|
||||
|
||||
class ProviderResource extends CRUDBase {
|
||||
endpoint = '/api/v1/saml/providers'
|
||||
required_fields = ['name', 'acs_url', 'entity_id']
|
||||
|
||||
item = 'SAML Service Provider'
|
||||
plural = 'SAML Service Providers'
|
||||
|
||||
listing_definition = {
|
||||
columns: [
|
||||
{
|
||||
name: 'Provider Name',
|
||||
field: 'name',
|
||||
},
|
||||
{
|
||||
name: 'Entity ID',
|
||||
field: 'entity_id',
|
||||
},
|
||||
{
|
||||
name: 'Has SLO?',
|
||||
field: 'slo_url',
|
||||
renderer: 'boolean',
|
||||
},
|
||||
{
|
||||
name: 'ACS URL',
|
||||
field: 'acs_url',
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
type: 'resource',
|
||||
position: 'main',
|
||||
action: 'insert',
|
||||
text: 'Create New',
|
||||
color: 'success',
|
||||
},
|
||||
{
|
||||
type: 'resource',
|
||||
position: 'row',
|
||||
action: 'update',
|
||||
icon: 'fa fa-edit',
|
||||
color: 'primary',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
form_definition = {
|
||||
fields: [
|
||||
{
|
||||
name: 'Provider Name',
|
||||
field: 'name',
|
||||
placeholder: 'Awesome External App',
|
||||
required: true,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'Entity ID',
|
||||
field: 'entity_id',
|
||||
placeholder: 'https://my.awesome.app/saml/metadata.xml',
|
||||
required: true,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'Assertion Consumer Service URL',
|
||||
field: 'acs_url',
|
||||
placeholder: 'https://my.awesome.app/saml/acs',
|
||||
required: true,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'Single-Logout URL',
|
||||
field: 'slo_url',
|
||||
placeholder: 'https://my.awesome.app/saml/logout',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
const saml_provider = new ProviderResource()
|
||||
export { saml_provider }
|
||||
@@ -1,10 +1,27 @@
|
||||
import { location_service } from './Location.service.js'
|
||||
import { resource_service } from './Resource.service.js'
|
||||
|
||||
class ActionService {
|
||||
async perform({ text, action, ...args }) {
|
||||
async perform({ text = '', action, ...args }) {
|
||||
if ( action === 'redirect' ) {
|
||||
if ( args.next ) {
|
||||
return location_service.redirect(args.next, args.delay || 1500)
|
||||
return location_service.redirect(args.next, args.delay || 0)
|
||||
}
|
||||
} else if ( action === 'back' ) {
|
||||
return location_service.back()
|
||||
} else if ( args.type === 'resource' ) {
|
||||
const { resource } = args
|
||||
if ( action === 'insert' ) {
|
||||
return location_service.redirect(`/dash/c/form/${resource}`, 0)
|
||||
} else if ( action === 'update' ) {
|
||||
const { id } = args
|
||||
return location_service.redirect(`/dash/c/form/${resource}?id=${id}`, 0)
|
||||
} else if ( action === 'delete' ) {
|
||||
const { id } = args
|
||||
const rsc = await resource_service.get(resource)
|
||||
await rsc.delete(id)
|
||||
} else if ( action === 'list' ) {
|
||||
return location_service.redirect(`/dash/c/listing/${resource}`, 0)
|
||||
}
|
||||
} else {
|
||||
throw new TypeError(`Unknown action type: ${action}`)
|
||||
|
||||
@@ -20,6 +20,13 @@ class LocationService {
|
||||
}, delay)
|
||||
})
|
||||
}
|
||||
|
||||
set_query(query) {
|
||||
if (history.pushState) {
|
||||
const new_url = window.location.protocol + "//" + window.location.host + window.location.pathname + '?' + query;
|
||||
window.history.pushState({path: new_url}, '', new_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const location_service = new LocationService()
|
||||
|
||||
18
app/assets/app/service/Resource.service.js
Normal file
18
app/assets/app/service/Resource.service.js
Normal file
@@ -0,0 +1,18 @@
|
||||
class ResourceService {
|
||||
async get(name) {
|
||||
const resource_mod = await import(`../resource/${name}.resource.js`)
|
||||
if ( !resource_mod ) throw new Error(`Unable to fetch resource ${name}.`)
|
||||
|
||||
if ( !this.object_name(name) in resource_mod )
|
||||
throw new Error(`Unable to retrieve resource from module (${this.object_name(name)}).`)
|
||||
|
||||
return resource_mod[this.object_name(name)]
|
||||
}
|
||||
|
||||
object_name(name) {
|
||||
return name.toLowerCase().replace(/\//g, '_')
|
||||
}
|
||||
}
|
||||
|
||||
const resource_service = new ResourceService()
|
||||
export { resource_service }
|
||||
Reference in New Issue
Block a user