Initial import

This commit is contained in:
Benjamin Drieu 2021-11-09 15:21:21 +01:00
commit b770e12885
8 changed files with 732 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*~
#*
schemas/gschemas.compiled

8
Makefile Normal file
View File

@ -0,0 +1,8 @@
reload: schema
busctl --user call org.gnome.Shell /org/gnome/Shell org.gnome.Shell Eval s 'Meta.restart("Restarting…")'
schema:
glib-compile-schemas --strict schemas/
pref: reload
sleep 1 && gnome-extensions prefs monito@drieu.org

95
convenience.js Normal file
View File

@ -0,0 +1,95 @@
/* -*- mode: js; js-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
Copyright (c) 2011-2012, Giovanni Campagna <scampa.giovanni@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of the GNOME nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER 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.
*/
const Gettext = imports.gettext;
const Gio = imports.gi.Gio;
const Config = imports.misc.config;
const ExtensionUtils = imports.misc.extensionUtils;
/**
* initTranslations:
* @domain: (optional): the gettext domain to use
*
* Initialize Gettext to load translations from extensionsdir/locale.
* If @domain is not provided, it will be taken from metadata['gettext-domain']
*/
function initTranslations(domain) {
let extension = ExtensionUtils.getCurrentExtension();
domain = domain || extension.metadata['gettext-domain'];
// check if this extension was built with "make zip-file", and thus
// has the locale files in a subfolder
// otherwise assume that extension has been installed in the
// same prefix as gnome-shell
let localeDir = extension.dir.get_child('locale');
if (localeDir.query_exists(null))
Gettext.bindtextdomain(domain, localeDir.get_path());
else
Gettext.bindtextdomain(domain, Config.LOCALEDIR);
}
/**
* getSettings:
* @schema: (optional): the GSettings schema id
*
* Builds and return a GSettings schema for @schema, using schema files
* in extensionsdir/schemas. If @schema is not provided, it is taken from
* metadata['settings-schema'].
*/
function getSettings(schema,path=null) {
let extension = ExtensionUtils.getCurrentExtension();
schema = schema || extension.metadata['settings-schema'];
const GioSSS = Gio.SettingsSchemaSource;
// check if this extension was built with "make zip-file", and thus
// has the schema files in a subfolder
// otherwise assume that extension has been installed in the
// same prefix as gnome-shell (and therefore schemas are available
// in the standard folders)
let schemaDir = extension.dir.get_child('schemas');
let schemaSource;
if (schemaDir.query_exists(null))
schemaSource = GioSSS.new_from_directory(schemaDir.get_path(),
GioSSS.get_default(),
false);
else
schemaSource = GioSSS.get_default();
let schemaObj = schemaSource.lookup(schema, true);
if (!schemaObj)
throw new Error('Schema ' + schema + ' could not be found for extension '
+ extension.metadata.uuid + '. Please check your installation.');
if ( path )
return new Gio.Settings({ settings_schema: schemaObj, path: path });
else
return new Gio.Settings({ settings_schema: schemaObj });
}

299
extension.js Normal file
View File

@ -0,0 +1,299 @@
/* extension.js
*
* 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, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
/* exported init */
const GETTEXT_DOMAIN = 'monito';
let _httpSession;
let _status;
let _ok_text;
let _warning_text;
let _critical_text;
const Gettext = imports.gettext.domain(GETTEXT_DOMAIN);
const _ = Gettext.gettext;
const Lang = imports.lang;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const { GObject, St, Soup, Clutter } = imports.gi;
const SETTINGS_SCHEMA = "org.gnome.shell.extensions.monito@drieu.org";
const Convenience = Me.imports.convenience;
let settings = Convenience.getSettings(SETTINGS_SCHEMA);
const Indicator = GObject.registerClass(
class Indicator extends PanelMenu.Button {
_init() {
super._init(0.0, _('Monito Checker'));
let box = new St.BoxLayout ( { } );
this.add_child(box);
this.initStatus ( );
// Main Box
/*
let ok_box = new St.BoxLayout({ style_class: 'monito-ok-box monito-box' });
_ok_text = new St.Label({ text: String(_status['OK']) })
ok_box.add_child(_ok_text);
box.add_child(ok_box);
*/
let warning_box = new St.BoxLayout({ style_class: 'monito-warning-box monito-box' });
_warning_text = new St.Label({ text: String(_status['WARNING']) })
warning_box.add_child(_warning_text);
box.add_child(warning_box);
let critical_box = new St.BoxLayout({ style_class: 'monito-critical-box monito-box' });
_critical_text = new St.Label({ text: String(_status['CRITICAL']) })
critical_box.add_child(_critical_text);
box.add_child(critical_box);
box.add_child(PopupMenu.arrowIcon(St.Side.BOTTOM));
// Menu
this._buttonMenu = new PopupMenu.PopupBaseMenuItem({
reactive: false,
style_class: 'monito-menu-button-container',
});
this.menu.addMenuItem(this._buttonMenu);
// let item = new PopupMenu.PopupMenuItem(_('Reload'));
// item.connect('activate', () => {
// this.updateStatus ( );
// });
// this.menu.addMenuItem(item);
this._mainLabel = new St.Label({ style_class: 'monito-title', text: 'Monito Checker', x_expand: true });
this._buttonMenu.actor.add_actor(this._mainLabel);
this._prefsButton = this._createButton ( 'preferences-system-symbolic', 'Preferences', this._onPreferencesActivate );
this._buttonMenu.actor.add_child (this._prefsButton);
this._reloadButton = this._createButton ( 'view-refresh-symbolic', 'Reload', this.updateStatus );
this._buttonMenu.actor.add_child (this._reloadButton );
let _intermediate = new PopupMenu.PopupBaseMenuItem ( {
style_class: 'monito-services',
reactive: false
});
this.menu.addMenuItem(_intermediate);
this._box = new St.BoxLayout({
style_class: 'monito-services',
vertical: true,
x_expand: true
});
_intermediate.actor.add_actor(this._box);
// let _bin = new St.Bin();
// _intermediate.actor.add_actor(_bin);
// _bin.set_child(this._services_table);
// this._list = new FlatList()
// _bin.set_child(this._list);
this.updateStatus ( );
}
initStatus ( ) {
_status = { 'OK': 0,
'WARNING': 0,
'CRITICAL': 0,
'UNKNOWN': 0 };
}
updateStatus ( ) {
const SETTINGS_SCHEMA_ACCOUNT = "org.gnome.shell.extensions.monito@drieu.org.account";
const Convenience = Me.imports.convenience;
let account_settings = Convenience.getSettings(SETTINGS_SCHEMA_ACCOUNT, '/org/gnome/shell/extensions/monito@drieu/org/account/0');
let username = account_settings.get_string("username");
let password = account_settings.get_string("password");
let urlcgi = account_settings.get_string("urlcgi");
if ( ! urlcgi )
{
log ( 'Not updating monito because no URL configured' );
return;
}
urlcgi = urlcgi.replace ( /^(https?:\/\/)/, '$1' + username + ':' + password + '@' );
// log ( 'monito >>> ' + urlcgi );
this.load_data_async ( urlcgi, { 'jsonoutput': '' }, function(res) { Main.notify(res); } )
}
createBin(status, text, col) {
let _widths = [ 300, 300, 200, 50, 600 ];
let _bin = new St.Bin({
style_class: 'monito-service-' + status,
width: _widths[col],
x_expand: ( col == 4 ? true : false ),
child: new St.Label({ style_class: 'monito-label', text: text })
});
return _bin;
}
load_data_async(url, params, fun) {
if (_httpSession === undefined) {
_httpSession = new Soup.Session();
_httpSession.user_agent = Me.metadata.uuid;
} else {
// abort previous requests.
_httpSession.abort();
}
let message = Soup.form_request_new_from_hash('GET', url, params);
_httpSession.queue_message(message, Lang.bind(this, function(_httpSession, message) {
//Main.notify(message.response_body.data);
try {
// log ( message.response_body.data );
let json = JSON.parse(message.response_body.data);
this.initStatus ( );
this._box.remove_all_children();
for ( let i = 0 ; i < json.status.service_status.length ; i ++ )
{
_status [ json.status.service_status[i].status ] ++;
if ( json.status.service_status[i].status != 'OK' )
{
let infoBox = new St.BoxLayout({
style_class: 'monito-service-line monito-service-line-' + json.status.service_status[i].status,
hover: true,
x_expand: true
});
this._box.add_child(infoBox);
infoBox.add_child ( this.createBin ( json.status.service_status[i].status,
json.status.service_status[i].host_name,
0 ) );
infoBox.add_child ( this.createBin ( json.status.service_status[i].status,
json.status.service_status[i].service_display_name,
1 ) );
infoBox.add_child ( this.createBin ( json.status.service_status[i].status,
json.status.service_status[i].last_check,
2) );
infoBox.add_child ( this.createBin ( json.status.service_status[i].status,
json.status.service_status[i].attempts,
3 ) );
infoBox.add_child ( this.createBin ( json.status.service_status[i].status,
json.status.service_status[i].status_information,
4 ) );
// let url = 'https://xxx:xxx@host/cgi-bin/icinga/extinfo.cgi?type=2&host='+json.status.service_status[i].host_name+"&service="+json.status.service_status[i].service_description;
// activeLabel.connect('button-press-event', () => {
// Main.notify(url);
// Gtk.show_uri(null, url, global.get_current_time());
// } );
}
}
// _ok_text.set_text ( String(_status.OK) );
_warning_text.set_text ( String(_status.WARNING) );
_critical_text.set_text ( String(_status.CRITICAL) );
} catch (e) {
Main.notify(_('Zbeu!'));
_warning_text.set_text ( '…' );
_critical_text.set_text ( '…' );
log(e);
return;
}
}));
return;
}
_onPreferencesActivate() {
this.menu.actor.hide();
if (typeof ExtensionUtils.openPrefs === 'function') {
ExtensionUtils.openPrefs();
} else {
Util.spawn([
"gnome-shell-extension-prefs",
Me.uuid
]);
}
return 0;
}
_createButton ( icon, text, callback ) {
let button = new St.Button({
x_align: Clutter.ActorAlign.END,
y_align: Clutter.ActorAlign.CENTER,
reactive: true,
can_focus: true,
track_hover: true,
accessible_name: text,
style_class: 'button'
});
button.child = new St.Icon({
style_class: 'monito-button-icon',
icon_name: icon,
icon_size: 24,
width: 24,
height: 24,
});
button.connect('clicked', Lang.bind(this, callback ) );
return button;
}
});
class Extension {
constructor(uuid) {
this._uuid = uuid;
ExtensionUtils.initTranslations(GETTEXT_DOMAIN);
}
enable() {
this._indicator = new Indicator();
Main.panel.addToStatusArea(this._uuid, this._indicator);
}
disable() {
this._indicator.destroy();
this._indicator = null;
if (_httpSession !== undefined)
_httpSession.abort();
_httpSession = undefined;
}
}
function init(meta) {
return new Extension(meta.uuid);
}

8
metadata.json Normal file
View File

@ -0,0 +1,8 @@
{
"name": "Monito",
"description": "Checks various monitoring websites",
"uuid": "monito@drieu.org",
"shell-version": [
"3.38"
]
}

198
prefs.js Normal file
View File

@ -0,0 +1,198 @@
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
// 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 Gettext = imports.gettext.domain('monito');
const _ = Gettext.gettext;
const N_ = function (e) {
return e;
};
const SETTINGS_SCHEMA = "org.gnome.shell.extensions.monito@drieu.org";
const SETTINGS_SCHEMA_ACCOUNT = "org.gnome.shell.extensions.monito@drieu.org.account";
const SETTINGS_SCHEMA_ACCOUNT_PATH = "/org/gnome/shell/extensions/monito@drieu.org/account";
const prefs = [ { type: Gtk.Entry, label: _('Name'), key: 'name' },
{ type: Gtk.ComboBoxText, label: _('Type'), key: 'type' },
{ type: Gtk.Entry, label: _('Username'), key: 'username' },
{ type: Gtk.Entry, label: _('Password'), key: 'password' },
{ type: Gtk.Entry, label: _('URL CGI'), key: 'urlcgi' },
];
// Like 'extension.js' this is used for any one-time setup like translations.
function init() {
// log('initializing ${Me.metadata.name} Preferences');
}
// 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);
let mainVbox = new Gtk.Box( { orientation: Gtk.Orientation.VERTICAL } );
let mainWidget = new Gtk.Notebook( { } );
let prefsWidget = new Gtk.Grid({
margin: 18,
column_spacing: 12,
row_spacing: 12,
column_homogeneous: false,
});
this.prefWidgets = { };
// Add a simple title and add it to the prefsWidget
let title = new Gtk.Label({
label: `<b>${Me.metadata.name} Preferences</b>`,
halign: Gtk.Align.START,
use_markup: true,
});
prefsWidget.attach(title, 0, 0, 2, 1);
// Misc Settings (TBD)
mainWidget.append_page ( prefsWidget,
new Gtk.Label ( { label: 'General', } ) );
// Accounts
let accountsWidgetContainer = new Gtk.Box( { orientation: Gtk.Orientation.HORIZONTAL,
homogeneous: false, } );
mainWidget.append_page ( accountsWidgetContainer,
new Gtk.Label ( { label: 'Servers', } ) );
let accountsChooserContainer = new Gtk.Box( { orientation: Gtk.Orientation.VERTICAL,
homogeneous: false, } );
accountsWidgetContainer.pack_start(accountsChooserContainer, true, true, 0);
let accountsChooser = new Gtk.ListBox ( { valign: Gtk.Align.FILL,
hexpand: true,
vexpand: true } );
accountsChooserContainer.pack_start(accountsChooser, true, true, 0);
// Account list
for ( var server of this.settings.get_string ( 'servers' ) . split ( ',' ) )
{
let _account_settings = getAccountSettings ( server );
let row = new Gtk.ListBoxRow ( { hexpand: true,
halign: Gtk.Align.FILL } );
row.server= server
let _label = new Gtk.Label ( { label: _account_settings.get_string('name'),
hexpand: true,
margin: 5,
halign: Gtk.Align.START,
expand: true } );
row.add ( _label );
_label.set_data ( 'server', GLib.strdup(server) );
accountsChooser.add ( row );
}
accountsChooser.connect('row-activated', Lang.bind(this, function () {
let _row = accountsChooser.get_selected_row();
log('Active:' + _row.server);
let _account_settings = getAccountSettings ( _row.server );
for ( var prefEntry of prefs )
{
if ( prefEntry.type == Gtk.Entry )
{
// How to unbind previous one?
_account_settings.bind (
prefEntry.key,
this.prefWidgets[prefEntry.key],
'text',
Gio.SettingsBindFlags.DEFAULT
);
}
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 () {
log('Active:' + this.prefWidgets[prefEntry.key].get_active());
_account_settings.set_enum('type', this.prefWidgets[prefEntry.key].get_active());
}));
}
}
} ) );
let accountsChooserActionBar = new Gtk.ActionBar ( { visible: true,
valign: Gtk.Align.END,
vexpand: false} );
accountsChooserContainer.pack_start(accountsChooserActionBar, true, true, 0);
let accountsWidget = new Gtk.Grid ( {
halign: Gtk.Align.FILL,
margin: 18,
column_spacing: 12,
row_spacing: 12,
visible: true,
column_homogeneous: false,
});
accountsWidgetContainer.pack_start(accountsWidget, true, true, 0);
let y = 1;
for ( var prefEntry of prefs )
{
let _label = new Gtk.Label({
label: prefEntry.label + ':',
visible: true,
halign: Gtk.Align.START,
});
accountsWidget.attach ( _label, 0, y, 1, 1 );
this.prefWidgets[prefEntry.key] = new prefEntry.type({
halign: Gtk.Align.FILL,
visible: true,
hexpand: true,
});
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' )
{
[ 'Icinga', 'Icinga2' ].forEach((item) => {
this.prefWidgets[prefEntry.key].append_text(item);
} );
}
accountsWidget.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++;
}
mainVbox.pack_start(mainWidget, true, true, 0);
mainVbox.show_all();
return mainVbox;
}
function getAccountSettings ( id )
{
let _path = SETTINGS_SCHEMA_ACCOUNT_PATH + '/' + id;
return Convenience.getSettings(SETTINGS_SCHEMA_ACCOUNT, _path);
}

View File

@ -0,0 +1,59 @@
<schemalist >
<enum id="org.gnome.shell.extensions.monito@drieu.org.MonitoringType">
<value value="0" nick="Icinga"/>
<value value="1" nick="Icinga2"/>
</enum>
<enum id="org.gnome.shell.extensions.monito@drieu.org.DisplayType">
<value value="0" nick="Short"/>
<value value="1" nick="Long"/>
</enum>
<!-- Main Schema -->
<schema id="org.gnome.shell.extensions.monito@drieu.org" path="/org/gnome/shell/extensions/monito/">
<child name="account" schema="org.gnome.shell.extensions.monito@drieu.org.account"/>
<key name="servers" type="s">
<default>'0'</default>
</key>
<key name="display-type" enum="org.gnome.shell.extensions.monito@drieu.org.DisplayType">
<default>"Short"</default>
</key>
</schema>
<!-- Account list -->
<schema id="org.gnome.shell.extensions.monito@drieu.org.account">
<key name="icon" type="s">
<default>''</default>
</key>
<key name="type" enum="org.gnome.shell.extensions.monito@drieu.org.MonitoringType">
<default>"Icinga"</default>
</key>
<key name="name" type="s">
<default>''</default>
</key>
<key name="username" type="s">
<default>''</default>
</key>
<key name="password" type="s">
<default>''</default>
</key>
<key name="urlcgi" type="s">
<default>''</default>
</key>
<key name="timeoutinterval" type="i">
<default>1</default>
</key>
</schema>
</schemalist>

62
stylesheet.css Normal file
View File

@ -0,0 +1,62 @@
/* Add your custom extension styling here */
.panel-status-menu-box {
color: inherit;
}
.monito-services {
/* padding: 0px;
margin: 0px; */
}
.monito-box {
color: white;
border: 1px solid white;
padding: 0px .5em;
vertical-align: middle;
font-size: 20px;
line-height: 20px;
}
.monito-critical-box, .monito-service-CRITICAL, .monito-service-line-CRITICAL {
background-color: #FF3300;
}
.monito-warning-box, .monito-service-WARNING, .monito-service-line-WARNING {
background-color: #ffa500;
}
.monito-ok-box, .monito-service-OK, .monito-service-line-OK {
background-color: #00CC33;
}
.monito-unknown-box, .monito-service-UNKNOWN, .monito-service-line-UNKNOWN {
background-color: purple;
}
.monito-service-line {
padding: 3px;
margin: 0px;
color: white;
border: 1px solid white;
vertical-align: middle;
font-size: 20px;
line-height: 20px;
}
.monito-title {
margin: 2px;
font-size: 200%;
}
.monito-label {
margin: 2px;
}
.monito-button-icon {
padding: 0px;
margin: 0px;
}
.button {
padding: 12px !important;
}