241 lines
7.9 KiB
JavaScript
241 lines
7.9 KiB
JavaScript
import jQuery from 'jquery'
|
|
import riot from 'riot'
|
|
|
|
import 'riot-hot-reload'
|
|
|
|
import './edit.tag.pug'
|
|
import './postgresql.tag.pug'
|
|
import './help-edit.tag.pug'
|
|
import './help-general.tag.pug'
|
|
import './postgresqls.tag.pug'
|
|
import './logs.tag.pug'
|
|
import './new.tag.pug'
|
|
import './status.tag.pug'
|
|
import './app.tag.pug'
|
|
import './restore.tag.pug'
|
|
|
|
Object.fromEntries = entries => entries.length === 0 ? {} : Object.assign(...entries.map(([k, v]) => ({[k]: v})))
|
|
Object.mapValues = (o, f) => Object.fromEntries(Object.entries(o).map(([k, v]) => [k, f(v, k)]))
|
|
Object.mapEntries = (o, f) => Object.fromEntries(Object.entries(o).map(f).filter(x => x))
|
|
Object.filterEntries = (o, f) => Object.mapEntries(o, entry => f(entry) && entry)
|
|
Object.filterValues = (o, f) => Object.filterEntries(o, ([key, value]) => f(value) && [key, value])
|
|
|
|
|
|
const getDefaulting = (object, key, def) => (
|
|
object.hasOwnProperty(key) ? object[key] : def
|
|
)
|
|
|
|
|
|
const Dynamic = (options={}) => {
|
|
const instance = {
|
|
init: getDefaulting(options, 'init', () => ''),
|
|
refresh: getDefaulting(options, 'refresh', () => true),
|
|
update: getDefaulting(options, 'update', value => (instance.state = value, true)),
|
|
validState: getDefaulting(options, 'validState', state => (
|
|
state !== undefined &&
|
|
state !== null &&
|
|
typeof state === 'string' &&
|
|
state.length > 0
|
|
)),
|
|
|
|
edit: event => (instance.update(event.target.value, instance, event), true),
|
|
valid: () => instance.validState(instance.state),
|
|
}
|
|
|
|
instance.state = instance.init()
|
|
return instance
|
|
}
|
|
|
|
|
|
/*
|
|
Dynamics manages a dynamic array whose elements are themselves Dynamic objects.
|
|
|
|
The default initializer builds an empty array as the initial state.
|
|
|
|
The "add" DOM event callback is provided to add a newly initialized Dynamic
|
|
object to the end of the state array. The Dynamic array item is initialized
|
|
with the "itemInit" callback, which can be specified with a constructor option
|
|
and defaults to creating a Dynamic with all default options.
|
|
|
|
The "remove" DOM event callback is provided to handle DOM events that should
|
|
or remove a specific item. For events on elements generated by iterating the
|
|
state with an each= attribute, the event.item will be set to the correct value.
|
|
|
|
The refresh callback is forwarded to all constituent Dynamic objects.
|
|
*/
|
|
const Dynamics = (options={}) => {
|
|
const instance = Object.assign(
|
|
Dynamic(
|
|
Object.assign(
|
|
{ init: () => [] },
|
|
'refresh' in options
|
|
? { refresh: options.refresh }
|
|
: undefined
|
|
)
|
|
),
|
|
|
|
{
|
|
itemInit: getDefaulting(options, 'itemInit', () =>
|
|
Dynamic(
|
|
'refresh' in options
|
|
? { refresh: options.refresh }
|
|
: {}
|
|
)
|
|
),
|
|
itemValid: getDefaulting(options, 'itemValid', item => item.valid()),
|
|
validState: state => state.every(instance.itemValid),
|
|
update: () => true,
|
|
edit: () => true,
|
|
|
|
add: _event => {
|
|
instance.state.push(instance.itemInit())
|
|
instance.refresh()
|
|
return true
|
|
},
|
|
|
|
remove: event => {
|
|
instance.state.splice(instance.state.indexOf(event.item), 1)
|
|
instance.refresh()
|
|
return true
|
|
},
|
|
}
|
|
)
|
|
|
|
Object.defineProperty(instance, 'valids', { get: () =>
|
|
instance.state.filter(instance.itemValid)
|
|
})
|
|
|
|
return instance
|
|
}
|
|
|
|
|
|
/*
|
|
DynamicSet manages a keyed collection of Dynamic objects. The constructor
|
|
receives an object mapping keys to initialization functions, and its state is a
|
|
mapping from the same keys to Dynamics initialized using the corresponding
|
|
key's initialization function. A DynamicSet is valid when its constituent
|
|
Dynamics are all simultaneously valid. The refresh callback is forwarded to
|
|
all constituent Dynamic objects.
|
|
|
|
Example:
|
|
|
|
DynamicSet({
|
|
foo: undefined,
|
|
bar: () => 'baz',
|
|
})
|
|
|
|
This call would create a DynamicSet with two constituent dynamics in its state:
|
|
one of them under the 'foo' key of the state object, built with the default
|
|
Dynamic initializer, and another under the 'bar' key of the state object, whose
|
|
state, in turn, would initially hold the value 'baz'.
|
|
*/
|
|
const DynamicSet = (items, options={}) => Object.assign(
|
|
Dynamic(
|
|
Object.assign(
|
|
{
|
|
init: () => Object.mapValues(items, init =>
|
|
Dynamic(
|
|
Object.assign(
|
|
init ? { init: init } : undefined,
|
|
'refresh' in options ? { refresh: options.refresh } : undefined
|
|
)
|
|
)
|
|
),
|
|
},
|
|
'refresh' in options
|
|
? { refresh: options.refresh }
|
|
: undefined
|
|
)
|
|
),
|
|
|
|
{
|
|
items: items,
|
|
validState: state => Object.values(state).every(item => item.valid()),
|
|
edit: () => true,
|
|
update: () => true,
|
|
}
|
|
)
|
|
|
|
|
|
const delete_cluster = (namespace, clustername) => {
|
|
jQuery.confirm({
|
|
backgroundDismiss: true,
|
|
content: `
|
|
<p>
|
|
Are you sure you want to remove this PostgreSQL cluster? If so,
|
|
please <strong>type the cluster name here
|
|
(<code>${namespace}/${clustername}</code>)</strong> and click the
|
|
confirm button:
|
|
</p>
|
|
<input
|
|
type="text"
|
|
class="confirm-delete"
|
|
placeholder="cluster name"
|
|
style="width: 100%"
|
|
>
|
|
<hr>
|
|
<p><small>
|
|
<strong>Note</strong>: if you create a cluster with the same name as
|
|
this one after deleting it, the new cluster will restore the data
|
|
from this cluster's current backups stored in AWS S3. This behavior
|
|
will change soon and you will be able to reuse a cluster name and
|
|
get a completely new cluster.
|
|
</small></p>
|
|
`,
|
|
escapeKey: true,
|
|
icon: 'glyphicon glyphicon-warning-sign',
|
|
title: 'Confirm cluster deletion?',
|
|
typeAnimated: true,
|
|
type: 'red',
|
|
onOpen: function () {
|
|
const dialog = this
|
|
const confirm = dialog.buttons.confirm
|
|
const confirmSelector = jQuery(confirm.el)
|
|
const input = dialog.$content.find('input')
|
|
input.on('input', () => {
|
|
if (input.val() === namespace + '/' + clustername) {
|
|
confirmSelector.removeClass('btn-default').addClass('btn-danger')
|
|
confirm.enable()
|
|
} else {
|
|
confirm.disable()
|
|
confirmSelector.removeClass('btn-danger').addClass('btn-default')
|
|
}
|
|
})
|
|
},
|
|
buttons: {
|
|
cancel: {
|
|
text: 'Cancel',
|
|
},
|
|
confirm: {
|
|
btnClass: 'btn-default',
|
|
isDisabled: true,
|
|
text: 'Delete cluster',
|
|
action: () => {
|
|
jQuery.ajax({
|
|
type: 'DELETE',
|
|
url: (
|
|
'/postgresqls/'
|
|
+ encodeURI(namespace)
|
|
+ '/' + encodeURI(clustername)
|
|
),
|
|
dataType: 'text',
|
|
success: () => location.assign('/#/list'),
|
|
error: (r, status, error) => location.assign('/#/list'), // TODO: show error
|
|
})
|
|
},
|
|
},
|
|
}
|
|
})
|
|
}
|
|
|
|
|
|
/* Unfortunately, there does not appear to be a good way to import local modules
|
|
inside a Riot tag, so we define/import things here and pass them manually in the
|
|
opts variable. Remember to propagate opts manually when instantiating tags. */
|
|
riot.mount('app', {
|
|
Dynamic: Dynamic,
|
|
Dynamics: Dynamics,
|
|
DynamicSet: DynamicSet,
|
|
delete_cluster: delete_cluster,
|
|
})
|