/* -*- mode: js; js-basic-offset: 4; indent-tabs-mode: nil -*- */ /* Monito Gnome-Shell extension Copyright (C) 2021 Benjamin Drieu This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. SPDX-License-Identifier: GPL-2.0-or-later */ const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const Gtk = imports.gi.Gtk; const Gdk = imports.gi.Gdk; const GObject = imports.gi.GObject; // It's common practice to keep GNOME API and JS imports in separate blocks const Lang = imports.lang; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); const Convenience = Me.imports.convenience; const Mainloop = imports.mainloop; const Gettext = imports.gettext.domain('monito'); const _ = Gettext.gettext; const N_ = function (e) { return e; }; const SETTINGS_SCHEMA = "org.gnome.shell.extensions.monito"; const SETTINGS_SCHEMA_ACCOUNT = "org.gnome.shell.extensions.monito.account"; const SETTINGS_SCHEMA_ACCOUNT_PATH = "/org/gnome/shell/extensions/monito/account"; const column_definitions = { status: { label: _('Status'), width: 50, expand: false, }, host_name: { label: _('Host name'), width: 300, expand: false, }, service_display_name: { label: _('Service'), width: 300, expand: false, }, has_been_acknowledged: { label: _('Ack'), width: 50, expand: false }, last_check: { label: _('Last check'), width: 200, expand: false, type: 'date' }, next_check: { label: _('Next check'), width: 200, expand: false, type: 'date' }, last_state_change: { label: _('Last state changed'), width: 200, expand: false, type: 'date' }, attempts: { label: _('Attempts'), width: 50, expand: false, }, status_information: { label: _('Test result'), width: 600, expand: false, }, output: { label: _('Output'), width: 600, expand: false, }, actions: { label: 'Actions', width: 50, expand: false, special: 'actions' }, }; const prefs = [ { type: Gtk.Switch, category: 'Settings', label: _('Active (restart for effect)'), key: 'active', align: Gtk.Align.START }, { type: Gtk.Entry, category: 'Settings', label: _('Name'), key: 'name' }, { type: Gtk.ComboBoxText, category: 'Settings', label: _('Type'), key: 'type' }, { type: Gtk.Entry, category: 'Settings', label: _('Username'), key: 'username' }, { type: Gtk.Entry, category: 'Settings', label: _('Password'), key: 'password' }, { type: Gtk.Entry, category: 'Settings', label: _('Web URL'), key: 'url' }, { type: Gtk.Entry, category: 'Settings', label: _('CGI / API URL'), key: 'urlcgi' }, { type: Gtk.Switch, category: 'Settings', label: _('Check SSL certificate'), key: 'strict-ssl', align: Gtk.Align.START }, { type: Gtk.ColorButton, category: 'Colors', label: _('OK background color'), key: 'ok-color', align: Gtk.Align.START }, { type: Gtk.ColorButton, category: 'Colors', label: _('Warning background color'), key: 'warning-color', align: Gtk.Align.START }, { type: Gtk.ColorButton, category: 'Colors', label: _('Critical background color'), key: 'critical-color', align: Gtk.Align.START }, { type: Gtk.ColorButton, category: 'Colors', label: _('Unknown background color'), key: 'unknown-color', align: Gtk.Align.START }, { type: Gtk.ColorButton, category: 'Colors', label: _('Pending background color'), key: 'pending-color', align: Gtk.Align.START }, { type: Gtk.ColorButton, category: 'Colors', label: _('OK color'), key: 'ok-fg', align: Gtk.Align.START }, { type: Gtk.ColorButton, category: 'Colors', label: _('Warning color'), key: 'warning-fg', align: Gtk.Align.START }, { type: Gtk.ColorButton, category: 'Colors', label: _('Critical color'), key: 'critical-fg', align: Gtk.Align.START }, { type: Gtk.ColorButton, category: 'Colors', label: _('Unknown color'), key: 'unknown-fg', align: Gtk.Align.START }, { type: Gtk.ColorButton, category: 'Colors', label: _('Pending color'), key: 'pending-fg', align: Gtk.Align.START }, { type: Gtk.Switch, category: 'Filters', label: _('Do not display acknowledged services'), key: 'acknowledged-filter-out', align: Gtk.Align.START }, { type: Gtk.Entry, category: 'Filters', label: _('Only display hosts matching'), key: 'host-grep' }, { type: Gtk.Entry, category: 'Filters', label: _('Only display services matching'), key: 'service-grep' }, { type: Gtk.Entry, category: 'Filters', label: _('Only display status info matching'), key: 'status-info-grep' }, { type: Gtk.Entry, category: 'Filters', label: _('Do not display hosts matching'), key: 'host-filter-out' }, { type: Gtk.Entry, category: 'Filters', label: _('Do not display services matching'), key: 'service-filter-out' }, { type: Gtk.Entry, category: 'Filters', label: _('Do not display status info matching'), key: 'status-info-filter-out' }, { type: Gtk.Entry, category: 'Replacements', label: _('Host regexp ...'), key: 'host-match' }, { type: Gtk.Entry, category: 'Replacements', label: _('... to replace with'), key: 'host-replace' }, { type: Gtk.Entry, category: 'Replacements', label: _('Service regexp ...'), key: 'service-match' }, { type: Gtk.Entry, category: 'Replacements', label: _('... to replace with'), key: 'service-replace' }, { type: Gtk.Entry, category: 'Replacements', label: _('Status info regexp ...'), key: 'status-info-match' }, { type: Gtk.Entry, category: 'Replacements', label: _('... to replace with'), key: 'status-info-replace' }, ]; // Like 'extension.js' this is used for any one-time setup like translations. function init() { monitoLog('initializing ${Me.metadata.name} Preferences'); this.settings = ExtensionUtils.getSettings(SETTINGS_SCHEMA); this.gtkVersion = Gtk.get_major_version(); // monitoLog ( 'GTK version is ' + this.gtkVersion ); } // This function is called when the preferences window is first created to build // and return a Gtk widget. As an example we'll create and return a GtkLabel. function buildPrefsWidget() { // Copy the same GSettings code from `extension.js` this.settings = ExtensionUtils.getSettings(SETTINGS_SCHEMA); this._columnsStores = { }; let mainVbox = new Gtk.Box( { orientation: Gtk.Orientation.VERTICAL } ); let mainWidget = new Gtk.Notebook( { } ); let prefsWidget = new Gtk.Grid({ column_spacing: 12, row_spacing: 12, column_homogeneous: false, }); if ( this.gtkVersion < 4 ) prefsWidget.margin = 18; this.prefWidgets = { }; // Add a simple title and add it to the prefsWidget let title = new Gtk.Label({ label: '' + _('Monito Preferences') + '', halign: Gtk.Align.START, use_markup: true, }); prefsWidget.attach(title, 0, 0, 2, 1); let _label = new Gtk.Label({ label: _('Poll delay in seconds'), visible: true, halign: Gtk.Align.START, }); prefsWidget.attach ( _label, 0, 1, 1, 1 ); let _entry = new Gtk.SpinButton ({ numeric: true, halign: Gtk.Align.FILL, visible: true, hexpand: true, can_focus: true, }); _entry.set_range ( 1, 1000 ); _entry.set_increments ( 1, 5 ); prefsWidget.attach ( _entry, 1, 1, 1, 1 ); this.settings.bind ( 'poll-delay', _entry, 'value', Gio.SettingsBindFlags.DEFAULT ); _label = new Gtk.Label({ label: _('Web browser'), visible: true, halign: Gtk.Align.START, }); prefsWidget.attach ( _label, 0, 2, 1, 1 ); _entry = new Gtk.Entry ({ halign: Gtk.Align.FILL, visible: true, hexpand: true, can_focus: true, }); prefsWidget.attach ( _entry, 1, 2, 1, 1 ); this.settings.bind ( 'web-browser', _entry, 'text', Gio.SettingsBindFlags.DEFAULT ); // Misc Settings (TBD) mainWidget.append_page ( prefsWidget, new Gtk.Label ( { label: _('General'), } ) ); // Accounts this._accountsWidgetContainer = new Gtk.Box( { orientation: Gtk.Orientation.HORIZONTAL, homogeneous: false, } ); mainWidget.append_page ( this._accountsWidgetContainer, new Gtk.Label ( { label: _('Servers'), } ) ); let accountsChooserContainer = new Gtk.Box( { orientation: Gtk.Orientation.VERTICAL, homogeneous: false, } ); if ( this.gtkVersion == 4 ) this._accountsWidgetContainer.append(accountsChooserContainer, true, true, 0); else this._accountsWidgetContainer.add(accountsChooserContainer); this.accountsChooser = new Gtk.ListBox ( { valign: Gtk.Align.FILL, hexpand: true, vexpand: true } ); if ( this.gtkVersion == 4 ) accountsChooserContainer.append(this.accountsChooser, true, true, 0); else accountsChooserContainer.add(this.accountsChooser); // Account list for ( var server_id of this.getServersList ( ) ) this.addAccountLine ( server_id ); this.accountsChooser.connect ( 'row-activated', Lang.bind ( this, this.activateAccountRow ) ); // Action Bar let accountsChooserActionBar = new Gtk.ActionBar ( { valign: Gtk.Align.END } ); this.addToContainer ( accountsChooserContainer, accountsChooserActionBar ); let accountCreateButton = Gtk.Button.new_from_icon_name ( 'list-add', Gtk.IconSize.BUTTON ); accountsChooserActionBar.pack_start(accountCreateButton, true); //this.addToContainer ( accountsChooserActionBar, accountCreateButton ); accountCreateButton.connect ( 'clicked', Lang.bind ( this, this.createAccount ) ); let accountRemoveButton = Gtk.Button.new_from_icon_name ( 'list-remove', Gtk.IconSize.BUTTON ); accountsChooserActionBar.pack_start(accountRemoveButton, true); // this.addToContainer ( accountsChooserActionBar, accountRemoveButton ); accountRemoveButton.connect ( 'clicked', Lang.bind ( this, this.removeAccount ) ); this.createAccountWidgets ( false ) ; this.addToContainer ( mainVbox, mainWidget ); if ( this.gtkVersion == 4 ) mainVbox.set_visible(true); else mainVbox.show_all(); return mainVbox; } function getServersList ( ) { if ( ! this.settings ) this.settings = ExtensionUtils.getSettings(SETTINGS_SCHEMA); return this.settings.get_string ( 'servers' ) . split ( ',' ); } function getAccountSettings ( id ) { let _path = SETTINGS_SCHEMA_ACCOUNT_PATH + '/' + id + '/'; return Convenience.getSettings(SETTINGS_SCHEMA_ACCOUNT, _path); } function getSortOrder ( server ) { return this.getAccountSettings ( server ) . get_strv ( 'columns-order' ); } function setSortOrder ( server, sort_order ) { return this.getAccountSettings ( server ) . set_strv ( 'columns-order', sort_order ); } function getColumns ( server ) { let _columns = this.getAccountSettings ( server ) . get_strv ( 'columns' ); let _columnsSizes = this.getAccountSettings ( server ) . get_strv ( 'columns-size' ); const zip = (a1, a2) => a1.map((x, i) => { return { name: x, size: a2[i] } } ); let _result = zip ( _columns, _columnsSizes ); return _result; } function setColumns ( server, columns ) { return this.getAccountSettings ( server ) . set_strv ( 'columns', columns ); } function createAccountWidgets ( isActive ) { this.prefWidgets = { }; this._accountsWidget = new Gtk.Notebook( { } ); if ( this.gtkVersion == 4 ) this._accountsWidgetContainer.append(this._accountsWidget, true, true, 0); else this._accountsWidgetContainer.add(this._accountsWidget); for ( var _tab of [ 'Settings', 'Columns', 'Colors', 'Filters', 'Replacements' ] ) { if ( _tab != 'Columns' ) this.createPrefWidgets ( _accountsWidget, _tab, isActive ); else this.createColumnsPrefTab ( _accountsWidget, _tab, isActive ); } // Settings this.prefWidgets['name'].connect('changed', Lang.bind(this, function () { let _row = this.accountsChooser.get_selected_row(); _row.get_child().label = this.prefWidgets['name'].text; } ) ); if ( this.gtkVersion == 4 ) this._accountsWidget.set_visible(true); else this._accountsWidget.show_all(); } function createPrefWidgets ( noteBook, type, isActive ) { let grid = new Gtk.Grid ( { halign: Gtk.Align.FILL, // margin: 18, column_spacing: 12, row_spacing: 12, visible: true, column_homogeneous: false, }); if ( this.gtkVersion < 4 ) grid.margin = 18; noteBook.append_page ( grid, new Gtk.Label ( { label: _(type), } ) ); let y = 0; for ( var prefEntry of prefs ) { if ( prefEntry.category != type ) continue; let _label = new Gtk.Label({ label: prefEntry.label + ':', visible: true, halign: Gtk.Align.START, use_markup: true, }); grid.attach ( _label, 0, y, 1, 1 ); this.prefWidgets[prefEntry.key] = new prefEntry.type ( { halign: ( prefEntry.align ? prefEntry.align : Gtk.Align.FILL ), visible: true, hexpand: true, can_focus: isActive, } ); let boundValue = 'text'; if ( prefEntry.key == 'password' ) { this.prefWidgets[prefEntry.key].set_input_purpose ( Gtk.InputPurpose.PASSWORD ); this.prefWidgets[prefEntry.key].set_visibility ( false ); } else if ( prefEntry.key == 'type' ) { [ { name: 'Icinga', value: 'Icinga server' }, { name: 'Icinga2API', value: 'Icinga2 server (using API, prefered)' }, { name: 'Icinga2', value: 'Icinga2 server (using Icingaweb2, limited)' } ].forEach((item) => { this.prefWidgets[prefEntry.key].insert ( -1, item.name, item.value ); } ); } grid.attach(this.prefWidgets[prefEntry.key], 1, y, 1, 1); if ( prefEntry.type != Gtk.ComboBoxText ) { // this.account_settings.bind( // prefEntry.key, // _entry, // boundValue, // Gio.SettingsBindFlags.DEFAULT // ); } y++; } } function createColumnsPrefTab ( noteBook, type, isActive ) { let grid = new Gtk.Grid ( { halign: Gtk.Align.FILL, margin: 18, column_spacing: 12, row_spacing: 12, visible: true, column_homogeneous: false, }); if ( this.gtkVersion < 4 ) grid.margin = 18; noteBook.append_page ( grid, new Gtk.Label ( { label: _(type), } ) ); this.treeView = new Gtk.TreeView ( { headers_visible: true, reorderable: true, hexpand: true, vexpand: true }); let columnNumbers = new Gtk.TreeViewColumn ( { title: _("Column") } ); this.ColumnNameRenderer = new Gtk.CellRendererCombo ( { editable: true, has_entry: false, text_column: 0 } ); this.ColumnNameRenderer.connect("edited", onComboChanged) this.ColumnNameRenderer.col = 0; columnNumbers.pack_start(this.ColumnNameRenderer, true); columnNumbers.add_attribute(this.ColumnNameRenderer, 'text', 0); this.treeView.append_column(columnNumbers); this.ColumnNameRenderer.connect('edited', Lang.bind ( this, this.editColumn ) ); let _colSize = new Gtk.TreeViewColumn ( { title: _("Size") } ); let _colRenderer = new Gtk.CellRendererSpin ( { adjustment: new Gtk.Adjustment ( { lower: 0, upper: 999, step_increment: 1, page_increment: 10 } ), editable: true } ); // if ( this.gtkVersion == 4 ) // _colSize.append(_colRenderer, true); // else _colSize.pack_start(_colRenderer, true); _colSize.add_attribute(_colRenderer, 'text', 1); this.treeView.append_column(_colSize); _colRenderer.col = 1; _colRenderer.connect('edited', Lang.bind ( this, this.editColumn ) ); // _treeView.connect('row-activated', this._editPath.bind(this)); grid.attach ( this.treeView, 0, 1, 1, 1 ); // Action Bar let rowsChooserActionBar = new Gtk.ActionBar ( { valign: Gtk.Align.END } ); let rowCreateButton = Gtk.Button.new_from_icon_name ( 'list-add', Gtk.IconSize.BUTTON ); rowsChooserActionBar.pack_start(rowCreateButton, true); // rowsChooserActionBar.add ( rowCreateButton ); rowCreateButton.connect ( 'clicked', Lang.bind ( this, this.createColumnRow ) ); let rowRemoveButton = Gtk.Button.new_from_icon_name ( 'list-remove', Gtk.IconSize.BUTTON ); rowsChooserActionBar.pack_start(rowRemoveButton, true); // rowsChooserActionBar.add ( rowRemoveButton ); rowRemoveButton.connect ( 'clicked', Lang.bind ( this, this.removeColumnRow ) ); grid.attach ( rowsChooserActionBar, 0, 2, 1, 1 ); } function onComboChanged(widget, path, text) { monitoLog ( widget ); monitoLog ( path ); monitoLog ( text ); } function activateAccountRow ( ) { if ( this._accountsWidget ) this._accountsWidgetContainer.remove ( this._accountsWidget ); this.createAccountWidgets ( true ); this.store = new Gtk.ListStore(); this.store.set_column_types([GObject.TYPE_STRING, GObject.TYPE_INT]); this.store.filter_new(null); this.treeView.model = this.store; let _row = this.accountsChooser.get_selected_row(); let _account_settings = getAccountSettings ( _row.server ); this.columnsModel = new Gtk.ListStore ( ); this.columnsModel.set_column_types([GObject.TYPE_STRING,GObject.TYPE_STRING]); for ( let [ _colName, _colDef ] of Object.entries(column_definitions) ) { let _iter = this.columnsModel.append(); this.columnsModel.set_value(_iter, 1, _colName ); this.columnsModel.set_value(_iter, 0, _colDef.label ); } this.ColumnNameRenderer.model = this.columnsModel; if ( ! _account_settings ) return; let _columns = _account_settings . get_strv ( 'columns' ); let _columnsSizes = _account_settings . get_strv ( 'columns-size' ); for ( var i in _columns ) { let _iter = this.store.append(); this.store.set_value(_iter, 0, getEntryLabel ( _columns [ i ] ) ); this.store.set_value(_iter, 1, parseInt(_columnsSizes [ i ]) ); } for ( var prefEntry of prefs ) { if ( prefEntry.type == Gtk.Entry ) { _account_settings.bind ( prefEntry.key, this.prefWidgets[prefEntry.key], 'text', Gio.SettingsBindFlags.DEFAULT ); } else if ( prefEntry.type == Gtk.Switch ) { _account_settings.bind ( prefEntry.key, this.prefWidgets[prefEntry.key], 'active', Gio.SettingsBindFlags.DEFAULT ); } else if ( prefEntry.type == Gtk.ColorButton ) { let _color = new Gdk.RGBA ( ); _color.parse (_account_settings.get_string(prefEntry.key) ); this.prefWidgets[prefEntry.key].set_use_alpha ( false ); this.prefWidgets[prefEntry.key].set_rgba ( _color ); this.prefWidgets[prefEntry.key].connect('color-set', Lang.bind ( { key: prefEntry.key, settings: _account_settings }, setColor ) ); } else if ( prefEntry.type == Gtk.ComboBoxText ) { this.prefWidgets[prefEntry.key].set_active(_account_settings.get_enum('type')); this.prefWidgets[prefEntry.key].connect('changed', Lang.bind(this, function (e) { let _account_settings = getAccountSettings ( _row.server ); _account_settings.set_enum('type', this.prefWidgets['type'].get_active()); })); } } } function addAccountLine ( server_id ) { monitoLog ( '> Add line ' + server_id ); let _account_settings = getAccountSettings ( server_id ); let row = new Gtk.ListBoxRow ( { hexpand: true, halign: Gtk.Align.FILL } ); row.server = server_id; let _label = new Gtk.Label ( { label: _account_settings.get_string('name'), hexpand: true, // margin: 5, halign: Gtk.Align.START } ); if ( this.gtkVersion == 4 ) { row.set_child ( _label ); this.accountsChooser.append ( row ); } else { _label.margin = 5; row.add ( _label ); this.accountsChooser.add ( row ); if ( this.gtkVersion >= 4 ) this.accountsChooser.set_visible(true); else this.accountsChooser.show_all(); } } function createAccount ( ) { monitoLog ( '> Create Account' ); let _servers = this.getServersList ( ); let _max = Math.max ( ..._servers ); let _server_id = _max + 1; _servers.push ( _server_id ); let _account_settings = getAccountSettings ( _server_id ); _account_settings.set_string ( 'name', _('New server #') + _server_id ); this.addAccountLine ( _max + 1 ); this.settings.set_string ( 'servers', _servers.join(',') ); } function removeAccount ( ) { let _row = this.accountsChooser.get_selected_row(); monitoLog('Active:' + _row.server); let _servers = this.getServersList ( ); _servers.splice ( _servers.indexOf ( _row.server ), 1 ); monitoLog ( _servers ); if ( ! _row ) return; _row.destroy(); this.settings.set_string ( 'servers', _servers . join ( ',' ) ); } function setColor ( color ) { monitoLog ( 'Color ' + + ': ' + color ); let _output = '#%02x%02x%02x'.format( 255 * color.get_rgba().red, 255 * color.get_rgba().green, 255 * color.get_rgba().blue ); this.settings.set_string('' + this.key, _output); } function createColumnRow ( ) { let _item = this.store.append(); this.store.set_value(_item, 0, 'status' ); this.store.set_value(_item, 1, 300 ); let _row = this.accountsChooser.get_selected_row(); let _account_settings = getAccountSettings ( _row.server ); let prefKey; let _defs = _account_settings . get_strv ( 'columns' ); _defs.push ( 'status' ); _account_settings . set_strv ( 'columns', _defs ); _defs = _account_settings . get_strv ( 'columns-size' ); _defs.push ( '300' ); _account_settings . set_strv ( 'columns-size', _defs ); } function removeColumnRow ( widget ) { let [any, model, _iter] = this.treeView.get_selection().get_selected ( ); let _row = this.accountsChooser.get_selected_row(); let _account_settings = getAccountSettings ( _row.server ); let prefKey; for ( var foo of this.treeView.get_selection().get_selected_rows()[0] ) { let _defs = _account_settings . get_strv ( 'columns' ); _defs.splice ( foo.get_indices ( ), 1 ); _account_settings . set_strv ( 'columns', _defs ); _defs = _account_settings . get_strv ( 'columns-size' ); _defs.splice ( foo.get_indices ( ), 1 ); _account_settings . set_strv ( 'columns-size', _defs ); } this.store.remove ( _iter ); } function getEntryName ( text ) { for ( let [ _colName, _colDef ] of Object.entries(column_definitions) ) if ( _colDef.label == text ) return _colName; return 'N/A'; } function getEntryLabel ( text ) { return column_definitions[text].label; } function editColumn ( widget, path, text ) { let _row = this.accountsChooser.get_selected_row(); let _account_settings = getAccountSettings ( _row.server ); let prefKey; let _iter = this.store.get_iter ( Gtk.TreePath.new_from_string ( path ) ); if ( widget.col == 1 ) { this.store.set_value(_iter[1], widget.col, parseInt(text) ); prefKey = 'columns-size'; } else { this.store.set_value ( _iter[1], widget.col, text ); text = getEntryName ( text ); prefKey = 'columns'; } let _defs = _account_settings . get_strv ( prefKey ); _defs [ parseInt(path) ] = text; _account_settings . set_strv ( prefKey, _defs ); return true; } function addToContainer ( parent, child ) { if ( this.gtkVersion == 4 ) parent.append(child, true, true, 0); else parent.add(child); } function monitoLog ( msg ) { log ( 'Monito: ' + msg ); }