monito/prefs.js

655 lines
24 KiB
JavaScript

/* -*- 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' },
attempts: { label: _('Attempts'), width: 50, expand: false, },
status_information: { label: _('Information'), 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 <b>not</b> 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 <b>not</b> display hosts matching'), key: 'host-filter-out' },
{ type: Gtk.Entry, category: 'Filters', label: _('Do <b>not</b> display services matching'), key: 'service-filter-out' },
{ type: Gtk.Entry, category: 'Filters', label: _('Do <b>not</b> 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();
}
// 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: '<b>' + _('Monito Preferences') + '</b>',
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 } );
if ( this.gtkVersion == 4 )
accountsChooserContainer.append(accountsChooserActionBar, true, true, 0);
else
accountsChooserContainer.add(accountsChooserActionBar);
let accountCreateButton = Gtk.Button.new_from_icon_name ( 'list-add',
Gtk.IconSize.BUTTON );
accountsChooserActionBar.add ( accountCreateButton );
accountCreateButton.connect ( 'clicked', Lang.bind ( this, this.createAccount ) );
let accountRemoveButton = Gtk.Button.new_from_icon_name ( 'list-remove',
Gtk.IconSize.BUTTON );
accountsChooserActionBar.add ( accountRemoveButton );
accountRemoveButton.connect ( 'clicked', Lang.bind ( this, this.removeAccount ) );
this.createAccountWidgets ( false ) ;
if ( this.gtkVersion == 4 )
mainVbox.append(mainWidget, true, true, 0);
else
mainVbox.add(mainWidget);
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;
} ) );
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.col = 0;
if ( this.gtkVersion == 4 )
columnNumbers.append(this.ColumnNameRenderer, true);
else
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.add ( rowCreateButton );
rowCreateButton.connect ( 'clicked', Lang.bind ( this, this.createColumnRow ) );
let rowRemoveButton = Gtk.Button.new_from_icon_name ( 'list-remove',
Gtk.IconSize.BUTTON );
rowsChooserActionBar.add ( rowRemoveButton );
rowRemoveButton.connect ( 'clicked', Lang.bind ( this, this.removeColumnRow ) );
grid.attach ( rowsChooserActionBar, 0, 2, 1, 1 );
}
function activateAccountRow ( ) {
if ( this._accountsWidget )
this._accountsWidget.destroy ( );
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, 0, _colName );
this.columnsModel.set_value(_iter, 1, _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, _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 );
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 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 );
prefKey = 'columns';
}
let _defs = _account_settings . get_strv ( prefKey );
_defs [ parseInt(path) ] = text;
_account_settings . set_strv ( prefKey, _defs );
return true;
}
function monitoLog ( msg )
{
log ( 'Monito: ' + msg );
}