1 %# BEGIN BPS TAGGED BLOCK {{{
5 %# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
6 %# <sales@bestpractical.com>
8 %# (Except where explicitly superseded by other copyright notices)
13 %# This work is made available to you under the terms of Version 2 of
14 %# the GNU General Public License. A copy of that license should have
15 %# been provided with this software, but in any event can be snarfed
18 %# This work is distributed in the hope that it will be useful, but
19 %# WITHOUT ANY WARRANTY; without even the implied warranty of
20 %# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 %# General Public License for more details.
23 %# You should have received a copy of the GNU General Public License
24 %# along with this program; if not, write to the Free Software
25 %# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 %# 02110-1301 or visit their web page on the internet at
27 %# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
30 %# CONTRIBUTION SUBMISSION POLICY:
32 %# (The following paragraph is not intended to limit the rights granted
33 %# to you to modify and distribute this software under the terms of
34 %# the GNU General Public License and is only of importance to you if
35 %# you choose to contribute your changes and enhancements to the
36 %# community by submitting them to Best Practical Solutions, LLC.)
38 %# By intentionally submitting any modifications, corrections or
39 %# derivatives to this work, or any other work intended for use with
40 %# Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 %# you are the copyright holder for those contributions and you grant
42 %# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43 %# royalty-free, perpetual, license to use, copy, create derivative
44 %# works based on those contributions, and sublicense and distribute
45 %# those contributions and any derivatives thereof.
47 %# END BPS TAGGED BLOCK }}}
48 <& /Elements/Header, Title => $title &>
51 <& /Widgets/SelectionBox:header, nojs => 1 &>
53 <& /Elements/ListActions, actions => \@results &>
55 <& Elements/Deleted, searches => \@deleted, Dashboard => $Dashboard &>
57 <& Elements/HiddenSearches, searches => \@hidden_searches, Dashboard => $Dashboard &>
59 <table width="100%" border="0">
60 % for my $pane (@panes) {
61 <tr><td valign="top" class="boxcontainer">
62 <form action="Queries.html" name="Dashboard-<%$pane->{Name}%>" method="post" enctype="multipart/form-data">
63 <input type="hidden" class="hidden" name="id" value="<%$Dashboard->Id%>" />
64 <input type="hidden" class="hidden" name="Privacy" value="<%$Dashboard->Privacy%>" />
66 <&| /Widgets/TitleBox, title => $pane->{DisplayName} &>
67 % my ( $pane_name ) = $pane->{Name} =~ /Searches-(.+)/;
68 <& /Widgets/SelectionBox:show, self => $pane, nojs => 1, grep( {
69 $_->{pane} eq $pane_name} @deleted ) ? ( ShowUpdate => 1 ) : () &>
81 my $Dashboard = RT::Dashboard->new($session{'CurrentUser'});
82 my ($ok, $msg) = $Dashboard->LoadById($id);
83 $ok || Abort(loc("Couldn't load dashboard [_1]: [_2]", $id, $msg));
84 my $title = loc("Modify the content of dashboard [_1]", $Dashboard->Name);
91 # add portlets (homepage componenets)
92 my @components = @{ RT->Config->Get('HomepageComponents') };
94 for my $desc (@components) {
95 my $name = "component-$desc";
96 push @items, [$name, $desc];
97 $desc_of{$name} = $desc;
98 $still_exists{$name} = 1;
102 my @dashboards = $m->comp("/Dashboards/Elements/ListOfDashboards");
103 for my $dashboard (@dashboards) {
104 # Users *can* set up mutually recursive dashboards, but don't make it THIS
105 # easy for them to shoot themselves in the foot.
106 next if $dashboard->Id == $Dashboard->Id;
108 my $name = 'dashboard-' . $dashboard->Id . '-' . $dashboard->Privacy;
109 my $desc = "Dashboard: " . $dashboard->Name;
110 push @items, [$name, $desc];
111 $desc_of{$name} = $desc;
112 $still_exists{$name} = 1;
116 my @objs = RT::System->new($session{'CurrentUser'});
118 push @objs, RT::SavedSearch->new( $session{CurrentUser} )->ObjectsForLoading
119 if $session{'CurrentUser'}->HasRight( Right => 'LoadSavedSearch',
120 Object => $RT::System );
122 for my $object (@objs) {
123 for ($m->comp("/Search/Elements/SearchesForObject", Object => $object)) {
124 my ($desc, $search) = @$_;
125 my $SearchType = $search->Content->{'SearchType'} || 'Ticket';
126 my $type = ($SearchType eq 'Ticket') ? 'Saved Search' : $SearchType; # loc
127 $desc = "$type: $desc";
128 my $privacy = $Dashboard->_build_privacy($object);
129 my $name = 'search-' . $search->Id . '-' . $privacy;
130 push @items, [$name, $desc];
131 $desc_of{$name} = $desc;
132 $still_exists{$name} = 1;
136 # Get the list of portlets already in use
139 my $panes = $Dashboard->Panes;
140 for my $pane (keys %$panes) {
141 for my $portlet (@{ $panes->{$pane} }) {
143 my $type = $portlet->{portlet_type};
145 if ($type eq 'search' || $type eq 'dashboard') {
146 $name = join '-', $type, $portlet->{id}, $portlet->{privacy};
148 elsif ($type eq 'component') {
149 $name = join '-', 'component', $portlet->{component};
152 if (!$still_exists{$name}) {
156 description => $portlet->{description},
161 push @{ $selected{$pane} }, $name;
162 $desc_of{$name} = $portlet->{description};
168 CallbackName => 'PopulatePossibilities',
169 Dashboard => $Dashboard,
171 desc_of => \%desc_of,
172 still_exists => \%still_exists,
173 selected => \%selected,
176 # Create selectionbox widgets for those portlets
179 'body' => loc('Body'),
180 'sidebar' => loc('Sidebar'),
184 CallbackName => 'Panes',
185 Dashboard => $Dashboard,
186 panes => \%pane_name,
190 for my $pane (keys %pane_name) {
192 '/Widgets/SelectionBox:new',
193 Action => 'Queries.html',
194 Name => "Searches-$pane",
195 DisplayName => $pane_name{$pane},
196 Available => \@items,
197 Selected => $selected{$pane},
203 CallbackName => 'Submit',
204 Dashboard => $Dashboard,
205 Selected => $self->{Current},
210 for (@{ $self->{Current} }) {
212 my $desc = $desc_of{$item};
213 my $portlet_type = $1 if $item =~ s/^(\w+)-//;
215 if ($portlet_type eq 'search' || $portlet_type eq 'dashboard') {
216 my ($id, $privacy) = split '-', $item, 2;
218 portlet_type => $portlet_type,
221 description => $desc,
225 elsif ($portlet_type eq 'component') {
226 # Absolute paths stay absolute, relative paths go into
227 # /Elements. This way, extensions that add portlets work.
229 $path = "/Elements/$path" if substr($path, 0, 1) ne '/';
232 portlet_type => $portlet_type,
235 description => $item,
241 # we want to keep all the other panes the same
242 my $panes = $Dashboard->Panes;
243 $panes->{$pane} = \@portlets;
245 # remove "deleted" warnings about this pane
246 @deleted = grep { $_->{pane} ne $pane } @deleted;
249 CallbackName => 'BeforeUpdate',
250 Dashboard => $Dashboard,
254 my ($ok, $msg) = $Dashboard->Update(Panes => $panes);
257 push @results, loc("Dashboard updated");
260 push @results, loc("Dashboard could not be updated: [_1]", $msg);
268 $m->comp('/Widgets/SelectionBox:process', %ARGS, self => $_, nojs => 1)
271 my @hidden_searches = $Dashboard->PossibleHiddenSearches;
274 $id => '' unless defined $id