Flesh out Cobalt, LDAP groups, &c.

This commit is contained in:
garrettmills
2020-05-11 20:26:09 -05:00
parent c389e151b5
commit 6f621f5891
34 changed files with 1508 additions and 31 deletions

View 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() {}
}

View File

@@ -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()
}
}
}