parent
07e3c0a699
commit
02d27a814b
@ -0,0 +1,337 @@
|
||||
package NGCP::Panel::Controller::Sound;
|
||||
use Sipwise::Base;
|
||||
use namespace::autoclean;
|
||||
|
||||
BEGIN { extends 'Catalyst::Controller'; }
|
||||
|
||||
use NGCP::Panel::Form::SoundSet;
|
||||
use NGCP::Panel::Form::SoundFile;
|
||||
use File::Type;
|
||||
|
||||
sub auto :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
|
||||
my ($self, $c) = @_;
|
||||
$c->log->debug(__PACKAGE__ . '::auto');
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub sets_list :Chained('/') :PathPart('sound') :CaptureArgs(0) {
|
||||
my ( $self, $c ) = @_;
|
||||
|
||||
my $sets_rs = $c->model('provisioning')->resultset('voip_sound_sets');
|
||||
$c->stash(sets_rs => $sets_rs);
|
||||
|
||||
$c->stash(has_edit => 1);
|
||||
$c->stash(has_delete => 1);
|
||||
$c->stash(template => 'sound/list.tt');
|
||||
}
|
||||
|
||||
sub root :Chained('sets_list') :PathPart('') :Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
}
|
||||
|
||||
sub ajax :Chained('sets_list') :PathPart('ajax') :Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
my $resultset = $c->stash->{sets_rs};
|
||||
|
||||
$c->forward( "/ajax_process_resultset", [$resultset,
|
||||
["id", "name", "description"],
|
||||
[1,2]]);
|
||||
|
||||
$c->detach( $c->view("JSON") );
|
||||
}
|
||||
|
||||
sub base :Chained('sets_list') :PathPart('') :CaptureArgs(1) {
|
||||
my ($self, $c, $set_id) = @_;
|
||||
|
||||
unless($set_id && $set_id->is_integer) {
|
||||
$c->flash(messages => [{type => 'error', text => 'Invalid Sound Set id detected!'}]);
|
||||
$c->response->redirect($c->uri_for());
|
||||
$c->detach;
|
||||
return;
|
||||
}
|
||||
|
||||
my $res = $c->stash->{sets_rs}->find($set_id);
|
||||
unless(defined($res)) {
|
||||
$c->flash(messages => [{type => 'error', text => 'Sound Set does not exist!'}]);
|
||||
$c->response->redirect($c->uri_for());
|
||||
$c->detach;
|
||||
return;
|
||||
}
|
||||
$c->stash(set_result => $res);
|
||||
}
|
||||
|
||||
sub edit :Chained('base') :PathPart('edit') {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
my $posted = ($c->request->method eq 'POST');
|
||||
my $form = NGCP::Panel::Form::SoundSet->new;
|
||||
$form->process(
|
||||
posted => $posted,
|
||||
params => $c->request->params,
|
||||
action => $c->uri_for_action('/sound/edit'),
|
||||
item => $c->stash->{set_result},
|
||||
);
|
||||
if($form->validated) {
|
||||
$c->flash(messages => [{type => 'success', text => 'Sound Set successfully changed!'}]);
|
||||
$c->response->redirect($c->uri_for());
|
||||
return;
|
||||
}
|
||||
|
||||
$c->stash(form => $form);
|
||||
$c->stash(edit_flag => 1);
|
||||
}
|
||||
|
||||
sub delete :Chained('base') :PathPart('delete') {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
try {
|
||||
$c->stash->{set_result}->delete;
|
||||
$c->flash(messages => [{type => 'success', text => 'Sound Set successfully deleted!'}]);
|
||||
} catch (DBIx::Class::Exception $e) {
|
||||
$c->flash(messages => [{type => 'error', text => 'Delete failed.'}]);
|
||||
$c->log->info("Delete failed: " . $e);
|
||||
};
|
||||
$c->response->redirect($c->uri_for());
|
||||
}
|
||||
|
||||
sub create :Chained('sets_list') :PathPart('create') :Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
my $form = NGCP::Panel::Form::SoundSet->new;
|
||||
$form->process(
|
||||
posted => ($c->request->method eq 'POST'),
|
||||
params => $c->request->params,
|
||||
action => $c->uri_for_action('/sound/create'),
|
||||
item => $c->stash->{sets_rs}->new_result({}),
|
||||
);
|
||||
if($form->validated) {
|
||||
$c->flash(messages => [{type => 'success', text => 'Sound Set successfully created!'}]);
|
||||
$c->response->redirect($c->uri_for_action('/sound/root'));
|
||||
return;
|
||||
}
|
||||
|
||||
$c->stash(close_target => $c->uri_for());
|
||||
$c->stash(create_flag => 1);
|
||||
$c->stash(form => $form);
|
||||
}
|
||||
|
||||
sub handles_list :Chained('base') :PathPart('handles') :CaptureArgs(0) {
|
||||
my ( $self, $c ) = @_;
|
||||
|
||||
my $files_rs = $c->stash->{set_result}->voip_sound_files;
|
||||
$c->stash(files_rs => $files_rs);
|
||||
$c->stash(handles_base_uri =>
|
||||
$c->uri_for_action("/sound/handles_root", [$c->req->captures->[0]]));
|
||||
|
||||
my $handles_rs = $c->model('provisioning')->resultset('voip_sound_groups')
|
||||
->search({
|
||||
},{
|
||||
select => ['groups.name', \'handles.name', \'handles.id', 'files.filename', 'files.loopplay', 'files.codec'],
|
||||
as => [ 'groupname', 'handlename', 'handleid', 'filename', 'loopplay', 'codec'],
|
||||
alias => 'groups',
|
||||
from => [
|
||||
{ groups => 'voip_sound_groups' },
|
||||
[
|
||||
{ handles => 'voip_sound_handles', -join_type=>'left'},
|
||||
{ 'groups.id' => 'handles.group_id'},
|
||||
],
|
||||
[
|
||||
{ files => 'voip_sound_files', -join_type => 'left'},
|
||||
{ 'handles.id' => { '=' => \'files.handle_id'}, 'files.set_id' => $c->stash->{set_result}->id},
|
||||
],
|
||||
],
|
||||
});
|
||||
|
||||
my @rows = $handles_rs->all;
|
||||
|
||||
my %groups;
|
||||
for my $handle (@rows) {
|
||||
$groups{ $handle->get_column('groupname') } = []
|
||||
unless exists $groups{ $handle->get_column('groupname') };
|
||||
push $groups{ $handle->get_column('groupname') }, $handle;
|
||||
}
|
||||
$c->stash(sound_groups => \%groups);
|
||||
|
||||
$c->stash(has_edit => 1);
|
||||
$c->stash(has_delete => 1);
|
||||
$c->stash(template => 'sound/handles_list.tt');
|
||||
}
|
||||
|
||||
sub handles_root :Chained('handles_list') :PathPart('') :Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
}
|
||||
|
||||
sub handles_base :Chained('handles_list') :PathPart('') :CaptureArgs(1) {
|
||||
my ($self, $c, $handle_id) = @_;
|
||||
|
||||
unless($handle_id && $handle_id->is_integer) {
|
||||
$c->flash(messages => [{type => 'error', text => 'Invalid Sound Handle id detected!'}]);
|
||||
$c->response->redirect($c->stash->{handles_base_uri});
|
||||
$c->detach;
|
||||
return;
|
||||
}
|
||||
|
||||
my $res = $c->stash->{files_rs}->find_or_new(handle_id => $handle_id);
|
||||
unless(defined($res)) {
|
||||
$c->flash(messages => [{type => 'error', text => 'Sound File could not be found/created!'}]);
|
||||
$c->response->redirect($c->stash->{handles_base_uri});
|
||||
$c->detach;
|
||||
return;
|
||||
}
|
||||
$c->stash(file_result => $res);
|
||||
}
|
||||
|
||||
sub handles_edit :Chained('handles_base') :PathPart('edit') {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
my $posted = ($c->request->method eq 'POST');
|
||||
my $upload = $c->req->upload('soundfile');
|
||||
my %params = (
|
||||
%{ $c->request->params },
|
||||
soundfile => $posted ? $upload : undef,
|
||||
);
|
||||
my $file_result = $c->stash->{file_result};
|
||||
my $form = NGCP::Panel::Form::SoundFile->new;
|
||||
$form->process(
|
||||
posted => $posted,
|
||||
params => \%params,
|
||||
item => $file_result,
|
||||
);
|
||||
if($form->validated) {
|
||||
|
||||
if (defined $upload) {
|
||||
my $soundfile = eval { $upload->slurp };
|
||||
my $filename = eval { $upload->filename };
|
||||
|
||||
my $ft = File::Type->new();
|
||||
unless ($ft->checktype_contents($soundfile) eq 'audio/x-wav') {
|
||||
$c->flash(messages => [{type => 'error', text => 'Invalid File Type detected!'}]);
|
||||
$c->response->redirect($c->stash->{handles_base_uri});
|
||||
return;
|
||||
}
|
||||
$file_result->update({
|
||||
filename => $filename,
|
||||
data => $soundfile,
|
||||
codec => 'WAV',
|
||||
});
|
||||
}
|
||||
|
||||
$c->flash(messages => [{type => 'success', text => 'Sound File successfully changed!'}]);
|
||||
$c->response->redirect($c->stash->{handles_base_uri});
|
||||
return;
|
||||
}
|
||||
|
||||
$c->stash(form => $form);
|
||||
$c->stash(edit_flag => 1);
|
||||
}
|
||||
|
||||
sub handles_delete :Chained('handles_base') :PathPart('delete') {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
try {
|
||||
$c->stash->{file_result}->delete;
|
||||
$c->flash(messages => [{type => 'success', text => 'Sound File successfully deleted!'}]);
|
||||
} catch (DBIx::Class::Exception $e) {
|
||||
$c->flash(messages => [{type => 'error', text => 'Delete failed.'}]);
|
||||
$c->log->info("Delete failed: " . $e);
|
||||
};
|
||||
$c->response->redirect($c->stash->{handles_base_uri});
|
||||
}
|
||||
|
||||
__PACKAGE__->meta->make_immutable;
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
NGCP::Panel::Controller::Sound - Manage Sounds
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Show/Edit/Create/Delete Sound Sets.
|
||||
|
||||
Show/Upload Sound Files in Sound Sets.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 auto
|
||||
|
||||
Grants access to admin and reseller role.
|
||||
|
||||
=head2 sets_list
|
||||
|
||||
Basis for provisioning.voip_sound_sets.
|
||||
Provides sets_rs in stash.
|
||||
|
||||
=head2 root
|
||||
|
||||
Display Sound Sets through F<sound/list.tt> template.
|
||||
|
||||
=head2 ajax
|
||||
|
||||
Get provisioning.voip_sound_sets from db and output them as JSON.
|
||||
The format is meant for parsing with datatables.
|
||||
|
||||
=head2 base
|
||||
|
||||
Fetch a provisioning.voip_sound_sets row from the database by its id.
|
||||
The resultset is exported to stash as "set_result".
|
||||
|
||||
=head2 edit
|
||||
|
||||
Show a modal to edit the Sound Set determined by L</base> using the form
|
||||
L<NGCP::Panel::Form::SoundSet>.
|
||||
|
||||
=head2 delete
|
||||
|
||||
Delete the Sound Set determined by L</base>.
|
||||
|
||||
=head2 create
|
||||
|
||||
Show modal to create a new Sound Set using the form
|
||||
L<NGCP::Panel::Form::SoundSet>.
|
||||
|
||||
=head2 handles_list
|
||||
|
||||
Basis for provisioning.voip_sound_handles grouped by voip_sound_groups with
|
||||
the actual data in voip_sound_files.
|
||||
Stashes:
|
||||
* handles_base_uri: To show L</pattern_root>
|
||||
* files_rs: Resultset of voip_sound_files in the current voip_sound_group
|
||||
* sound_groups: Hashref of sound_goups with handles JOIN files inside
|
||||
(used in the template F<sound/handles_list.tt>)
|
||||
|
||||
=head2 handles_root
|
||||
|
||||
Display Sound Files through F<sound/handles_list.tt> template accordion
|
||||
grouped by sound_groups.
|
||||
|
||||
=head2 handles_base
|
||||
|
||||
Fetch a provisioning.voip_sound_files row from the database by the id
|
||||
of the according voip_sound_handle. Create a new one if it doesn't exist but
|
||||
do not immediately update the db.
|
||||
The ResultClass is exported to stash as "file_result".
|
||||
|
||||
=head2 handles_edit
|
||||
|
||||
Show a modal to upload a file or set/unset loopplay using the form
|
||||
L<NGCP::Panel::Form::SoundFile>.
|
||||
|
||||
=head2 handles_delete
|
||||
|
||||
Delete the Sound File determined by L</base>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Gerhard Jungwirth C<< <gjungwirth@sipwise.com> >>
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
This library is free software. You can redistribute it and/or modify
|
||||
it under the same terms as Perl itself.
|
||||
|
||||
=cut
|
||||
|
||||
# vim: set tabstop=4 expandtab:
|
||||
@ -0,0 +1,66 @@
|
||||
package NGCP::Panel::Form::SoundFile;
|
||||
|
||||
use HTML::FormHandler::Moose;
|
||||
extends 'HTML::FormHandler::Model::DBIC';
|
||||
use Moose::Util::TypeConstraints;
|
||||
|
||||
use HTML::FormHandler::Widget::Block::Bootstrap;
|
||||
|
||||
has '+enctype' => ( default => 'multipart/form-data');
|
||||
has '+widget_wrapper' => ( default => 'Bootstrap' );
|
||||
sub build_render_list {[qw/fields actions/]}
|
||||
sub build_form_element_class { [qw/form-horizontal/] }
|
||||
|
||||
has_field 'loopplay' => (
|
||||
type => 'Boolean',
|
||||
);
|
||||
|
||||
has_field 'soundfile' => (
|
||||
type => 'Upload',
|
||||
noupdate => 1, #let controller handle the file
|
||||
max_size => undef,
|
||||
);
|
||||
|
||||
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/loopplay soundfile/],
|
||||
);
|
||||
|
||||
has_block 'actions' => (
|
||||
tag => 'div',
|
||||
class => [qw/modal-footer/],
|
||||
render_list => [qw/save/],
|
||||
);
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
NGCP::Panel::Form::SoundFile
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Form to modify a provisioning.voip_sound_files row.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Gerhard Jungwirth
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
This library is free software. You can redistribute it and/or modify
|
||||
it under the same terms as Perl itself.
|
||||
|
||||
=cut
|
||||
|
||||
# vim: set tabstop=4 expandtab:
|
||||
@ -0,0 +1,65 @@
|
||||
package NGCP::Panel::Form::SoundSet;
|
||||
|
||||
use HTML::FormHandler::Moose;
|
||||
extends 'HTML::FormHandler::Model::DBIC';
|
||||
use Moose::Util::TypeConstraints;
|
||||
|
||||
use HTML::FormHandler::Widget::Block::Bootstrap;
|
||||
|
||||
has '+widget_wrapper' => ( default => 'Bootstrap' );
|
||||
sub build_render_list {[qw/fields actions/]}
|
||||
sub build_form_element_class { [qw/form-horizontal/] }
|
||||
|
||||
has_field 'name' => (
|
||||
type => 'Text',
|
||||
label => 'Name',
|
||||
required => 1,
|
||||
);
|
||||
|
||||
has_field 'description' => (
|
||||
type => 'Text',
|
||||
);
|
||||
|
||||
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/name description/],
|
||||
);
|
||||
|
||||
has_block 'actions' => (
|
||||
tag => 'div',
|
||||
class => [qw/modal-footer/],
|
||||
render_list => [qw/save/],
|
||||
);
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
NGCP::Panel::Form::SoundSet
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Form to modify a provisioning.voip_sound_sets row.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Gerhard Jungwirth
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
This library is free software. You can redistribute it and/or modify
|
||||
it under the same terms as Perl itself.
|
||||
|
||||
=cut
|
||||
|
||||
# vim: set tabstop=4 expandtab:
|
||||
@ -0,0 +1,110 @@
|
||||
[% site_config.title = 'Manage Sound Set ' _ set_result.name -%]
|
||||
|
||||
<a href="[% c.uri_for() %]">< Back</a>
|
||||
|
||||
[% IF messages -%]
|
||||
<div class="row">
|
||||
[% FOREACH m IN messages -%]
|
||||
<div class="alert alert-[% m.type %]">[% m.text %]</div>
|
||||
[% END -%]
|
||||
</div>
|
||||
[% END -%]
|
||||
|
||||
<div class="ngcp-separator"></div>
|
||||
|
||||
<div class="accordion" id="sound_groups">
|
||||
[% FOREACH group IN sound_groups.pairs %]
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" data-parent="#sound_groups" href="#collapse[% group.key %]">[% group.key %]</a>
|
||||
</div>
|
||||
<div class="accordion-body collapse" id="collapse[% group.key %]">
|
||||
<div class="accordion-inner">
|
||||
|
||||
<table class="table table-bordered table-striped table-highlight table-hover" id="sounds_table[% group.key %]">
|
||||
<thead>
|
||||
<tr>
|
||||
[% # one for description -%]
|
||||
<th></th>
|
||||
<th>Name</th>
|
||||
<th>Filename</th>
|
||||
<th>Loop</th>
|
||||
[% # one for actions -%]
|
||||
<th class="span3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
[% FOREACH r IN group.value %]
|
||||
<tr class="sw_action_row">
|
||||
<td>
|
||||
<a href="#" onclick="$.msgbox(
|
||||
'[% r.description.squote | html %]',
|
||||
{
|
||||
type:'info',
|
||||
buttons:[
|
||||
{type:'cancel',value:'Close'}
|
||||
]
|
||||
}); return false;"><i class="icon-question-sign"></i></a>
|
||||
</td>
|
||||
<td>
|
||||
[% r.get_column("handlename") %]
|
||||
</td>
|
||||
<td>
|
||||
[% r.get_column("filename") %]
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" disabled="disabled" [% r.get_column('loopplay') ? 'checked="checked"' : '' %]>
|
||||
</td>
|
||||
<td>
|
||||
<div class="sw_actions pull-right">
|
||||
<a class="btn btn-small btn-primary" href="[% handles_base_uri _ "/" _ r.get_column("handleid") _ "/edit" %]"><i class="icon-edit"></i> Upload</a>
|
||||
<a class="btn btn-small btn-secondary" href="[% handles_base_uri _ "/" _ r.get_column("handleid") _ "/delete" %]"><i class="icon-edit"></i> Delete</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
[% END %]
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
[% END %]
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
$('#sound_groups').on('shown', function (e) {
|
||||
localStorage.setItem('lastTab', $("#sound_groups .in").attr('id'));
|
||||
});
|
||||
|
||||
//go to the latest tab, if it exists:
|
||||
var lastTab = localStorage.getItem('lastTab');
|
||||
if (lastTab) {
|
||||
$('#'+lastTab).addClass("in");//collapse("show"); collapse does annoying animation
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
[% IF edit_flag -%]
|
||||
|
||||
[%
|
||||
PROCESS "helpers/modal.tt";
|
||||
modal_header(m.create_flag=0,
|
||||
m.name = "Preference " _ file_result.handle.name );
|
||||
-%]
|
||||
[% form.render -%]
|
||||
[%
|
||||
modal_footer();
|
||||
-%]
|
||||
<script>
|
||||
$(function () {
|
||||
$('#mod_edit').modal({keyboard: false, backdrop: 'static'});
|
||||
$('#mod_close').click(function(event) {
|
||||
window.location.href="[% handles_base_uri %]";
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
[% END -%]
|
||||
[% # vim: set tabstop=4 syntax=html expandtab: -%]
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
[% META title = 'Sound Sets' -%]
|
||||
[%
|
||||
helper.name = 'Sound Sets';
|
||||
helper.messages = messages;
|
||||
helper.column_titles = [ '#', 'Name', 'Description' ];
|
||||
helper.column_fields = [ 'id', 'name', 'description' ];
|
||||
|
||||
helper.close_target = close_target;
|
||||
helper.create_flag = create_flag;
|
||||
helper.edit_flag = edit_flag;
|
||||
helper.form_object = form;
|
||||
helper.has_edit = has_edit;
|
||||
helper.has_delete = has_delete;
|
||||
helper.ajax_uri = c.uri_for_action( "/sound/ajax" );
|
||||
helper.base_uri = c.uri_for_action( "/sound/root" );
|
||||
|
||||
helper.extra_buttons = [
|
||||
[ 'Files', 'handles'],
|
||||
];
|
||||
|
||||
PROCESS 'helpers/datatables.tt';
|
||||
-%]
|
||||
[% # vim: set tabstop=4 syntax=html expandtab: -%]
|
||||
@ -0,0 +1,10 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use Test::More;
|
||||
|
||||
|
||||
use Catalyst::Test 'NGCP::Panel';
|
||||
use NGCP::Panel::Controller::Sound;
|
||||
|
||||
ok( request('/sound')->is_success || request('/sound')->is_redirect, 'Request should succeed' );
|
||||
done_testing();
|
||||
Loading…
Reference in new issue