#!/usr/bin/perl
use strict;
use lib qw(/usr/lib/libDrakX);
use standalone;
use common;
use mouse;
use detect_devices;
use ugtk2 qw(:create :dialogs :helpers :wrappers);
use interactive;
use Libconf qw(:functions);
use Libconf::Glueconf::NUT::Ups_conf;
# config files:
my %files = (devices => "/etc/ups/ups.conf",
access => "/etc/ups/upsd.conf",
users => "/etc/ups/upsd.users",
);
my ($struct, $users); # NUT configuration
my ($w, $in); # GUI
my %indexes;
sub writeconf() {
info_dialog(N("Warning"), "Write support for users is incomplete\n\nIt lacks some support for some extra fields that would be lost else");
log::explanations("Updating NUT configuration accordingly");
$struct->writeConf($files{devices});
if ($users) {
log::explanations("Updating NUT users configuration accordingly");
$users->writeConf($files{users});
}
require services;
services::restart("upsd");
}
sub read_nut_config() {
$struct = Libconf::Glueconf::NUT::Ups_conf->new({ filename => $files{devices} });
}
sub readDriversList() {
my (%ups, @ups);
local $_;
foreach (cat_(first(glob("/usr/share/doc/nut-*/docs/driver.list")))) {
/^#/ and next;
if (my ($vendor, $model, $extra, $driver) = /^"(.*)"\s+"(.*)"\s+"(.*)"\s+"(.*)"/) {
$ups{$vendor}{$model . $extra} = {
driver => $driver,
extra => $extra,
};
push @ups, "$vendor|$model ($extra)";
}
}
\%ups, \@ups;
}
my %models;
sub add_device_wizard {
my ($in, $config) = @_;
my ($ups_models, $model_list) = readDriversList();
use wizards;
my ($ups, $vendor, $model, $extra, $name, $driver, $port, @new_devices, $opts);
my $w = wizards->new;
my $wiz;
my %methods = (
# network => N("Connected through the network"), # need SNMP probe
# serial => N("Connected through a serial port"),
# usb => N("Connected through an usb cable"),
auto => N("Connected through a serial port or an usb cable"),
manual => N("Manual configuration"),
);
my $method = $methods{auto};
$wiz = {
#defaultimage => "logdrake.png", # FIXME
name => N("Add an UPS device"),
pages => {
welcome => {
name => N("Welcome to the UPS configuration utility.
Here, you'll add a new UPS to your system.\n"),
no_back => 1,
next => 'method'
},
method => {
name => N("We're going to add an UPS device.
Do you want to autodetect UPS devices connected to this machine or to manually select them?"),
data => [ { label => N("Autodetection"), val => \$method, type => "list",
list => [ values %methods ] } ],
post => sub { +{ reverse %methods }->{$method} },
},
auto => {
end => 1,
pre => sub {
local $::isWizard;
my $_wait = $in->wait_message(N("Please wait"), N("Detection in progress"));
# UPS autoconfig:
detect_devices::probeSerialDevices() if !$::testing;
@new_devices = ();
foreach my $ups_device (detect_devices::getUPS()) {
my $str = $ups_device->{name} || $ups_device->{DESCRIPTION};
$str =~ s/ /_/g;
$name = $str;
if (!exists $struct->{$str}) {
$port = $struct->{$str}{port} = $ups_device->{port} || $ups_device->{DEVICE};
$driver = $struct->{$str}{driver} = $ups_device->{driver};
push @new_devices, $str;
}
}
},
name => sub {
if (@new_devices) {
N("Congratulations") . "\n\n" .
N("The wizard successfully added the following UPS devices:") . join("\n\n-", @new_devices);
} else {
N("No new UPS devices was found");
}
},
},
manual => {
name => N("UPS driver configuration") . "\n\n" . N("Please select your UPS model."),
data => [ { label => N("Manufacturer / Model:"), val => \$ups, list => $model_list,
type => 'combo', sort => 1, separator => '|' }, ],
post => sub {
($vendor, $model, $extra) = ($1, $2, $3) if $ups =~ /(.*)\|(.*) \((.*)\)$/;
($name, $driver, $port) = ("myups", $ups_models->{$vendor}{$model . $extra}{driver}, "");
($driver, $opts) = split(/\s+/, $driver);
"driver";
},
},
driver => {
name => sub {
N("UPS driver configuration") . "\n\n" . N("We are configuring the \"%s\" UPS from \"%s\".
Please fill in its name, its driver and its port.", $model, $vendor);
},
data => sub {
[
{ label => N("Name:"), val => \$name, help => N("The name of your ups") },
{ label => N("Driver:"), val => \$driver, help => N("The driver that manages your ups") },
{ label => N("Port:"), val => \$port, format => \&mouse::serial_port2text, type => "combo",
list => [ &mouse::serial_ports() ], not_edit => 0,
help => N("The port on which is connected your ups") },
];
},
post => sub {
$port = '/dev/' . $port if $port !~ m!/dev/!;
return 'end';
},
},
end => {
name => sub {
N("Congratulations") . "\n\n" . N("The wizard successfully configured the new \"%s\" UPS device.",
$model . "|" . $vendor);
},
end => 1,
no_back => 1,
next => 0
},
},
};
$w->process($wiz, $in);
$config->{$name}{driver} = $driver;
$config->{$name}{port} = $port;
$config->{$name}{$1} = $2 if $opts =~ /\b(.*)=(.*)\b/;
# refresh the GUI when needed:
$models{ups}->append_set(1 => $name, 2 => $driver, 3 => $port) if $models{ups};
log::explanations(qq(Configuring "$name" UPS));
}
my (@acls, @rules);
sub load_access_conf() {
foreach (cat_($files{access})) {
s/#.*//;
if (/^\s*ACL\s*(\S*)\s*(\S*)/) {
my ($ip, $mask) = split('/', $2);
push @acls, [ $1, $ip, $mask ];
} elsif (/^\s*ACCESS\s*(\S*)\s*(\S*)\s*(\S*)/) {
push @rules, [ $1, $2, $3 ];
}
}
}
#------------------------------------------------------------------
# misc gui data
sub edit_row {
my ($model, $o_iter) = @_;
# create new item if needed (that is when adding a new one) at end of list
my $iter = $o_iter || $model->append;
my $dialog = Gtk2::Dialog->new;
$dialog->set_transient_for($w->{real_window});
$dialog->set_modal(1);
gtkpack_($dialog->vbox,
#map {
#}
);
gtkadd($dialog->action_area,
gtksignal_connect(Gtk2::Button->new(N("Ok")), clicked => sub {
# create new item if needed (that is when adding a new one) at end of list
$iter ||= $model->append;
# $model->set($iter, 1 => $file->get_text); # FILL ME
$dialog->destroy;
# $modified++;
}),
gtksignal_connect(Gtk2::Button->new(N("Cancel")), clicked => sub { $dialog->destroy }),
);
$dialog->show_all;
}
sub add_callback {
my ($model, $_list, $_getindex) = @_;
edit_row($model);
}
sub edit_callback {
my ($model, $list) = @_;
my ($iter) = $list->get_selection->get_selected;
return unless $iter;
edit_row($model, $iter);
}
sub del_callback {
my ($model, $list) = @_;
my (undef, $iter) = $list->get_selection->get_selected;
my $removed_idx = $list->get($iter, 0); # 1st column is index
#@rules = grep { $_->{index} ne $removed_idx } @rules;
#$tree->remove($iter);
#sensitive_buttons(0);
#$modified++;
}
my @pages = (
{ name => N("UPS devices"),
columns => [ N("Name"), N("Driver"), N("Port") ], # N("Manufacturer"), N("Model"),
callbacks => {
add => sub {
eval { add_device_wizard($in, $struct) };
my $err = $@;
die $err if $err && $err !~ /wizcancel/;
$::WizardWindow->destroy if defined $::WizardWindow;
undef $::WizardWindow;
},
edit => sub {},
remove => sub {},
},
load => sub {
read_nut_config();
map { [ $_, @{$struct->{$_}}{qw(driver port)} ] } keys %$struct;
},
id => "ups",
},
{ name => N("UPS users"),
columns => [ N("Name") ],
callbacks => {
add => sub {
my ($name) = @_;
$users->{$name} = {};
},
edit => sub {},
remove => sub {},
},
load => sub {
$users = Libconf::Glueconf::NUT::Ups_conf->new({ filename => $files{users} });
map { [ $_ ] } keys %$users;
},
id => "users",
},
{ name => N("Access Control Lists"),
columns => [ N("Name"), N("IP address"), N("IP mask") ],
callbacks => {
add => sub {},
edit => sub {},
remove => sub {},
},
load => sub {
load_access_conf();
@acls;
},
id => "acls",
},
{ name => N("Rules"),
columns => [ N("Action"), N("Level"), N("ACL name"), N("Password") ],
callbacks => {
N("Add") => sub {},
N("Edit") => sub {},
N("Remove") => sub {},
},
load => sub { @rules }, # already loaded when we loaded acls
id => "rules",
},
);
#------------------------------------------------------------------
# initialize:
#$in = 'interactive'->vnew('su'); # require_root_capability();
$in = 'interactive'->vnew;
$ugtk2::wm_icon = "drakups";
$in->do_pkgs->ensure_is_installed('nut-server', '/etc/rc.d/init.d/upsd') if !$::testing;
if (member('--wizard', @ARGV)) {
read_nut_config();
add_device_wizard($in, $struct);
writeconf();
$in->exit($@ ? 1 : 0);
}
$w = ugtk2->new(N("DrakUPS"));
if (!$::isEmbedded) {
$::main_window = $w->{real_window};
$w->{window}->set_size_request(500, 550);
$w->{rwindow}->set_title(N("DrakUPS"));
}
#------------------------------------------------------------------
# main window:
my $_msg = N("Welcome to the UPS configuration tools");
$w->{window}->add(gtkpack_(Gtk2::VBox->new,
if_(!$::isEmbedded, 0, Gtk2::Banner->new('drakups', N("DrakUPS"))),
1, my $nb = Gtk2::Notebook->new,
0, create_okcancel(my $oc =
{
ok_clicked => sub {
#$_->{save}->() foreach @pages;
writeconf();
$w->exit;
},
cancel_clicked => sub { $w->exit },
},
),
),
);
#------------------------------------------------------------------
# build the notebook
my %labels = (
add => N("Add"),
edit => N("Edit"),
remove => N("Remove"),
);
foreach my $i (@pages) {
my $model = $models{$i->{id}} = Gtk2::ListStore->new("Glib::Int", ("Glib::String") x listlength(@{$i->{columns}}));
my (%buttons, $list);
$indexes{$i->{name}} = 0;
my $idx = \$indexes{$i->{name}};
my $getindex = sub { $$idx++ };
$nb->append_page(gtkpack_(Gtk2::VBox->new,
1, create_scrolled_window($list = Gtk2::TreeView->new_with_model($model),
[ 'automatic', 'automatic' ]),
0, gtkpack(Gtk2::HButtonBox->new,
(map {
my ($id, $label, $sub) = @$_;
gtksignal_connect($buttons{$id} = Gtk2::Button->new($label), clicked => sub {
$sub->($model, $list, $getindex);
});
} ([ 'add', N("Add"), $i->{callbacks}{add} || \&add_callback ],
[ 'edit', N("Edit"), \&edit_callback ],
[ 'remove', N("Remove"), \&del_callback ],
)
)
#(map {
# gtksignal_connect(Gtk2::Button->new($_), clicked => $i->{callbacks}{$_}),
#} keys %{$i->{callbacks}})
),
),
Gtk2::Label->new($i->{name}),
);
#$i->{list} = $list;
each_index {
$list->append_column(Gtk2::TreeViewColumn->new_with_attributes($_, Gtk2::CellRendererText->new, 'text' => $::i + 1));
} @{$i->{columns}};
my @u = $i->{load}->();
foreach my $line (@u) {
$model->append_set(0 => $getindex->(), map_index { $::i + 1 => $_ } @$line);
}
my $set_sensitive = sub {
my ($bool) = @_;
$buttons{$_}->set_sensitive($bool) foreach qw(remove edit);
};
$set_sensitive->(0);
$list->get_selection->signal_connect('changed' => sub {
my ($select) = @_;
my (undef, $iter) = $select->get_selected;
$set_sensitive->(defined $iter);
});
}
#------------------------------------------------------------------
# let's start the show:
$w->{rwindow}->show_all;
$w->main;
pan class="hl str">&& find [A-Z]* -noleaf -type f |");
my @l = difference2([ chomp_(<$F>) ], [ 'ROC', 'PRC' ]);
close $F or die "cannot list the available zoneinfos";
sort @l;
}
sub read() {
my %t = getVarsFromSh("$::prefix/etc/sysconfig/clock") or return {};
{ timezone => $t{ZONE}, UTC => text2bool($t{UTC}) };
}
my $ntp_conf_file = "/etc/ntp.conf";
sub ntp_server() {
find { $_ ne '127.127.1.0' } map { if_(/^\s*server\s+(\S*)/, $1) } cat_($::prefix . $ntp_conf_file);
}
sub set_ntp_server {
my ($server) = @_;
my $f = $::prefix . $ntp_conf_file;
-f $f or return;
my $pool_match = qr/\.pool\.ntp\.org$/;
my @servers = $server =~ $pool_match ? (map { "$_.$server" } 0 .. 2) : $server;
my $added = 0;
substInFile {
if (/^#?\s*server\s+(\S*)/ && $1 ne '127.127.1.0') {
$_ = $added ? $_ =~ $pool_match ? undef : "#server $1\n" : join('', map { "server $_\n" } @servers);
$added = 1;
}
} $f;
output_p("$::prefix/etc/ntp/step-tickers", join('', map { "$_\n" } @servers));
require services;
services::set_status('ntpd', to_bool($server), $::isInstall);
}
sub write {
my ($t) = @_;
set_ntp_server($t->{ntp});
my $tz_prefix = get_timezone_prefix();
eval { cp_af($tz_prefix . '/' . $t->{timezone}, "$::prefix/etc/localtime") };
$@ and log::l("installing /etc/localtime failed");
setVarsInSh("$::prefix/etc/sysconfig/clock", {
ZONE => $t->{timezone},
UTC => bool2text($t->{UTC}),
ARC => "false",
});
my $adjtime_file = $::prefix . '/etc/adjtime';
my @adjtime = cat_($adjtime_file);
@adjtime or @adjtime = ("0.0 0 0.0\n", "0\n");
$adjtime[2] = $t->{UTC} ? "UTC\n" : "LOCAL\n";
output_p($adjtime_file, @adjtime);
}
sub reload_sys_clock {
my ($t) = @_;
require run_program;
any::disable_x_screensaver();
run_program::run('hwclock', '--hctosys', ($t->{UTC} ? '--utc' : '--localtime'));
any::enable_x_screensaver();
}
#- best guesses for a given country
my %c2t = (
'AM' => 'Asia/Yerevan',
'AR' => 'America/Buenos_Aires',
'AT' => 'Europe/Vienna',
'AU' => 'Australia/Sydney',
'BA' => 'Europe/Sarajevo',
'BE' => 'Europe/Brussels',
'BG' => 'Europe/Sofia',
'BR' => 'America/Sao_Paulo', #- most brazilians live on this time zone
'BY' => 'Europe/Minsk',
'CA' => 'Canada/Eastern',
'CH' => 'Europe/Zurich',
'CN' => 'Asia/Beijing',
'CZ' => 'Europe/Prague',
'DE' => 'Europe/Berlin',
'DK' => 'Europe/Copenhagen',
'EE' => 'Europe/Tallinn',
'ES' => 'Europe/Madrid',
'FI' => 'Europe/Helsinki',
'FR' => 'Europe/Paris',
'GB' => 'Europe/London',
'GE' => 'Asia/Yerevan',
'GL' => 'Arctic/Longyearbyen',
'GR' => 'Europe/Athens',
'HR' => 'Europe/Zagreb',
'HU' => 'Europe/Budapest',
'ID' => 'Asia/Jakarta',
'IE' => 'Europe/Dublin',
'IL' => 'Asia/Tel_Aviv',
'IN' => 'Asia/Kolkata',
'IR' => 'Asia/Tehran',
'IS' => 'Atlantic/Reykjavik',
'IT' => 'Europe/Rome',
'JP' => 'Asia/Tokyo',
'KR' => 'Asia/Seoul',
'LT' => 'Europe/Vilnius',
'LV' => 'Europe/Riga',
'MK' => 'Europe/Skopje',
'MT' => 'Europe/Malta',
'MX' => 'America/Mexico_City',
'MY' => 'Asia/Kuala_Lumpur',
'NL' => 'Europe/Amsterdam',
'NO' => 'Europe/Oslo',
'NZ' => 'Pacific/Auckland',
'PL' => 'Europe/Warsaw',
'PT' => 'Europe/Lisbon',
'RO' => 'Europe/Bucharest',
'RU' => 'Europe/Moscow',
'SE' => 'Europe/Stockholm',
'SI' => 'Europe/Ljubljana',
'SK' => 'Europe/Bratislava',
'TH' => 'Asia/Bangkok',
'TJ' => 'Asia/Dushanbe',
'TR' => 'Europe/Istanbul',
'TW' => 'Asia/Taipei',
'UA' => 'Europe/Kiev',
'US' => 'America/New_York',
'UZ' => 'Asia/Tashkent',
'VN' => 'Asia/Saigon',
'YU' => 'Europe/Belgrade',
'ZA' => 'Africa/Johannesburg',
);
sub fuzzyChoice {
my ($b, $count) = bestMatchSentence($_[0], keys %c2t);
$count ? $b : '';
}
sub bestTimezone { $c2t{fuzzyChoice($_[0])} || 'GMT' }
our %ntp_servers;
sub get_ntp_server_tree {
my ($zone) = @_;
map {
$ntp_servers{$zone}{$_} => (
exists $ntp_servers{$_} ?
$zone ?
translate($_) . "|" . N("All servers") :
N("All servers") :
translate($zone) . "|" . translate($_)
),
get_ntp_server_tree($_);
} keys %{$ntp_servers{$zone}};
}
sub ntp_servers() {
# FIXME: missing parameter:
+{ get_ntp_server_tree() };
}
sub dump_ntp_zone {
my ($zone) = @_;
map { if_(/\[\d+\](.+) -- (.+\.ntp\.org)/, $1 => $2) } `lynx -dump http://www.pool.ntp.org/zone/$zone`;
}
sub print_ntp_zone {
my ($zone, $name) = @_;
# FIXME: missing parameter:
my %servers = dump_ntp_zone($zone);
print qq(\$ntp_servers{"$name"} = {\n);
print join('', map { qq( N_("$_") => "$servers{$_}",\n) } sort(keys %servers));
print "};\n";
\%servers;
}
sub print_ntp_servers() {
print_ntp_zone();
my $servers = print_ntp_zone('@', "Global");
foreach my $name (sort(keys %$servers)) {
my ($zone) = $servers->{$name} =~ /^(.*?)\./;
print_ntp_zone($zone, $name);
}
}
# perl -Mtimezone -e 'timezone::print_ntp_servers()'
$ntp_servers{""} = {
N_("Global") => "pool.ntp.org",
};
$ntp_servers{Global} = {
N_("Africa") => "africa.pool.ntp.org",
N_("Asia") => "asia.pool.ntp.org",
N_("Europe") => "europe.pool.ntp.org",
N_("North America") => "north-america.pool.ntp.org",
N_("Oceania") => "oceania.pool.ntp.org",
N_("South America") => "south-america.pool.ntp.org",
};
$ntp_servers{Africa} = {