Merge branch 'master' of git.mgm.sipwise.com:ngcp-panel

agranig/subprof
Irina Peshinskaya 12 years ago
commit 021b0af430

1
.gitignore vendored

@ -9,7 +9,6 @@ _darcs
Descrip.MMS
DESCRIP.MMS
descrip.mms
MANIFEST
MANIFEST.bak
Makefile
blib

@ -1,10 +1,11 @@
use lib 'inc';
use Local::Module::Build qw();
use Local::Module::Build;
my $builder = Local::Module::Build->new(
module_name => 'NGCP::Panel',
license => 'restrictive',
license => 'perl',
dist_version_from => 'lib/NGCP/Panel.pm',
dist_abstract => 'Sipwise Configuration Panel',
tap_harness_args => {
timer => 1,
formatter_class => 'TAP::Formatter::JUnit',
@ -14,7 +15,6 @@ my $builder = Local::Module::Build->new(
'lib' => 0,
'Module::Build' => '0.4004',
'Moose' => 2,
'Sipwise::Base' => 0,
},
requires => {
'base' => 0,
@ -81,6 +81,7 @@ my $builder = Local::Module::Build->new(
'Scalar::Util' => 0,
'Sereal::Decoder' => 0,
'Sereal::Encoder' => 0,
'Sipwise::Base' => 0,
'strict' => 0,
'Template' => 0,
'Text::CSV_XS' => 0,
@ -145,38 +146,43 @@ Build.PL - NGCP-Panel build system including test fixtures
./Build test --webdriver=selenium-rc # from CPAN distro Alien-SeleniumRC
./Build test --webdriver=external --wd-server=127.0.0.1:5555
./Build test_tap --webdriver=external # outputs tap to tap/ folder
./Build testcover --webdriver='phantomjs --webdriver=4444'
./Build test_api
./Build test_api --schema-base-dir=path/to/schema --server=https://1.2.3.4:1443
=head2 Options
--webdriver (required) external webdriver command
--wd-server HOST:PORT of an external webdriver to connect to
--server URI for socket test server
--schema-base-dir directory of NGCP::Schema if its not yet installed
--mysqld-port port where the mysqld should be started
--mysql-dump one or more mysql dumps to be imported to our mysqld
--no-junit don't output junit but normal TAP, for manual testing
--run-server panel should be started by build script
--schema-base-dir directory of NGCP::Schema if its not yet installed
--server URI for socket test server
--webdriver Command to be run as Webdriver
--wd-server HOST:PORT of an external webdriver to connect to
--help brief help message
--man full documentation
=head1 OPTIONS
=head2 C<--webdriver>
=head2 C<--mysqld-port>
(required) command to launch a webdriver
external if the webdriver is launched externally
If this option and C<--mysqld-dir> are supplied, a mysqld will be started
at the specified port and be used for the tests. mysqld will be stopped
and the temporary data deleted when this script finishes.
=head2 C<--wd-server>
=head2 C<--mysql-dump>
Host:Port of the webdriver to which the tests should connect.
Default is set by Test::WebDriver to localhost:4444
If this option and C<--mysqld-port> are supplied, a mysqld will be started
and be used for the tests. It will import all dumps supplied with this option.
This option can be set multiple times. In this case all specified files will
be dumped into the database.
=head2 C<--server>
=head2 C<--run-server>
URI for the HTTP::Server::PSGI socket server run for testing,
default C<http://localhost:5000>
Does not take an argument. Indicates the ./Build script should start a
server running the panel by itself on the Address given by the --server
argument.
=head2 C<--schema-base-dir>
@ -184,18 +190,21 @@ If the NGCP::Schema is not installed to a known path to perl, this
option can specify the base directory of its development location.
It will then be included via blib, so we have access to its lib and share.
=head2 C<--mysqld-port>
=head2 C<--server>
If this option and C<--mysqld-dir> are supplied, a mysqld will be started
at the specified port and be used for the tests. mysqld will be stopped
and the temporary data deleted when this script finishes.
URI for the HTTP::Server::PSGI socket server run for testing,
default C<http://localhost:5000>
=head2 C<--mysql-dump>
=head2 C<--webdriver>
If this option and C<--mysqld-port> are supplied, a mysqld will be started
and be used for the tests. It will import all dumps supplied with this option.
This option can be set multiple times. In this case all specified files will
be dumped into the database.
command to launch a webdriver
external if the webdriver is launched externally
this command is no longer required
=head2 C<--wd-server>
Host:Port of the webdriver to which the tests should connect.
Default is set by Test::WebDriver to localhost:4444
=head2 C<--help>

@ -59,3 +59,5 @@
^MYMETA\.
^NGCP-Panel-
.*

7
debian/control vendored

@ -61,17 +61,12 @@ Build-Depends: debhelper (>= 8),
libxml-mini-perl,
ngcp-schema,
openssl
Standards-Version: 3.9.4
Standards-Version: 3.9.5
Homepage: http://sipwise.com/
Package: ngcp-panel
Architecture: all
Pre-Depends: nginx-common
# only for trunk installations, remove it
Replaces: ngcp-panel-common (<= 1.0.15),
ngcp-panel-nginx (<= 1.0.15)
Breaks: ngcp-panel-common (<= 1.0.15),
ngcp-panel-nginx (<= 1.0.15)
Depends: gettext,
gnutls-bin,
libautodie-perl (>= 2.21~),

@ -1,8 +1,7 @@
package Local::Module::Build;
use Sipwise::Base;
use Moose qw(around);
use Moose qw(around extends);
use Child qw(child);
use Capture::Tiny qw(capture);
use Capture::Tiny;
use TryCatch;
use MooseX::Method::Signatures;
use LWP::UserAgent;
@ -10,7 +9,7 @@ extends 'Module::Build';
our ($plackup, $webdriver, @cover_opt, $mysqld);
method wait_socket($host, $port) {
method wait_socket($host, $port, $timeout=90) {
require IO::Socket::IP;
my $timer = 0;
while (1) {
@ -22,8 +21,8 @@ method wait_socket($host, $port) {
last if $sock;
sleep 1;
$timer++;
die sprintf('socket %s:%s is not accessible within 30 seconds after start', $host, $port)
if $timer > 90;
die sprintf('socket %s:%s is not accessible within %d seconds after start', $host, $port, $timeout)
if $timer > $timeout;
};
}
@ -40,14 +39,15 @@ sub _test_preconditions {
my ($self) = @_;
require Getopt::Long;
my %opt = (server => 'http://localhost:5000');
Getopt::Long::GetOptions(\%opt, 'webdriver=s', 'server:s', 'help|?', 'man', 'wd-server=s', 'schema-base-dir=s', 'mysqld-port=s', 'mysql-dump=s@', 'no-junit')
Getopt::Long::Configure('pass_through');
my %opt = (server => 'http://localhost:5000', webdriver => 'external');
Getopt::Long::GetOptions(\%opt, 'webdriver=s', 'server:s', 'help|?', 'man', 'wd-server=s',
'schema-base-dir=s', 'mysqld-port=s', 'mysql-dump=s@', 'no-junit', 'run-server')
or die 'could not process command-line options';
require Pod::Usage;
Pod::Usage::pod2usage(-exitval => 1, -input => 'Build.PL') if $opt{help};
Pod::Usage::pod2usage(-exitval => 0, -input => 'Build.PL', -verbose => 2) if $opt{man};
Pod::Usage::pod2usage("$0: --webdriver option required.\nRun `perldoc Build.PL`") unless $opt{webdriver};
if ($opt{'no-junit'}) {
delete $self->tap_harness_args->{formatter_class};
@ -86,29 +86,37 @@ sub _test_preconditions {
}
require URI;
unless ($opt{server} =~ m|^https?://|) {
die "Wrong format of server argument, should start with 'http(s)'.";
}
my $uri = URI->new($opt{server});
require File::Which;
$ENV{ NGCP_PANEL_CONFIG_LOCAL_SUFFIX } = "testing";
$plackup = child {
my $out_fh = IO::File->new("panel_debug_stdout", "w+");
my $err_fh = IO::File->new("panel_debug_stderr", "w+");
$out_fh->autoflush(1);
$err_fh->autoflush(1);
local $| = 1;
capture {
exec $^X,
'-Ilib',
exists $opt{'schema-base-dir'} ? "-Mblib=$opt{'schema-base-dir'}" : (),
@cover_opt,
scalar File::Which::which('plackup'),
sprintf('--listen=%s:%s', $uri->host, $uri->port),
'ngcp_panel.psgi';
} stdout => $out_fh, stderr => $err_fh;
};
$self->wait_socket($uri->host, $uri->port);
if( $opt{'run-server'} ) {
require File::Which;
$ENV{ NGCP_PANEL_CONFIG_LOCAL_SUFFIX } = "testing";
$plackup = child {
my $out_fh = IO::File->new("panel_debug_stdout", "w+");
my $err_fh = IO::File->new("panel_debug_stderr", "w+");
$out_fh->autoflush(1);
$err_fh->autoflush(1);
local $| = 1;
Capture::Tiny::capture {
exec $^X,
'-Ilib',
exists $opt{'schema-base-dir'} ? "-Mblib=$opt{'schema-base-dir'}" : (),
@cover_opt,
scalar File::Which::which('plackup'),
sprintf('--listen=%s:%s', $uri->host, $uri->port),
'ngcp_panel.psgi';
} stdout => $out_fh, stderr => $err_fh;
};
$self->wait_socket($uri->host, $uri->port);
}
$ENV{CATALYST_SERVER} = $opt{server};
if ($self->verbose) {
print("Server is: ".$opt{server}."\n");
}
}
sub _download_certs {
@ -129,6 +137,7 @@ sub _download_certs {
$res = $ua->post($uri.'/administrator/1/api_key', {'ca.download' => 'foo'}, 'Referer' => $uri.'/dashboard', ':content_file' => $tmp_apica_filename);
$ENV{API_SSL_CLIENT_CERT} = $tmp_apiclient_filename;
$ENV{API_SSL_CA_CERT} = $tmp_apica_filename;
print "Client cert: $tmp_apiclient_filename - CA cert: $tmp_apica_filename\n" if $self->verbose;
}
around('ACTION_test', sub {
@ -167,14 +176,6 @@ method ACTION_testcover {
$self->do_system(qw(cover));
}
method ACTION_test_tap {
$self->depends_on('code');
$self->_test_preconditions;
system( "mkdir -p tap" );
$ENV{PERL_TEST_HARNESS_DUMP_TAP} = "tap/";
$self->generic_test(type => 'default');
}
method ACTION_test_servers {
$self->depends_on('code');
$self->_test_preconditions;
@ -205,3 +206,5 @@ method ACTION_readme {
}
END { shutdown_servers }
1;

@ -50,7 +50,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,

@ -23,7 +23,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,

@ -22,6 +22,24 @@ class_has 'api_description' => (
'Defines a collection of <a href="#billingfees">Billing Fees</a> and <a href="#billingzones">Billing Zones</a> and can be assigned to <a href="#customers">Customers</a> and <a href="#contracts">System Contracts</a>.'
);
class_has 'query_params' => (
is => 'ro',
isa => 'ArrayRef',
default => sub {[
{
param => 'reseller_id',
description => 'Filter for billing profiles belonging to a specific reseller',
query => {
first => sub {
my $q = shift;
{ reseller_id => $q };
},
second => sub {},
},
},
]},
);
with 'NGCP::Panel::Role::API::BillingProfiles';
class_has('resource_name', is => 'ro', default => 'billingprofiles');
@ -32,7 +50,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,

@ -23,7 +23,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,

@ -50,7 +50,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,

@ -23,7 +23,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,

@ -23,6 +23,35 @@ class_has 'api_description' => (
'Defines a billing container for peerings and resellers. A <a href="#billingprofiles">Billing Profile</a> is assigned to a contract, and it has <a href="#contractbalances">Contract Balances</a> indicating the saldo of the contract for current and past billing intervals.'
);
class_has 'query_params' => (
is => 'ro',
isa => 'ArrayRef',
default => sub {[
{
param => 'contact_id',
description => 'Filter for contracts with a specific contact id',
query => {
first => sub {
my $q = shift;
{ contact_id => $q };
},
second => sub {},
},
},
{
param => 'status',
description => 'Filter for contracts with a specific status (except "terminated")',
query => {
first => sub {
my $q = shift;
{ status => $q };
},
second => sub {},
},
},
]},
);
with 'NGCP::Panel::Role::API::Contracts';
class_has('resource_name', is => 'ro', default => 'contracts');

@ -23,6 +23,35 @@ class_has 'api_description' => (
'Defines a physical or legal person\'s address (postal and/or email) to be used to identify <a href="#customers">Customers</a>.'
);
class_has 'query_params' => (
is => 'ro',
isa => 'ArrayRef',
default => sub {[
{
param => 'email',
description => 'Filter for contacts matching an email pattern',
query => {
first => sub {
my $q = shift;
{ email => { like => $q } };
},
second => sub {},
},
},
{
param => 'reseller_id',
description => 'Filter for contacts belonging to a specific reseller',
query => {
first => sub {
my $q = shift;
{ reseller_id => $q };
},
second => sub {},
},
},
]},
);
with 'NGCP::Panel::Role::API::CustomerContacts';
class_has('resource_name', is => 'ro', default => 'customercontacts');
@ -33,7 +62,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,

@ -23,7 +23,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,

@ -23,6 +23,37 @@ class_has 'api_description' => (
'Defines a billing container for end customers. Customers usually have one or more <a href="#subscribers">Subscribers</a>. A <a href="#billingprofiles">Billing Profile</a> is assigned to a customer, and it has <a href="#contractbalances">Contract Balances</a> indicating the saldo of the customer for current and past billing intervals.'
);
class_has 'query_params' => (
is => 'ro',
isa => 'ArrayRef',
default => sub {[
{
param => 'reseller_id',
description => 'Filter for customers belonging to a specific reseller',
query => {
first => sub {
my $q = shift;
{ 'contact.reseller_id' => $q };
},
second => sub {
{ join => 'contact' };
},
},
},
{
param => 'contact_id',
description => 'Filter for customers belonging to a specific contact',
query => {
first => sub {
my $q = shift;
{ contact_id => $q };
},
second => sub { },
},
},
]},
);
with 'NGCP::Panel::Role::API::Customers';
class_has('resource_name', is => 'ro', default => 'customers');
@ -33,7 +64,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,

@ -26,7 +26,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,

@ -27,7 +27,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,

@ -33,7 +33,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,

@ -26,7 +26,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,

@ -23,6 +23,37 @@ class_has 'api_description' => (
'Specifies a SIP Domain to be used as host part for SIP <a href="#subscribers">Subscribers</a>. You need a domain before you can create a subscriber. Multiple domains can be created. A domain could also be an IPv4 or IPv6 address (whereas the latter needs to be enclosed in square brackets, e.g. [::1]).'
);
class_has 'query_params' => (
is => 'ro',
isa => 'ArrayRef',
default => sub {[
{
param => 'reseller_id',
description => 'Filter for domains belonging to a specific reseller',
query => {
first => sub {
my $q = shift;
{ 'domain_resellers.reseller_id' => $q };
},
second => sub {
{ join => 'domain_resellers' };
},
},
},
{
param => 'domain',
description => 'Filter for domains matching the given pattern',
query => {
first => sub {
my $q = shift;
{ domain => { like => $q } };
},
second => sub { },
},
},
]},
);
with 'NGCP::Panel::Role::API::Domains';
class_has('resource_name', is => 'ro', default => 'domains');

@ -26,7 +26,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,

@ -22,6 +22,24 @@ class_has 'api_description' => (
'Defines a reseller on the system. A reseller can manage his own <a href="#domains">Domains</a> and <a href="#customers">Customers</a>.'
);
class_has 'query_params' => (
is => 'ro',
isa => 'ArrayRef',
default => sub {[
{
param => 'name',
description => 'Filter for resellers matching the given name pattern',
query => {
first => sub {
my $q = shift;
{ name => { like => $q } };
},
second => sub {},
},
},
]},
);
with 'NGCP::Panel::Role::API::Resellers';
class_has('resource_name', is => 'ro', default => 'resellers');

@ -73,7 +73,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,
@ -173,11 +173,17 @@ sub POST :Allow {
);
last unless $resource;
unless(defined $resource->{reseller_id}) {
my $reseller_id;
if($c->user->roles eq "admin") {
try {
$resource->{reseller_id} = $c->user->contract->contact->reseller_id;
}
$reseller_id = $resource->{reseller_id}
|| $c->user->contract->contact->reseller_id;
}
} elsif($c->user->roles eq "reseller") {
$reseller_id = $c->user->reseller_id;
}
$resource->{reseller_id} = $reseller_id;
my $reseller = $c->model('DB')->resultset('resellers')->find($resource->{reseller_id});
unless($reseller) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'reseller_id', doesn't exist.");

@ -26,7 +26,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,

@ -21,12 +21,6 @@ class_has 'api_description' => (
'Defines a set of Rewrite Rules which are grouped in <a href="#rewriterulesets">Rewrite Rule Sets</a>. They can be used to alter incoming and outgoing numbers.',
);
with 'NGCP::Panel::Role::API::RewriteRules';
class_has('resource_name', is => 'ro', default => 'rewriterules');
class_has('dispatch_path', is => 'ro', default => '/api/rewriterules/');
class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-rewriterules');
class_has 'query_params' => (
is => 'ro',
isa => 'ArrayRef',
@ -56,11 +50,18 @@ class_has 'query_params' => (
]},
);
with 'NGCP::Panel::Role::API::RewriteRules';
class_has('resource_name', is => 'ro', default => 'rewriterules');
class_has('dispatch_path', is => 'ro', default => '/api/rewriterules/');
class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-rewriterules');
__PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,
@ -176,7 +177,16 @@ sub POST :Allow {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Required: 'set_id'");
last;
}
my $ruleset = $schema->resultset('voip_rewrite_rule_sets')->find($set_id);
my $reseller_id;
if($c->user->roles eq "reseller") {
$reseller_id = $c->user->reseller_id;
}
my $ruleset = $schema->resultset('voip_rewrite_rule_sets')->find({
id => $set_id,
($reseller_id ? (reseller_id => $reseller_id) : ()),
});
unless($ruleset) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'set_id'.");
last;

@ -26,7 +26,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,

@ -27,7 +27,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,

@ -33,7 +33,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,

@ -26,7 +26,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,

@ -68,7 +68,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,

@ -26,7 +26,7 @@ __PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => 'admin',
AllowedRole => [qw/admin reseller/],
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,

@ -23,6 +23,24 @@ class_has 'api_description' => (
'Defines a physical or legal person\'s address (postal and/or email) to be used in <a href="#contracts">System Contracts</a> (contracts for peerings and resellers).'
);
class_has 'query_params' => (
is => 'ro',
isa => 'ArrayRef',
default => sub {[
{
param => 'email',
description => 'Filter for contacts matching an email pattern',
query => {
first => sub {
my $q = shift;
{ email => { like => $q } };
},
second => sub {},
},
},
]},
);
with 'NGCP::Panel::Role::API::SystemContacts';
class_has('resource_name', is => 'ro', default => 'systemcontacts');

@ -290,7 +290,7 @@ sub api_key :Chained('base') :PathPart('api_key') :Args(0) {
);
}
$CLASS->meta->make_immutable;
__PACKAGE__->meta->make_immutable;
__END__

@ -785,7 +785,7 @@ sub peaktime_specials_create :Chained('peaktimes_list') :PathPart('date/create')
$c->stash(peaktimes_special_createflag => 1);
}
$CLASS->meta->make_immutable;
__PACKAGE__->meta->make_immutable;
1;

@ -119,12 +119,12 @@ sub edit :Chained('base') :PathPart('edit') {
$c->stash->{group_result}->update($form->custom_get_values);
$self->_sip_lcr_reload;
delete $c->session->{created_objects}->{contract};
$c->flash(messages => [{type => 'success', text => $c->('Peering group successfully updated')}]);
$c->flash(messages => [{type => 'success', text => $c->loc('Peering group successfully updated')}]);
} catch ($e) {
NGCP::Panel::Utils::Message->error(
c => $c,
error => $e,
desc => $c->('Failed to update peering group.'),
desc => $c->loc('Failed to update peering group.'),
);
};
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for)

@ -4,13 +4,15 @@ use namespace::sweep;
BEGIN { extends 'Catalyst::Controller'; }
use DateTime qw();
use HTTP::Status qw(HTTP_SEE_OTHER);
use File::Type;
use NGCP::Panel::Form::Reseller;
use NGCP::Panel::Form::Reseller::Branding;
use NGCP::Panel::Utils::Contract;
use NGCP::Panel::Utils::DateTime;
use NGCP::Panel::Utils::Message;
use NGCP::Panel::Utils::Navigation;
sub auto :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) {
sub auto {
my ($self, $c) = @_;
$c->log->debug(__PACKAGE__ . '::auto');
NGCP::Panel::Utils::Navigation::check_redirect_chain(c => $c);
@ -45,11 +47,11 @@ sub list_reseller :Chained('/') :PathPart('reseller') :CaptureArgs(0) {
]);
}
sub root :Chained('list_reseller') :PathPart('') :Args(0) {
sub root :Chained('list_reseller') :PathPart('') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) {
my ($self, $c) = @_;
}
sub ajax :Chained('list_reseller') :PathPart('ajax') :Args(0) {
sub ajax :Chained('list_reseller') :PathPart('ajax') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) {
my ($self, $c) = @_;
my $resellers = $c->stash->{resellers};
NGCP::Panel::Utils::Datatables::process($c, $resellers, $c->stash->{reseller_dt_columns});
@ -57,7 +59,7 @@ sub ajax :Chained('list_reseller') :PathPart('ajax') :Args(0) {
return;
}
sub create :Chained('list_reseller') :PathPart('create') :Args(0) {
sub create :Chained('list_reseller') :PathPart('create') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) {
my ($self, $c) = @_;
$c->detach('/denied_page')
@ -116,6 +118,9 @@ sub base :Chained('list_reseller') :PathPart('') :CaptureArgs(1) {
$c->response->redirect($c->uri_for());
return;
}
$c->detach('/denied_page')
if($c->user->roles eq "reseller" && $c->user->reseller_id != $reseller_id);
$c->stash->{contact_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [
{ name => "id", search => 1, title => $c->loc('#') },
{ name => "firstname", search => 1, title => $c->loc('First Name') },
@ -159,9 +164,12 @@ sub base :Chained('list_reseller') :PathPart('') :CaptureArgs(1) {
);
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/reseller'));
}
$c->stash->{branding} = $c->stash->{reseller}->first->branding;
}
sub reseller_contacts :Chained('base') :PathPart('contacts/ajax') :Args(0) {
sub reseller_contacts :Chained('base') :PathPart('contacts/ajax') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) {
my ($self, $c) = @_;
my $rs = $c->stash->{reseller}->first->contract->search_related_rs('contact');
NGCP::Panel::Utils::Datatables::process($c, $rs, $c->stash->{contact_dt_columns});
@ -169,7 +177,7 @@ sub reseller_contacts :Chained('base') :PathPart('contacts/ajax') :Args(0) {
return;
}
sub reseller_single :Chained('base') :PathPart('single/ajax') :Args(0) {
sub reseller_single :Chained('base') :PathPart('single/ajax') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) {
my ($self, $c) = @_;
NGCP::Panel::Utils::Datatables::process($c, $c->stash->{reseller}, $c->stash->{reseller_dt_columns});
@ -177,7 +185,7 @@ sub reseller_single :Chained('base') :PathPart('single/ajax') :Args(0) {
return;
}
sub reseller_admin :Chained('base') :PathPart('admins/ajax') :Args(0) {
sub reseller_admin :Chained('base') :PathPart('admins/ajax') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) {
my ($self, $c) = @_;
my $rs = $c->stash->{reseller}->first->search_related_rs('admins');
NGCP::Panel::Utils::Datatables::process($c, $rs, $c->stash->{admin_dt_columns});
@ -185,7 +193,7 @@ sub reseller_admin :Chained('base') :PathPart('admins/ajax') :Args(0) {
return;
}
sub edit :Chained('base') :PathPart('edit') :Args(0) {
sub edit :Chained('base') :PathPart('edit') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) {
my ($self, $c) = @_;
$c->detach('/denied_page')
@ -247,7 +255,7 @@ sub edit :Chained('base') :PathPart('edit') :Args(0) {
return;
}
sub terminate :Chained('base') :PathPart('terminate') :Args(0) {
sub terminate :Chained('base') :PathPart('terminate') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) {
my ($self, $c) = @_;
my $reseller = $c->stash->{reseller}->first;
@ -305,13 +313,13 @@ sub _handle_reseller_status_change {
}
}
sub details :Chained('base') :PathPart('details') :Args(0) {
sub details :Chained('base') :PathPart('details') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) {
my ($self, $c) = @_;
$c->stash(template => 'reseller/details.tt');
return;
}
sub ajax_contract :Chained('list_reseller') :PathPart('ajax_contract') :Args(0) {
sub ajax_contract :Chained('list_reseller') :PathPart('ajax_contract') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) {
my ($self, $c) = @_;
my $edit_contract_id = $c->session->{edit_contract_id};
@ -334,7 +342,7 @@ sub ajax_contract :Chained('list_reseller') :PathPart('ajax_contract') :Args(0)
$c->detach( $c->view("JSON") );
}
sub create_defaults :Path('create_defaults') :Args(0) {
sub create_defaults :Path('create_defaults') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) {
my ($self, $c) = @_;
$c->detach('/denied_page') unless $c->request->method eq 'POST';
$c->detach('/denied_page')
@ -412,6 +420,128 @@ sub create_defaults :Path('create_defaults') :Args(0) {
return;
}
sub edit_branding_css :Chained('base') :PathPart('css/edit') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
$c->detach('/denied_page')
if($c->user->read_only);
$c->stash(template => 'reseller/details.tt');
my $reseller = $c->stash->{reseller}->first;
my $branding = $reseller->branding;
my $posted = $c->request->method eq 'POST';
my $form = NGCP::Panel::Form::Reseller::Branding->new;
if($posted) {
$c->req->params->{logo} = $c->req->upload('logo');
}
my $params = $branding ? { $branding->get_inflated_columns } : {};
$form->process(
posted => $posted,
params => $c->request->params,
item => $params,
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
form => $form,
fields => {},
back_uri => $c->uri_for('/reseller/details', $c->req->captures),
);
if($posted && $form->validated) {
try {
$c->model('DB')->txn_do(sub {
if($c->user->roles eq "admin") {
$form->params->{reseller_id} = $reseller->id;
} elsif($c->user->roles eq "reseller") {
$form->params->{reseller_id} = $c->user->reseller_id;
}
delete $form->params->{reseller};
delete $form->params->{back};
my $ft = File::Type->new();
if($form->params->{logo}) {
my $logo = delete $form->params->{logo};
$form->params->{logo} = $logo->slurp;
$form->params->{logo_image_type} = $ft->mime_type($form->params->{logo});
} else {
delete $form->params->{logo};
}
unless(defined $branding) {
$c->model('DB')->resultset('reseller_brandings')->create($form->params);
} else {
$branding->update($form->params);
}
});
$c->flash(messages => [{type => 'success', text => $c->loc('Reseller branding successfully updated')}]);
} catch($e) {
NGCP::Panel::Utils::Message->error(
c => $c,
error => $e,
desc => $c->loc('Failed to update reseller branding'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/reseller/details', $c->req->captures));
}
$c->stash(close_target => $c->uri_for());
$c->stash(branding_form => $form);
$c->stash(branding_edit_flag => 1);
$c->stash(close_target => $c->uri_for_action('/reseller/details', $c->req->captures));
return;
}
sub get_branding_logo :Chained('base') :PathPart('css/logo/download') :Args(0) {
my ($self, $c) = @_;
my $reseller = $c->stash->{reseller}->first;
my $branding = $reseller->branding;
unless($branding || $branding->logo) {
$c->response->body($c->loc('404 - No branding logo available for this reseller'));
$c->response->status(404);
return;
}
$c->response->content_type($branding->logo_image_type);
$c->response->body($branding->logo);
}
sub delete_branding_logo :Chained('base') :PathPart('css/logo/delete') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
my $reseller = $c->stash->{reseller}->first;
my $branding = $reseller->branding;
if($branding) {
$branding->update({ logo => undef, logo_image_type => undef });
}
$c->response->redirect($c->uri_for_action('/reseller/details', $c->req->captures));
}
sub get_branding_css :Chained('base') :PathPart('css/download') :Args(0) {
my ($self, $c) = @_;
my $reseller = $c->stash->{reseller}->first;
my $branding = $reseller->branding;
unless($branding || $branding->css) {
$c->response->body($c->loc('404 - No branding css available for this reseller'));
$c->response->status(404);
return;
}
$c->response->content_type('text/css');
$c->response->body($branding->css);
}
__PACKAGE__->meta->make_immutable;
__END__

@ -0,0 +1,51 @@
package NGCP::Panel::Form::Reseller::Branding;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
use Moose::Util::TypeConstraints;
use HTML::FormHandler::Widget::Block::Bootstrap;
has '+widget_wrapper' => ( default => 'Bootstrap' );
has '+enctype' => ( default => 'multipart/form-data');
has_field 'submitid' => ( type => 'Hidden' );
sub build_render_list {[qw/submitid fields actions/]}
sub build_form_element_class {[qw(form-horizontal)]}
has_field 'logo' => (
type => 'Upload',
required => 0,
label => 'Logo',
max_size => '67108864', # 64MB
);
has_field 'css' => (
type => 'TextArea',
label => 'CSS',
cols => 200,
rows => 10,
maxlength => '67108864', # 64MB
element_class => [qw/ngcp-autoconf-area/],
);
has_field 'save' => (
type => 'Submit',
value => 'Save',
element_class => [qw/btn btn-primary/],
label => '',
);
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/logo css/],
);
has_block 'actions' => (
tag => 'div',
class => [qw/modal-footer/],
render_list => [qw/save/],
);
1;
# vim: set tabstop=4 expandtab:

@ -53,12 +53,14 @@ has_field 'period.row.year' => (
);
has_field 'period.row.year.from' => (
type => 'Year',
range_start => '2014',
label => 'Year',
empty_select => '',
wrapper_class => [qw/hfh-rep-field ngcp-timeset-field/],
);
has_field 'period.row.year.to' => (
type => 'Year',
range_start => '2014',
label => 'through',
empty_select => '',
wrapper_class => [qw/hfh-rep-field ngcp-timeset-field/],

@ -82,7 +82,6 @@ sub validate_form {
# move {xxx_id} into {xxx}{id} for FormHandler
foreach my $key(keys %{ $resource } ) {
if($key =~ /^(.+)_id$/ && $key ne "external_id") {
$c->log->debug("++++++++++++ moving key $key with value " . ($resource->{$key} // '<null>') . " to $1/id");
push @normalized, $1;
$resource->{$1}{id} = delete $resource->{$key};
}
@ -92,7 +91,6 @@ sub validate_form {
my %fields = map { $_->name => undef } $form->fields;
for my $k (keys %{ $resource }) {
unless(exists $fields{$k}) {
$c->log->debug("+++++++++ deleting unknown key '$k' from message"); # TODO: user, message trace, ...
delete $resource->{$k};
}

@ -82,8 +82,9 @@ sub item_rs {
if($type eq "rulesets") {
if($c->user->roles eq "admin") {
$item_rs = $c->model('DB')->resultset('voip_rewrite_rule_sets');
} else {
return;
} elsif($c->user->roles eq "reseller") {
$item_rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')
->search_rs({reseller_id => $c->user->reseller_id});
}
} else {
die "You should not reach this";
@ -103,6 +104,10 @@ sub update_item {
delete $resource->{id};
if($c->user->roles eq "reseller") {
$resource->{reseller_id} = $old_resource->{reseller_id}; # prohibit change
}
if($old_resource->{reseller_id} != $resource->{reseller_id}) {
my $reseller = $c->model('DB')->resultset('resellers')
->find($resource->{reseller_id});

@ -62,8 +62,12 @@ sub item_rs {
if($type eq "rules") {
if($c->user->roles eq "admin") {
$item_rs = $c->model('DB')->resultset('voip_rewrite_rules');
} else {
return;
} elsif ($c->user->roles eq "reseller") {
$item_rs = $c->model('DB')->resultset('voip_rewrite_rules')->search_rs({
'ruleset.reseller_id' => $c->user->reseller_id,
},{
join => 'ruleset'
});
}
} else {
die "You should not reach this";

@ -214,68 +214,102 @@ sub create_preference_form {
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
my $preference_id = $c->stash->{preference}->first ? $c->stash->{preference}->first->id : undef;
my $attribute = $c->stash->{preference_meta}->attribute;
my $preference_id = $c->stash->{preference}->first ? $c->stash->{preference}->first->id : undef;
my $attribute = $c->stash->{preference_meta}->attribute;
if ($attribute eq "allowed_ips") {
unless(validate_ipnet($form->field($attribute))) {
goto OUT;
}
unless (defined $aip_group_id) {
#TODO put this in a transaction
my $new_group = $c->model('DB')->resultset('voip_aig_sequence')
->create({});
my $aig_preference_id = $c->model('DB')
->resultset('voip_preferences')
->find({ attribute => 'allowed_ips_grp' })
->id;
$pref_rs->create({
value => $new_group->id,
attribute_id => $aig_preference_id,
});
$aip_group_id = $new_group->id;
$aip_grp_rs = $c->model('DB')->resultset('voip_allowed_ip_groups')
->search({ group_id => $aip_group_id });
$c->model('DB')->resultset('voip_aig_sequence')->search_rs({
id => { '<' => $new_group->id },
})->delete_all;
try {
my $new_group = $c->model('DB')->resultset('voip_aig_sequence')
->create({});
my $aig_preference_id = $c->model('DB')
->resultset('voip_preferences')
->find({ attribute => 'allowed_ips_grp' })
->id;
$pref_rs->create({
value => $new_group->id,
attribute_id => $aig_preference_id,
});
$aip_group_id = $new_group->id;
$aip_grp_rs = $c->model('DB')->resultset('voip_allowed_ip_groups')
->search({ group_id => $aip_group_id });
$c->model('DB')->resultset('voip_aig_sequence')->search_rs({
id => { '<' => $new_group->id },
})->delete_all;
} catch($e) {
$c->log->error("failed to generate ip group sequence: $e");
$c->flash(messages => [{type => 'error', text => "Failed to generate ip group sequence."}]);
$c->response->redirect($base_uri);
return 1;
}
}
try {
$aip_grp_rs->create({
group_id => $aip_group_id,
ipnet => $form->field($attribute)->value,
});
} catch($e) {
$c->log->error("failed to create allowed_ip_grp: $e");
$c->flash(messages => [{type => 'error', text => "Failed to create allowed_ip_grp."}]);
$c->response->redirect($base_uri);
return 1;
}
$aip_grp_rs->create({
group_id => $aip_group_id,
ipnet => $form->field($attribute)->value,
});
} elsif ($attribute eq "man_allowed_ips") {
unless(validate_ipnet($form->field($attribute))) {
goto OUT;
}
unless (defined $man_aip_group_id) {
#TODO put this in a transaction
my $new_group = $c->model('DB')->resultset('voip_aig_sequence')
->create({});
my $man_aig_preference_id = $c->model('DB')
->resultset('voip_preferences')
->find({ attribute => 'man_allowed_ips_grp' })
->id;
$pref_rs->create({
value => $new_group->id,
attribute_id => $man_aig_preference_id,
});
$man_aip_group_id = $new_group->id;
$man_aip_grp_rs = $c->model('DB')->resultset('voip_allowed_ip_groups')
->search({ group_id => $man_aip_group_id });
$c->model('DB')->resultset('voip_aig_sequence')->search_rs({
id => { '<' => $new_group->id },
})->delete_all;
try {
my $new_group = $c->model('DB')->resultset('voip_aig_sequence')
->create({});
my $man_aig_preference_id = $c->model('DB')
->resultset('voip_preferences')
->find({ attribute => 'man_allowed_ips_grp' })
->id;
$pref_rs->create({
value => $new_group->id,
attribute_id => $man_aig_preference_id,
});
$man_aip_group_id = $new_group->id;
$man_aip_grp_rs = $c->model('DB')->resultset('voip_allowed_ip_groups')
->search({ group_id => $man_aip_group_id });
$c->model('DB')->resultset('voip_aig_sequence')->search_rs({
id => { '<' => $new_group->id },
})->delete_all;
} catch($e) {
$c->log->error("failed to create manual ip group sequence: $e");
$c->flash(messages => [{type => 'error', text => "Failed to generate manual ip group sequence."}]);
$c->response->redirect($base_uri);
return 1;
}
}
try {
$man_aip_grp_rs->create({
group_id => $man_aip_group_id,
ipnet => $form->field($attribute)->value,
});
} catch($e) {
$c->log->error("failed to create man_allowed_ip_grp: $e");
$c->flash(messages => [{type => 'error', text => "Failed to create man_allowed_ip_grp."}]);
$c->response->redirect($base_uri);
return 1;
}
$man_aip_grp_rs->create({
group_id => $man_aip_group_id,
ipnet => $form->field($attribute)->value,
});
} elsif ($c->stash->{preference_meta}->max_occur != 1) {
$pref_rs->create({
attribute_id => $c->stash->{preference_meta}->id,
value => $form->field($c->stash->{preference_meta}->attribute)->value,
});
try {
$pref_rs->create({
attribute_id => $c->stash->{preference_meta}->id,
value => $form->field($c->stash->{preference_meta}->attribute)->value,
});
} catch($e) {
$c->log->error("failed to create preference $attribute: $e");
$c->flash(messages => [{type => 'error', text => "Failed to delete preference $attribute."}]);
$c->response->redirect($base_uri);
return 1;
}
} elsif ($attribute eq "rewrite_rule_set") {
my $selected_rwrs = $c->stash->{rwr_sets_rs}->find(
$form->field($attribute)->value
@ -295,14 +329,20 @@ sub create_preference_form {
my $attribute_id = $c->model('DB')->resultset('voip_preferences')
->find({attribute => $attribute."_id"})->id;
my $preference = $pref_rs->search({ attribute_id => $attribute_id });
if(!defined $selected_level) {
$preference->first->delete if $preference->first;
} elsif($preference->first) {
$preference->first->update({ value => $selected_level->id });
} else {
$preference->create({ value => $selected_level->id });
try {
my $preference = $pref_rs->search({ attribute_id => $attribute_id });
if(!defined $selected_level) {
$preference->first->delete if $preference->first;
} elsif($preference->first) {
$preference->first->update({ value => $selected_level->id });
} else {
$preference->create({ value => $selected_level->id });
}
} catch($e) {
$c->log->error("failed to update preference $attribute: $e");
$c->flash(messages => [{type => 'error', text => "Failed to update preference $attribute."}]);
$c->response->redirect($base_uri);
return 1;
}
$c->flash(messages => [{type => 'success', text => "Preference $attribute successfully updated."}]);
@ -313,14 +353,21 @@ sub create_preference_form {
$form->field($attribute)->value
);
my $preference = $pref_rs->search({
attribute_id => $c->stash->{preference_meta}->id });
if(!defined $selected_set) {
$preference->first->delete if $preference->first;
} elsif($preference->first) {
$preference->first->update({ value => $selected_set->id });
} else {
$preference->create({ value => $selected_set->id });
try {
my $preference = $pref_rs->search({
attribute_id => $c->stash->{preference_meta}->id });
if(!defined $selected_set) {
$preference->first->delete if $preference->first;
} elsif($preference->first) {
$preference->first->update({ value => $selected_set->id });
} else {
$preference->create({ value => $selected_set->id });
}
} catch($e) {
$c->log->error("failed to update preference $attribute: $e");
$c->flash(messages => [{type => 'error', text => "Failed to update preference $attribute."}]);
$c->response->redirect($base_uri);
return 1;
}
$c->flash(messages => [{type => 'success', text => "Preference $attribute successfully updated."}]);
@ -331,14 +378,21 @@ sub create_preference_form {
$form->field($attribute)->value
);
my $preference = $pref_rs->search({
attribute_id => $c->stash->{preference_meta}->id });
if(!defined $selected_set) {
$preference->first->delete if $preference->first;
} elsif($preference->first) {
$preference->first->update({ value => $selected_set->id });
} else {
$preference->create({ value => $selected_set->id });
try {
my $preference = $pref_rs->search({
attribute_id => $c->stash->{preference_meta}->id });
if(!defined $selected_set) {
$preference->first->delete if $preference->first;
} elsif($preference->first) {
$preference->first->update({ value => $selected_set->id });
} else {
$preference->create({ value => $selected_set->id });
}
} catch($e) {
$c->log->error("failed to update preference $attribute: $e");
$c->flash(messages => [{type => 'error', text => "Failed to update preference $attribute."}]);
$c->response->redirect($base_uri);
return 1;
}
$c->flash(messages => [{type => 'success', text => "Preference $attribute successfully updated."}]);
@ -346,22 +400,43 @@ sub create_preference_form {
return 1;
} else {
if( ($c->stash->{preference_meta}->data_type ne 'enum' &&
$form->field($attribute)->value eq '') ||
(!defined $form->field($attribute)->value || $form->field($attribute)->value eq '')) ||
($c->stash->{preference_meta}->data_type eq 'enum' &&
! defined $form->field($attribute)->value)
) {
my $preference = $pref_rs->find($preference_id);
$preference->delete if $preference;
try {
my $preference = $pref_rs->find($preference_id);
$preference->delete if $preference;
} catch($e) {
$c->log->error("failed to delete preference $attribute: $e");
$c->flash(messages => [{type => 'error', text => "Failed to delete preference $attribute."}]);
$c->response->redirect($base_uri);
return 1;
}
} elsif($c->stash->{preference_meta}->data_type eq 'boolean' &&
$form->field($attribute)->value == 0) {
my $preference = $pref_rs->find($preference_id);
$preference->delete if $preference;
try {
my $preference = $pref_rs->find($preference_id);
$preference->delete if $preference;
} catch($e) {
$c->log->error("failed to delete preference $attribute: $e");
$c->flash(messages => [{type => 'error', text => "Failed to delete preference $attribute."}]);
$c->response->redirect($base_uri);
return 1;
}
} else {
$pref_rs->update_or_create({
id => $preference_id,
attribute_id => $c->stash->{preference_meta}->id,
value => $form->field($attribute)->value,
});
try {
$pref_rs->update_or_create({
id => $preference_id,
attribute_id => $c->stash->{preference_meta}->id,
value => $form->field($attribute)->value,
});
} catch($e) {
$c->log->error("failed to update preference $attribute: $e");
$c->flash(messages => [{type => 'error', text => "Failed to update preference $attribute."}]);
$c->response->redirect($base_uri);
return 1;
}
}
$c->flash(messages => [{type => 'success', text => "Preference $attribute successfully updated."}]);
$c->response->redirect($base_uri);

@ -30,29 +30,35 @@ around handle => sub {
peering_sum => $c->model('DB')->resultset('contract_balances')->search_rs({
'start' => { '>=' => $stime },
'end' => { '<' => $etime},
'product.class' => 'sippeering',
}, {
join => {
'contract' => { 'billing_mappings' => 'product' },
},
-exists => $c->model('DB')->resultset('billing_mappings')->search({
contract_id => \'= me.contract_id',
'product.class' => 'sippeering',
},{
alias => 'myinner',
join => 'product',
})->as_query,
})->get_column('cash_balance_interval')->sum,
reseller_sum => $c->model('DB')->resultset('contract_balances')->search_rs({
'start' => { '>=' => $stime },
'end' => { '<' => $etime},
'product.class' => 'reseller',
}, {
join => {
'contract' => { 'billing_mappings' => 'product' },
},
-exists => $c->model('DB')->resultset('billing_mappings')->search({
contract_id => \'= me.contract_id',
'product.class' => 'reseller',
},{
alias => 'myinner',
join => 'product',
})->as_query,
})->get_column('cash_balance_interval')->sum,
customer_sum => $c->model('DB')->resultset('contract_balances')->search_rs({
'start' => { '>=' => $stime },
'end' => { '<' => $etime},
'billing_mappings.product_id' => undef,
}, {
join => {
'contract' => 'billing_mappings',
},
-exists => $c->model('DB')->resultset('billing_mappings')->search({
contract_id => \'= me.contract_id',
'product.class' => 'sipaccount',
},{
alias => 'myinner',
join => 'product',
})->as_query,
})->get_column('cash_balance_interval')->sum,
);
return;

@ -34,6 +34,15 @@
<script type="text/javascript" src="/js/plugins/msgbox/jquery.msgbox.js"></script>
<link rel="stylesheet" type="text/css" href="/js/plugins/msgbox/jquery.msgbox.css" />
<link rel="stylesheet" type="text/css" href="/css/main.css" />
[% IF c.user -%]
[% IF c.user.roles == "reseller" && c.user.reseller.branding.css -%]
<link rel="stylesheet" href="[% c.uri_for_action('/reseller/get_branding_css', [c.user.reseller_id]) %]">
[% ELSIF c.user.roles != "admin" && c.user.contract.contact.reseller.branding.css -%]
<link rel="stylesheet" href="[% c.uri_for_action('/reseller/get_branding_css', [c.user.contract.contact.reseller_id]) %]">
[% END -%]
[% END -%]
</head>
[% content %]
</html>

@ -220,5 +220,68 @@
</div>
</div>
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#reseller_details" href="#collapse_branding">[% c.loc('Branding') %]</a>
</div>
<div class="accordion-body collapse" id="collapse_branding">
<div class="accordion-inner">
[% UNLESS c.user.read_only -%]
<span>
<a href="[% c.uri_for_action('/reseller/edit_branding_css', c.req.captures) %]" class="btn btn-large btn-primary"><i class="icon-edit"></i> Edit Branding</a>
[% IF branding.logo -%]
<a href="[% c.uri_for_action('/reseller/delete_branding_logo', c.req.captures) %]" class="btn btn-large btn-primary"><i class="icon-trash"></i> Delete Logo</a>
[% END -%]
</span>
<div class="ngcp-separator"></div>
[% END -%]
[% IF branding.defined -%]
[% IF branding.logo.defined -%]
<h3>Custom Logo</h3>
<img src="[% c.uri_for_action('/reseller/get_branding_logo', c.req.captures) %]">
<p>
[% c.loc("You can use the logo by adding the following CSS to the Custom CSS below:") %]
<pre>
#header .brand {
background: url([% c.uri_for_action('/reseller/get_branding_logo', c.req.captures) %]) no-repeat 0 0;
background-size: 280px 32px;
}
</pre>
</p>
[% ELSE -%]
No logo uploaded.
[% END -%]
<h3>Custom CSS</h3>
<p>
<pre style="color:#000">
[% branding.css %]
</pre>
</p>
[% ELSE -%]
No branding specified, using standard branding.
[% END -%]
</div>
</div>
</div>
[% IF branding_edit_flag == 1 -%]
[%
IF form.has_for_js;
form.render_repeatable_js;
END;
PROCESS "helpers/modal.tt";
modal_header(m.edit_flag = branding_edit_flag,
m.name = "Reseller Branding");
translate_form(branding_form).render;
modal_footer();
modal_script(m.close_target = close_target);
-%]
[% END -%]
[% # vim: set tabstop=4 syntax=html expandtab: -%]

Loading…
Cancel
Save