mirror of
https://github.com/smartyellow/status.git
synced 2025-06-28 04:35:11 +00:00
457
entities/webservice.js
Normal file
457
entities/webservice.js
Normal file
@ -0,0 +1,457 @@
|
||||
'use strict';
|
||||
|
||||
const { makeId } = require('core/makeid');
|
||||
|
||||
const states = {
|
||||
concept: 'concept',
|
||||
online: 'online',
|
||||
offline: 'offline',
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
format: 5,
|
||||
author: 'Romein van Buren',
|
||||
vendor: 'Smart Yellow',
|
||||
purpose: 'Store web services for use on a status page',
|
||||
store: 'webservices',
|
||||
|
||||
forms: ({ settings }) => ({
|
||||
default: {
|
||||
pages: [
|
||||
{ label: 'meta',
|
||||
sections: [
|
||||
'id',
|
||||
'state',
|
||||
'name',
|
||||
'tags',
|
||||
'channels',
|
||||
],
|
||||
},
|
||||
{ label: 'description',
|
||||
sections: [
|
||||
'summary',
|
||||
'visual',
|
||||
'body',
|
||||
],
|
||||
},
|
||||
{ label: 'auto testing',
|
||||
sections: [
|
||||
'autotestEnabled',
|
||||
'lastChecked',
|
||||
'autotest',
|
||||
],
|
||||
},
|
||||
{ label: 'statistics',
|
||||
sections: [
|
||||
'outageStats',
|
||||
'outageTable',
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
sections: {
|
||||
id: {
|
||||
label: 'id',
|
||||
fields: [
|
||||
{ key: 'id',
|
||||
editor: 'string',
|
||||
validator: '',
|
||||
visible: false,
|
||||
readonly: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
state: {
|
||||
label: 'status',
|
||||
fields: [
|
||||
{ key: 'state',
|
||||
editor: 'select',
|
||||
options: states,
|
||||
translate: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
name: {
|
||||
label: 'name',
|
||||
fields: [
|
||||
{ key: 'name',
|
||||
editor: 'string',
|
||||
placeholder: 'service name',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
tags: {
|
||||
label: 'tags',
|
||||
fields: [
|
||||
{ key: 'tags',
|
||||
editor: 'multiselect',
|
||||
placeholder: 'enter tags...',
|
||||
visible: !!(settings.serviceTags && Object.keys(settings.serviceTags).length),
|
||||
options: settings.serviceTags,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
channels: {
|
||||
label: 'channels',
|
||||
fields: [
|
||||
{ key: 'channels',
|
||||
editor: 'multiselect',
|
||||
placeholder: 'enter channels...',
|
||||
visible: !!(settings.channels && Object.keys(settings.channels).length),
|
||||
options: settings.channels,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
summary: {
|
||||
label: 'summary',
|
||||
fields: [
|
||||
{ key: 'summary',
|
||||
editor: 'text',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
body: {
|
||||
label: 'body',
|
||||
fields: [
|
||||
{ key: 'body',
|
||||
editor: 'text',
|
||||
type: 'string',
|
||||
localized: true,
|
||||
markup: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
visual: {
|
||||
label: 'visual',
|
||||
fields: [
|
||||
{ key: 'visual',
|
||||
editor: 'file',
|
||||
accept: [ 'image/*' ],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
lastChecked: {
|
||||
label: 'status last checked on',
|
||||
fields: [
|
||||
{ key: 'lastChecked',
|
||||
editor: 'date',
|
||||
readonly: true,
|
||||
format: 'datetime',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
outageStats: {
|
||||
label: 'number of outages',
|
||||
fields: [
|
||||
{ key: 'outages',
|
||||
editor: 'string',
|
||||
visible: ({ newEntity }) => !newEntity,
|
||||
readonly: true,
|
||||
placeholder: 'all outages',
|
||||
default: 0,
|
||||
},
|
||||
|
||||
{ key: 'outagesOpen',
|
||||
editor: 'string',
|
||||
visible: ({ newEntity }) => !newEntity,
|
||||
readonly: true,
|
||||
label: 'open',
|
||||
placeholder: '0',
|
||||
},
|
||||
|
||||
{ key: 'outagesResolved',
|
||||
editor: 'string',
|
||||
visible: ({ newEntity }) => !newEntity,
|
||||
readonly: true,
|
||||
label: 'closed',
|
||||
placeholder: '0',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
outageTable: {
|
||||
label: 'outage list',
|
||||
fields: [
|
||||
{ key: 'id',
|
||||
editor: 'smartyellow/outagetable',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
autotestEnabled: {
|
||||
label: 'autotesting enabled?',
|
||||
fields: [
|
||||
{ key: 'autotestEnabled',
|
||||
editor: 'checkbox',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
autotest: {
|
||||
label: 'endpoint requirements',
|
||||
fields: [
|
||||
{ key: 'autotest',
|
||||
editor: 'smartyellow/autotest',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
schema: ({ settings }) => ({
|
||||
id: {
|
||||
type: 'string',
|
||||
required: ({ newEntity }) => newEntity,
|
||||
lowercase: true,
|
||||
trim: true,
|
||||
filter: {
|
||||
title: 'id',
|
||||
match: '^[a-zA-Z0-9]{6}$',
|
||||
order: 999,
|
||||
},
|
||||
default: () => makeId(6),
|
||||
validate: async ({ newValues, oldValues, newEntity, storage }) => {
|
||||
if (newEntity) {
|
||||
const r = storage ? await storage.store('webdesq/blog').get(newValues.id) : null;
|
||||
return (r == null ? true : 'id already exists');
|
||||
}
|
||||
else {
|
||||
// ID cannot be changed if record was already created
|
||||
return (newValues.id == oldValues.id ? true : 'id cannot be changed');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
name: {
|
||||
type: 'stringset',
|
||||
trim: true,
|
||||
required: [ true, 'Name is missing!' ],
|
||||
filter: {
|
||||
title: 'title',
|
||||
match: '^[a-zA-Z0-9]*',
|
||||
localized: true,
|
||||
},
|
||||
format: {
|
||||
label: 'name',
|
||||
type: 'text',
|
||||
sortable: 'string',
|
||||
align: 'left',
|
||||
minWidth: 150,
|
||||
enabled: true,
|
||||
priority: 1,
|
||||
},
|
||||
},
|
||||
|
||||
state: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: 'concept',
|
||||
filter: {
|
||||
title: 'state',
|
||||
options: states,
|
||||
},
|
||||
format: {
|
||||
label: 'state',
|
||||
type: 'state',
|
||||
align: 'left',
|
||||
sortable: 'text',
|
||||
sorted: 'down',
|
||||
minWidth: 90,
|
||||
enabled: true,
|
||||
options: {
|
||||
concept: {
|
||||
name: 'concept',
|
||||
class: 'l2',
|
||||
},
|
||||
online: {
|
||||
name: 'online',
|
||||
class: 'l4',
|
||||
},
|
||||
offline: {
|
||||
name: 'offline',
|
||||
class: 'l1',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
tags: {
|
||||
type: 'array',
|
||||
of: 'string',
|
||||
default: [],
|
||||
filter: {
|
||||
title: 'tags',
|
||||
match: '^[a-z]',
|
||||
options: async ({ storage }) => (await storage.store('smartyellow/webservice').find({ 'log.deleted': { $exists: false } }, { keys: [ 'id', 'tags' ] }).sort({ name: 1 }).toArray())
|
||||
.reduce((acc, curr) => {
|
||||
if (curr.tags) {
|
||||
for (let i = 0; i < curr.tags.length; i++) {
|
||||
acc[curr.tags[i]] = curr.tags[i];
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, {}),
|
||||
},
|
||||
format: {
|
||||
type: 'text',
|
||||
label: 'tags',
|
||||
sortable: 'text',
|
||||
enabled: true,
|
||||
minWidth: 100,
|
||||
},
|
||||
},
|
||||
|
||||
channels: {
|
||||
type: 'array',
|
||||
of: 'string',
|
||||
default: () => {
|
||||
const keys = settings.channels ? Object.keys(settings.channels) : [];
|
||||
if (keys.length == 1) {
|
||||
return [ keys[0] ];
|
||||
}
|
||||
else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
filter: {
|
||||
title: 'channel',
|
||||
match: settings.channels && Object.keys(settings.channels).length ? '^[' + Object.keys(settings.channels).join('|') + ']' : null,
|
||||
},
|
||||
validate: async ({ newValues }) => (newValues.channels.every(key => !!settings.channels[key]) ? true : 'One or more invalid channels'),
|
||||
format: {
|
||||
label: 'channels',
|
||||
type: 'text',
|
||||
sortable: 'text',
|
||||
minWidth: 80,
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
|
||||
summary: {
|
||||
type: 'stringset',
|
||||
default: '',
|
||||
},
|
||||
|
||||
body: {
|
||||
type: 'stringset',
|
||||
default: '',
|
||||
filter: {
|
||||
title: 'message contains',
|
||||
match: '[a-z0-9A-Z]*',
|
||||
localized: true,
|
||||
},
|
||||
},
|
||||
|
||||
visual: {
|
||||
type: 'array',
|
||||
of: [ 'string' ],
|
||||
default: [],
|
||||
skip: true,
|
||||
onDataValid: async ({ newValues, storage, user }) => {
|
||||
newValues.visual = newValues.visual || [];
|
||||
for (let i = 0; i < newValues.visual.length; i++) {
|
||||
if (newValues.visual[i].data) {
|
||||
if (storage) {
|
||||
// If storage is available, insert the new file into storage and collect id
|
||||
const result = await storage({ user }).bucket('webdesq/media').insert({
|
||||
id: makeId(6),
|
||||
filename: newValues.visual[i].name,
|
||||
metadata: {
|
||||
contentType: newValues.visual[i].type,
|
||||
},
|
||||
}, newValues.visual[i].data)
|
||||
.catch(error => {
|
||||
if (error.code !== 'DUPLICATE_FILE') {
|
||||
throw error;
|
||||
}
|
||||
newValues.visual[i] = error.file.id;
|
||||
});
|
||||
if (result) {
|
||||
newValues.visual[i] = result.id;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If no storage is available, remove slot by setting it to null
|
||||
newValues.visual[i] = null;
|
||||
}
|
||||
}
|
||||
// remove empty slots in photo array
|
||||
newValues.visual = newValues.visual.filter(i => i != null);
|
||||
newValues.visual = [ ...new Set(newValues.visual) ];
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
outages: {
|
||||
type: 'computed',
|
||||
default: 0,
|
||||
format: {
|
||||
label: 'outages',
|
||||
type: 'number',
|
||||
align: 'left',
|
||||
sortable: 'number',
|
||||
minWidth: 100,
|
||||
enabled: true,
|
||||
},
|
||||
generator: async ({ values, storage, user }) => {
|
||||
const outages = await storage({ user })
|
||||
.store('smartyellow/webserviceoutage')
|
||||
.find({ services: values.id })
|
||||
.toArray();
|
||||
return outages.length || 0;
|
||||
},
|
||||
},
|
||||
|
||||
outagesOpen: {
|
||||
type: 'computed',
|
||||
default: 0,
|
||||
format: {
|
||||
label: 'open outages',
|
||||
type: 'number',
|
||||
align: 'left',
|
||||
sortable: 'number',
|
||||
minWidth: 100,
|
||||
enabled: true,
|
||||
},
|
||||
generator: async ({ values, storage, user }) => {
|
||||
const outages = await storage({ user })
|
||||
.store('smartyellow/webserviceoutage')
|
||||
.find({ services: values.id, resolved: false })
|
||||
.toArray();
|
||||
return outages.length || 0;
|
||||
},
|
||||
},
|
||||
|
||||
outagesResolved: {
|
||||
type: 'computed',
|
||||
default: 0,
|
||||
format: {
|
||||
label: 'resolved outages',
|
||||
type: 'number',
|
||||
align: 'left',
|
||||
sortable: 'number',
|
||||
minWidth: 100,
|
||||
enabled: true,
|
||||
},
|
||||
generator: async ({ values, storage, user }) => {
|
||||
const outages = await storage({ user })
|
||||
.store('smartyellow/webserviceoutage')
|
||||
.find({ services: values.id, resolved: true })
|
||||
.toArray();
|
||||
return outages.length || 0;
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
71
entities/webserviceheartbeat.js
Normal file
71
entities/webserviceheartbeat.js
Normal file
@ -0,0 +1,71 @@
|
||||
'use strict';
|
||||
|
||||
const { makeId } = require('core/makeid');
|
||||
|
||||
module.exports = {
|
||||
format: 5,
|
||||
author: 'Romein van Buren',
|
||||
vendor: 'Smart Yellow',
|
||||
purpose: 'Store the heartbeat of web services',
|
||||
store: 'webserviceheartbeat',
|
||||
|
||||
schema: () => ({
|
||||
id: {
|
||||
type: 'string',
|
||||
required: ({ newEntity }) => newEntity,
|
||||
lowercase: true,
|
||||
trim: true,
|
||||
filter: {
|
||||
title: 'id',
|
||||
match: '^[a-zA-Z0-9]{6}$',
|
||||
order: 999,
|
||||
},
|
||||
default: () => makeId(6),
|
||||
validate: async ({ newValues, oldValues, newEntity, storage }) => {
|
||||
if (newEntity) {
|
||||
const r = storage ? await storage.store('webdesq/blog').get(newValues.id) : null;
|
||||
return (r == null ? true : 'id already exists');
|
||||
}
|
||||
else {
|
||||
// ID cannot be changed if record was already created
|
||||
return (newValues.id == oldValues.id ? true : 'id cannot be changed');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
down: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
required: [ true, 'is the service up or down?' ],
|
||||
format: {
|
||||
type: 'state',
|
||||
options: {
|
||||
true: {
|
||||
name: 'yes',
|
||||
class: 'l1',
|
||||
},
|
||||
false: {
|
||||
name: 'no',
|
||||
class: 'l5',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
webservice: {
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: [ true, 'webservice id is missing' ],
|
||||
validate: async ({ storage, newValues }) => {
|
||||
const r = storage ? await storage.store('smartyellow/webservice').get(newValues.webservice) : null;
|
||||
return r == null ? 'service id does not exist' : true;
|
||||
},
|
||||
},
|
||||
|
||||
date: {
|
||||
type: 'date',
|
||||
default: '',
|
||||
required: [ true, 'date is missing' ],
|
||||
},
|
||||
}),
|
||||
};
|
424
entities/webserviceoutage.js
Normal file
424
entities/webserviceoutage.js
Normal file
@ -0,0 +1,424 @@
|
||||
'use strict';
|
||||
|
||||
const { makeId } = require('core/makeid');
|
||||
|
||||
const states = {
|
||||
concept: 'concept',
|
||||
online: 'online',
|
||||
offline: 'offline',
|
||||
};
|
||||
|
||||
const severity = {
|
||||
major: 'major outage',
|
||||
minor: 'minor outage',
|
||||
scheduled: 'scheduled maintenance',
|
||||
none: 'no impact on end user',
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
format: 5,
|
||||
author: 'Romein van Buren',
|
||||
vendor: 'Smart Yellow',
|
||||
purpose: 'Keep track of web service outage',
|
||||
store: 'webserviceoutage',
|
||||
|
||||
forms: ({ settings }) => ({
|
||||
default: {
|
||||
pages: [
|
||||
{ label: 'meta',
|
||||
sections: [
|
||||
'id',
|
||||
'name',
|
||||
'state',
|
||||
'severity',
|
||||
'resolved',
|
||||
'services',
|
||||
'tags',
|
||||
],
|
||||
},
|
||||
{ label: 'description',
|
||||
sections: [
|
||||
'summary',
|
||||
'visual',
|
||||
'body',
|
||||
],
|
||||
},
|
||||
{ label: 'updates',
|
||||
sections: [ 'updates' ],
|
||||
},
|
||||
{ label: 'internal notes',
|
||||
sections: [ 'notes' ],
|
||||
},
|
||||
],
|
||||
|
||||
sections: {
|
||||
id: {
|
||||
label: 'id',
|
||||
fields: [
|
||||
{ key: 'id',
|
||||
editor: 'string',
|
||||
validator: '',
|
||||
visible: false,
|
||||
readonly: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
state: {
|
||||
label: 'status',
|
||||
fields: [
|
||||
{ key: 'state',
|
||||
editor: 'select',
|
||||
options: states,
|
||||
translate: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
severity: {
|
||||
label: 'severity',
|
||||
fields: [
|
||||
{ key: 'severity',
|
||||
editor: 'select',
|
||||
options: severity,
|
||||
translate: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
resolved: {
|
||||
label: 'resolved?',
|
||||
fields: [
|
||||
{ key: 'resolved',
|
||||
editor: 'checkbox',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
services: {
|
||||
label: 'services',
|
||||
fields: [
|
||||
{ key: 'services',
|
||||
editor: 'multiselect',
|
||||
placeholder: 'Add one or more web services this outage message belongs to.',
|
||||
options: async ({ storage, user }) => await storage({ user })
|
||||
.store('smartyellow/webservice')
|
||||
.find({ 'log.deleted': { $exists: false } })
|
||||
.toObject('id', 'name'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
name: {
|
||||
label: 'name',
|
||||
fields: [
|
||||
{ key: 'name',
|
||||
editor: 'string',
|
||||
placeholder: 'a really brief description of the outage',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
tags: {
|
||||
label: 'tags',
|
||||
fields: [
|
||||
{ key: 'tags',
|
||||
editor: 'multiselect',
|
||||
placeholder: 'enter tags...',
|
||||
visible: !!(settings.outageTags && Object.keys(settings.outageTags).length),
|
||||
options: settings.outageTags,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
summary: {
|
||||
label: 'summary',
|
||||
fields: [
|
||||
{ key: 'summary',
|
||||
editor: 'text',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
body: {
|
||||
label: 'body',
|
||||
fields: [
|
||||
{ key: 'body',
|
||||
editor: 'text',
|
||||
type: 'string',
|
||||
localized: true,
|
||||
markup: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
visual: {
|
||||
label: 'visual',
|
||||
fields: [
|
||||
{ key: 'visual',
|
||||
editor: 'file',
|
||||
accept: [ 'image/*' ],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
updates: {
|
||||
label: 'updates',
|
||||
fields: [
|
||||
{ key: 'updates',
|
||||
editor: 'notes',
|
||||
userId: true,
|
||||
placeholder: 'updates',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
notes: {
|
||||
label: 'notes',
|
||||
fields: [
|
||||
{ key: 'notes',
|
||||
editor: 'notes',
|
||||
userId: true,
|
||||
//localized: true,
|
||||
placeholder: 'notes',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
schema: () => ({
|
||||
id: {
|
||||
type: 'string',
|
||||
required: ({ newEntity }) => newEntity,
|
||||
lowercase: true,
|
||||
trim: true,
|
||||
filter: {
|
||||
title: 'id',
|
||||
match: '^[a-zA-Z0-9]{6}$',
|
||||
order: 999,
|
||||
},
|
||||
default: () => makeId(6),
|
||||
validate: async ({ newValues, oldValues, newEntity, storage }) => {
|
||||
if (newEntity) {
|
||||
const r = storage ? await storage.store('webdesq/blog').get(newValues.id) : null;
|
||||
return (r == null ? true : 'id already exists');
|
||||
}
|
||||
else {
|
||||
// ID cannot be changed if record was already created
|
||||
return (newValues.id == oldValues.id ? true : 'id cannot be changed');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
name: {
|
||||
type: 'stringset',
|
||||
trim: true,
|
||||
required: [ true, 'Name is missing!' ],
|
||||
filter: {
|
||||
title: 'title',
|
||||
match: '^[a-zA-Z0-9]*',
|
||||
localized: true,
|
||||
},
|
||||
format: {
|
||||
label: 'name',
|
||||
type: 'text',
|
||||
sortable: 'string',
|
||||
align: 'left',
|
||||
minWidth: 150,
|
||||
enabled: true,
|
||||
priority: 1,
|
||||
},
|
||||
},
|
||||
|
||||
state: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: 'concept',
|
||||
filter: {
|
||||
title: 'state',
|
||||
options: states,
|
||||
},
|
||||
format: {
|
||||
label: 'state',
|
||||
type: 'state',
|
||||
align: 'left',
|
||||
sortable: 'text',
|
||||
sorted: 'down',
|
||||
minWidth: 90,
|
||||
enabled: true,
|
||||
options: {
|
||||
concept: {
|
||||
name: 'concept',
|
||||
class: 'l2',
|
||||
},
|
||||
online: {
|
||||
name: 'online',
|
||||
class: 'l4',
|
||||
},
|
||||
offline: {
|
||||
name: 'offline',
|
||||
class: 'l1',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
severity: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
filter: {
|
||||
title: 'severity',
|
||||
options: severity,
|
||||
},
|
||||
format: {
|
||||
label: 'severity',
|
||||
type: 'state',
|
||||
align: 'left',
|
||||
sortable: 'text',
|
||||
minWidth: 90,
|
||||
enabled: true,
|
||||
options: {
|
||||
major: {
|
||||
name: 'major',
|
||||
class: 'l1',
|
||||
},
|
||||
minor: {
|
||||
name: 'minor',
|
||||
class: 'l2',
|
||||
},
|
||||
scheduled: {
|
||||
name: 'scheduled',
|
||||
class: 'l3',
|
||||
},
|
||||
none: {
|
||||
name: 'no impact',
|
||||
class: 'l4',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
resolved: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
format: {
|
||||
type: 'checkbox',
|
||||
sortable: 'boolean',
|
||||
align: 'center',
|
||||
label: 'resolved?',
|
||||
minWidth: 90,
|
||||
enabled: true,
|
||||
|
||||
//type: 'state',
|
||||
//options: {
|
||||
// true: {
|
||||
// name: 'yes',
|
||||
// class: 'l5',
|
||||
// },
|
||||
// false: {
|
||||
// name: 'no',
|
||||
// class: 'l1',
|
||||
// },
|
||||
//},
|
||||
},
|
||||
},
|
||||
|
||||
tags: {
|
||||
type: 'array',
|
||||
of: 'string',
|
||||
default: [],
|
||||
filter: {
|
||||
title: 'tags',
|
||||
match: '^[a-z]',
|
||||
options: async ({ storage }) => (await storage.store('smartyellow/webserviceoutage').find({ 'log.deleted': { $exists: false } }, { keys: [ 'id', 'tags' ] }).sort({ name: 1 }).toArray())
|
||||
.reduce((acc, curr) => {
|
||||
if (curr.tags) {
|
||||
for (let i = 0; i < curr.tags.length; i++) {
|
||||
acc[curr.tags[i]] = curr.tags[i];
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, {}),
|
||||
},
|
||||
format: {
|
||||
type: 'text',
|
||||
label: 'tags',
|
||||
sortable: 'text',
|
||||
enabled: true,
|
||||
minWidth: 100,
|
||||
},
|
||||
},
|
||||
|
||||
summary: {
|
||||
type: 'stringset',
|
||||
default: '',
|
||||
},
|
||||
|
||||
body: {
|
||||
type: 'stringset',
|
||||
default: '',
|
||||
filter: {
|
||||
title: 'message contains',
|
||||
match: '[a-z0-9A-Z]*',
|
||||
localized: true,
|
||||
},
|
||||
},
|
||||
|
||||
visual: {
|
||||
type: 'array',
|
||||
of: [ 'string' ],
|
||||
default: [],
|
||||
skip: true,
|
||||
onDataValid: async ({ newValues, storage, user }) => {
|
||||
newValues.visual = newValues.visual || [];
|
||||
for (let i = 0; i < newValues.visual.length; i++) {
|
||||
if (newValues.visual[i].data) {
|
||||
if (storage) {
|
||||
// If storage is available, insert the new file into storage and collect id
|
||||
const result = await storage({ user }).bucket('webdesq/media').insert({
|
||||
id: makeId(6),
|
||||
filename: newValues.visual[i].name,
|
||||
metadata: {
|
||||
contentType: newValues.visual[i].type,
|
||||
},
|
||||
}, newValues.visual[i].data)
|
||||
.catch(error => {
|
||||
if (error.code !== 'DUPLICATE_FILE') {
|
||||
throw error;
|
||||
}
|
||||
newValues.visual[i] = error.file.id;
|
||||
});
|
||||
if (result) {
|
||||
newValues.visual[i] = result.id;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If no storage is available, remove slot by setting it to null
|
||||
newValues.visual[i] = null;
|
||||
}
|
||||
}
|
||||
// remove empty slots in photo array
|
||||
newValues.visual = newValues.visual.filter(i => i != null);
|
||||
newValues.visual = [ ...new Set(newValues.visual) ];
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
notes: {
|
||||
type: 'array',
|
||||
of: {},
|
||||
default: [],
|
||||
},
|
||||
|
||||
updates: {
|
||||
type: 'array',
|
||||
of: {},
|
||||
default: [],
|
||||
},
|
||||
}),
|
||||
};
|
Reference in New Issue
Block a user