diff --git a/src/opnsense/www/js/widgets/BaseTableWidget.js b/src/opnsense/www/js/widgets/BaseTableWidget.js index 5e92507f7..380e5ac32 100644 --- a/src/opnsense/www/js/widgets/BaseTableWidget.js +++ b/src/opnsense/www/js/widgets/BaseTableWidget.js @@ -153,6 +153,10 @@ export default class BaseTableWidget extends BaseWidget { this.tables[id].data = data; } + if (rowIdentifier !== null) { + rowIdentifier = rowIdentifier.replace(/[:/]/gi, '__'); + } + data.forEach(row => { let $gridRow = options.headerPosition === 'top' ? $(`
`) diff --git a/src/opnsense/www/js/widgets/Metadata/Core.xml b/src/opnsense/www/js/widgets/Metadata/Core.xml index 94f031491..457b2d8c4 100644 --- a/src/opnsense/www/js/widgets/Metadata/Core.xml +++ b/src/opnsense/www/js/widgets/Metadata/Core.xml @@ -266,4 +266,19 @@ There are currently no tunnels. + + Services.js + + /api/core/service/* + + + Services + No services found + Running + Stopped + Restart + Stop + Start + + \ No newline at end of file diff --git a/src/opnsense/www/js/widgets/Services.js b/src/opnsense/www/js/widgets/Services.js new file mode 100644 index 000000000..ee2efe333 --- /dev/null +++ b/src/opnsense/www/js/widgets/Services.js @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2024 Deciso B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +import BaseTableWidget from "./BaseTableWidget.js"; + +export default class Services extends BaseTableWidget { + constructor() { + super(); + + this.tickTimeout = 5; + + this.locked = false; + } + + getGridOptions() { + return { + // trigger overflow-y:scroll after 850px height + sizeToContent: 650 + } + } + + getMarkup() { + let $table = this.createTable('services-table', { + headerPosition: 'left' + }); + return $(`
`).append($table); + } + + serviceControl(actions) { + return actions.map(({ action, id, title, icon }) => ` + + `).join(''); + } + + async updateServices() { + const data = await this.ajaxGet('/api/core/service/search'); + + if (!data || !data.rows || data.rows.length === 0) { + this.displayError(this.translations.noservices); + return; + } + + if (!this.dataChanged('services', data)) { + return; + } + + for (const service of data.rows) { + let name = service.name; + let description = service.description; + + let actions = []; + if (service.locked) { + actions.push({ action: 'restart', id: service.id, title: this.translations.restart, icon: 'refresh' }); + } else if (service.running) { + actions.push({ action: 'restart', id: service.id, title: this.translations.restart, icon: 'refresh' }); + actions.push({ action: 'stop', id: service.id, title: this.translations.stop, icon: 'stop' }); + } else { + actions.push({ action: 'start', id: service.id, title: this.translations.start, icon: 'play' }); + } + + let $buttonContainer = $(`
+ + + +
+ `); + + $buttonContainer.append(this.serviceControl(actions)); + + super.updateTable('services-table', [[description, $buttonContainer.prop('outerHTML')]], service.id); + } + + $('[data-toggle="tooltip"]').tooltip(); + + $('.srv_status_act2').on('click', async (event) => { + this.locked = true; + event.preventDefault(); + let $elem = $(event.currentTarget); + const icon = $elem.find('i').clone(); + $elem.remove('i').html(''); + await $.post(`/api/core/service/${$elem.data('service_action')}/${$elem.data('service')}`); + $elem.remove('i').html(icon); + await this.updateServices(); + this.locked = false; + }); + } + + async onWidgetTick() { + if (!this.locked) { + await this.updateServices(); + } + } + + displayError(message) { + const $error = $(`
${message}
`); + $('#services-table').empty().append($error); + } + +} \ No newline at end of file