• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

munin-monitoring / munin / 6722058331

01 Nov 2023 04:03PM UTC coverage: 51.434% (+0.02%) from 51.413%
6722058331

push

github

steveschnepp
m/g: add some extra args to rrd-graph

This enables a generic way to redefine the color palette.

Note:

  This is very crude so I won't document it publicly. If it is very
  useful, it should have a proper munin.conf configuration.

3 of 3 new or added lines in 1 file covered. (100.0%)

1112 of 2162 relevant lines covered (51.43%)

9651.4 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

7.54
/lib/Munin/Master/HTML.pm
1
package Munin::Master::HTML;
2

3
use strict;
1✔
4
use warnings;
1✔
5

6
use POSIX;
1✔
7
use HTML::Template::Pro;
1✔
8

9
use Munin::Master::Utils;
1✔
10

11
use Munin::Common::Logger;
1✔
12

13
use File::Basename;
1✔
14
use Data::Dumper;
1✔
15

16
use CGI::Cookie;
1✔
17

18
my @times = qw(day week month year);
19
# Current incarnation of $cgi.
20
# XXX - this is NOT thread-safe!
21
my $cgi;
22

23
sub handle_request
24
{
25
        $cgi = shift;
1✔
26
        my %cookies = CGI::Cookie->fetch;
1✔
27
        my $path = $cgi->path_info();
1✔
28

29
        # Handle static page now, since there is no need to do any SQL
30
        if ($path =~ m/static\/(.+)$/) {
1✔
31
                # Emit the static page
32
                my $page = $1;
×
33
                if ($page =~ m#/\.\./#) {
×
34
                        # "/../" indicates a traversal up the path. We have to prevent this,
35
                        # otherwise we may get tricked into delivering arbitrary files.
36
                        # Since there is no readily available function for determining the
37
                        # canonical path, we simply refuse malformed requests.
38
                        # Most webservers (used for proxying) should do canonicalization on their
39
                        # own, but we cannot rely on this.
40
                        # Static resource paths should never include parent references, anyway.
41
                        print "HTTP/1.0 404 Not found\r\n";
×
42
                        return;
×
43
                }
44
                my ($ext) = ($page =~ m/.*\.([^.]+)$/);
×
45
                my %mime_types = (
×
46
                        css => "text/css",
47
                        html => "text/html",
48
                        png => "image/png",
49
                        jpg => "image/jpeg",
50
                        jpeg => "image/jpeg",
51
                        js => "application/javascript",
52
                        svg => "image/svg+xml",
53
                        svgz => "image/svg+xml",
54
                        gif => "image/gif",
55
                );
56

57
                my $filename = get_param("staticdir"). "/$page";
×
58
                my $fh = new IO::File("$filename");
×
59

60
                if (! $fh) {
×
61
                        print "HTTP/1.0 404 Not found\r\n";
×
62
                        print $cgi->header(
63
                                -type => $mime_types{$ext},
×
64
                                -Cache_Control => "public, max-age=3600"
65
                        );
66
                        return;
×
67
                }
68

69
                print "HTTP/1.0 200 OK\r\n";
×
70
                print $cgi->header(
71
                        -type => $mime_types{$ext},
×
72
                        -Cache_Control => "public, max-age=3600"
73
                );
74
                while (my $line = <$fh>) { print $line; }
×
75
                return;
×
76
        }
77

78
        # Get graph extension (jpg / png / svg / pngx2 /...)
79
        # Get from cookie if it exists
80
        my $graph_ext = "png";
1✔
81
        if (defined $cgi->url_param("graph_ext")) {
1✔
82
                $graph_ext = $cgi->url_param("graph_ext");
×
83
        }
84
        elsif (exists $cookies{"graph_ext"}) {
85
                $graph_ext = $cookies{"graph_ext"}->value;
×
86
        }
87

88
        # Handle rest-like URL : .json & .xml
89
        my $output_format = "html";
1✔
90
        if ($path =~ /.(json|xml)$/) {
1✔
91
                $output_format = $1;
×
92
                # Replace that part with a ".html" in order to simplify the next handling
93
                $path =~ s/.(json|xml)$/.html/;
×
94
        }
95

96

97
        # Force either a trailing "/" or ".html" to enable simpler url handling: it is like in
98
        # a subdir from the browser pov
99
        if ($path eq "" || $path !~ /(\/|\.html)$/) {
1✔
100
                #if ($path eq "") {
101
                print "HTTP/1.0 301 Redirect Permanent\r\n";
1✔
102
                print $cgi->header(
1✔
103
                        -Location => ($cgi->url(-path_info=>1,-query=>1) . "/"),
104
                        -Cache_Control => "public, max-age=14400",  # Cache is valid of 1 day
105
                );
106
                return;
1✔
107
        }
108

109
        # Remove now the leading "/" as *every* path will have it
110
        $path =~ s,^/,,;
×
111

112
        # Remove now the ending "/" as *every* dir will have it
113
        $path =~ s,/$,,;
×
114

115
        # Ok, now SQL is needed to go further
116
        use Munin::Master::Update;
1✔
117
        my $dbh = Munin::Master::Update::get_dbh(1);
×
118

119
        my $comparison;
×
120
        my $template_filename;
121
        my %template_params = (
×
122
                MUNIN_VERSION   => $Munin::Common::Defaults::MUNIN_VERSION,
123
                TIMESTAMP       => strftime("%Y-%m-%d %T%z (%Z)", localtime),
124
                R_PATH          => '',
125
                GRAPH_EXT       => $graph_ext,
126
        );
127

128

129
        # Reduced navigation panel
130
        $template_params{NAV_PANEL_FOLD} = exists $cookies{"nav_panel_fold"}
131
                ? ($cookies{"nav_panel_fold"}->value eq "true" ? 1 : 0)
×
132
                : 0;
133
        $template_params{NAV_PANEL_FOLD_FORCED} = 0;
×
134

135
        # Common Navigation params
136
        ###################
137

138
        # Problems nav
139
        {
140
                my $sth = $dbh->prepare_cached("SELECT SUM(critical), SUM(warning), SUM(unknown) FROM ds");
×
141
                $sth->execute();
×
142
                my ($critical, $warning, $unknown) = $sth->fetchrow_array;
×
143
                $sth->finish();
×
144
                $template_params{NCRITICAL} = $critical;
×
145
                $template_params{NWARNING} = $warning;
×
146
                $template_params{NUNKNOWN} = $unknown;
×
147
        }
148

149
        # Groups nav
150
        {
151
                my $sth = $dbh->prepare_cached("SELECT g.name, u.path FROM grp g INNER JOIN url u ON u.id = g.id AND u.type = 'group' WHERE g.p_id = 0 ORDER BY g.name ASC");
×
152
                $sth->execute();
×
153

154
                my $rootgroups = [];
×
155
                while (my ($_name, $_path) = $sth->fetchrow_array) {
×
156
                        push @$rootgroups, { NAME => $_name, R_PATH => '', URL => $_path };
×
157
                }
158
                $template_params{ROOTGROUPS} = $rootgroups;
×
159
        }
160

161
        # Categories nav
162
        {
163
                my $sth = $dbh->prepare_cached("SELECT DISTINCT category FROM service_categories ORDER BY category ASC");
×
164
                $sth->execute();
×
165

166
                my $globalcats = [];
×
167
                while (my ($_category) = $sth->fetchrow_array) {
×
168
                        my %urls = map { ("URL$_" => "$_category-$_.html") } @times;
×
169
                        push @$globalcats, {
×
170
                                R_PATH => '',
171
                                NAME => $_category,
172
                                %urls,
173
                        };
174
                }
175
                $template_params{GLOBALCATS} = $globalcats;
×
176
        }
177

178
        # Handle all the special pages that are not in the url table, but with fixed urls
179
        if ($path eq "") {
×
180
                # Emit overview template
181
                $template_filename = 'munin-overview.tmpl';
×
182

183
                # Header params
184
                ###################
185
                {
186
                        $template_params{PATH} = [
187
                                { } , # XXX - Template says first args has to be empty
188
                                { "pathname" => "Overview", },
×
189
                        ];
190
                }
191

192
                # Main page
193
                {
194
                        # Constructing the recursive datastructure.
195
                        # Note that it is quite naive, and not optimized for speed.
196
                        my $sth_grp = $dbh->prepare_cached("SELECT g.id, g.name, u.path FROM grp g INNER JOIN url u ON u.id = g.id AND u.type = 'group' AND p_id = ? ORDER BY g.name ASC");
×
197
                        my $sth_grp_root = $dbh->prepare_cached("SELECT g.id, g.name, u.path FROM grp g INNER JOIN url u ON u.id = g.id AND u.type = 'group' AND p_id = 0 ORDER BY g.name ASC");
×
198
                        my $sth_node = $dbh->prepare_cached("SELECT n.id, n.name, u.path, n.path FROM node n INNER JOIN url u ON u.id = n.id AND u.type = 'node' AND n.grp_id = ? ORDER BY n.name ASC");
×
199

200
                        $template_params{GROUPS} = _get_params_groups($path, $dbh, $sth_grp, $sth_grp_root, $sth_node, undef, $graph_ext);
×
201
                        $template_params{NGROUPS} = scalar(@{$template_params{GROUPS}});
×
202
                }
203

204
                # TODO - We still have to write the bottom navigation links
205
        } elsif ($path eq "dynazoom.html") {
206
                # Emit dynamic zoom template
207

208
                $template_params{SHOW_ZOOM_JS} = 1;
×
209
                $template_params{PATH} = [
210
                        # first args should have path and r_path for backlink to overview
211
                        { "r_path" => url_absolutize(''), "path" => url_absolutize(''), },
×
212
                        { "pathname" => "Dynazoom", "path" => "#" }
213
                ];
214
                # Content only: when dynazoom page is shown in a modal,
215
                #   we hide the header & navigation
216
                $template_params{CONTENT_ONLY} = $cgi->url_param("content_only") || 0;
×
217

218
                $template_filename = "munin-dynazoom.tmpl";
×
219
        } elsif ($path eq "problems.html") {
220
                # Emit problem template
221

222
                $template_filename = "munin-problemview.tmpl";
×
223

224
                $template_params{PATH} = [
225
                        # first args should have path and r_path for backlink to overview
226
                        { "r_path" => url_absolutize(''), "path" => url_absolutize(''), },
×
227
                        { "pathname" => "Problems" }
228
                ];
229

230
                my $sth = $dbh->prepare_cached("SELECT nu.path, n.name, su.path, s.name, d.critical, d.warning, d.unknown FROM ds d
×
231
                                LEFT OUTER JOIN service s ON s.id = d.service_id
232
                                LEFT OUTER JOIN url su ON su.id = s.id and su.type = 'service'
233
                                LEFT OUTER JOIN node n ON n.id = s.node_id
234
                                LEFT OUTER JOIN url nu ON nu.id = n.id and nu.type = 'node'
235
                                WHERE d.critical = 1 OR d.warning = 1 OR d.unknown = 1
236
                        ");
237
                $sth->execute();
×
238

239
                my @criticals;
×
240
                my @warnings;
241
                my @unknowns;
×
242
                while (my ($_node_url, $_node_name, $_url, $_s_name, $_c, $_w, $_u) = $sth->fetchrow_array) {
×
243

244
                        my $img_day = $_url . "-day.png";
×
245
                        my $img_week = $_url . "-day.png";
×
246

247
                        my $item = {
×
248
                                NODEURL => $_node_url,
249
                                NODENAME => $_node_name,
250
                                URL => $_url,
251
                                URLX => $_url,
252
                                LABEL => $_s_name,
253

254
                                STATE_CRITICAL => $_c,
255
                                STATE_WARNING => $_w,
256
                                STATE_UNKNOWN => $_u,
257

258
                                CIMGDAY => $img_day,
259
                                CIMGWEEK => $img_week,
260
                        };
261

262
                        push @criticals, $item if $_c;
×
263
                        push @warnings, $item if $_w;
×
264
                        push @unknowns, $item if $_u;
×
265
                }
266

267
                # TODO - Create the model (problem)
268
                $template_params{CRITICAL} = \@criticals;
×
269
                $template_params{WARNING} = \@warnings;
×
270
                $template_params{UNKNOWN} = \@unknowns;
×
271

272
        } elsif ($path =~ /^([^\/]+)-(day|month|week|year)\.html$/) {
273
                # That's a category URL
274
                $template_filename = 'munin-categoryview.tmpl';
×
275

276
                my $category = $1;
×
277
                my $time = $2;
×
278

279
                $template_params{PATH} = [
280
                        # first args should have path and r_path for backlink to overview
281
                        { "r_path" => url_absolutize(''), "path" => url_absolutize(''), },
×
282
                        { "pathname" => "Category", },
283
                        { "pathname" => ucfirst($category), },
284
                ];
285

286
                $template_params{CATEGORY} = $category;
×
287
                $template_params{TIMERANGE} = $time;
×
288

289
                my $sth_cat;
×
290
                if ($category eq 'other') {
×
291
                        # account for those that explicitly mention 'other' as category
292
                        $sth_cat = $dbh->prepare_cached(
×
293
                                "SELECT DISTINCT s.name, s.service_title FROM service s
294
                                LEFT JOIN service_categories sc ON s.id = sc.id
295
                                WHERE (sc.category = 'other' OR sc.id IS NULL)
296
                                AND EXISTS (select sa.id from service_attr sa where sa.id = s.id)
297
                                ORDER BY s.name");
298
                        $sth_cat->execute();
×
299
                } else {
300
                        $sth_cat = $dbh->prepare_cached(
×
301
                                "SELECT DISTINCT s.name, s.service_title FROM service s
302
                                INNER JOIN service_categories sc ON s.id = sc.id
303
                                WHERE (sc.category = ?)
304
                                AND EXISTS (select sa.id from service_attr sa where sa.id = s.id)
305
                                ORDER BY s.name");
306
                        $sth_cat->execute($category);
×
307
                }
308

309
                my @services;
×
310
                while (my ($_service, $_service_title) = $sth_cat->fetchrow_array) {
×
311
                        push @services, _get_params_services_by_name($dbh, $_service, $_service_title, $time, $graph_ext);
×
312
                }
313
                $template_params{SERVICES} = \@services;
×
314

315
                # Force-reduce navigation panel
316
                $template_params{NAV_PANEL_FOLD} = 1;
×
317
                $template_params{NAV_PANEL_FOLD_FORCED} = 1;
×
318
        } elsif ($path =~ /^(.+)\/comparison-(day|month|week|year)\.html$/) {
319
                # That's a comparison URL, handle it as special case of groups
320
                $path = $1;
×
321
                $comparison = $2;
×
322
        }
323

324

325
        # Handle normal pages only if not already handled
326
        goto RENDERING if $template_filename;
×
327

328
        # Remove an eventual [/index].html
329
        $path =~ s/(\/index)?\.html$//;
×
330

331
        my ($id, $type);
×
332
        {
333
                my $sth_url = $dbh->prepare_cached("SELECT id, type FROM url WHERE path = ?");
×
334
                $sth_url->execute($path);
×
335
                ($id, $type) = $sth_url->fetchrow_array;
×
336
                $sth_url->finish();
×
337
        }
338

339
        if (! defined $id) {
×
340
                # Not found
341
                print "HTTP/1.0 404 Not found\r\n";
×
342
                goto CLEANUP;
×
343
        } elsif ($type eq "group") {
344
                # Shared code for group views and comparison views
345

346
                # Constructing the recursive datastructure.
347
                # Note that it is quite naive, and not optimized for speed.
348
                my $sth_grp = $dbh->prepare_cached("SELECT g.id, g.name, u.path FROM grp g INNER JOIN url u ON u.id = g.id AND u.type = 'group' AND p_id = ? ORDER BY g.name ASC");
×
349
                my $sth_grp_root = $dbh->prepare_cached("SELECT g.id, g.name, u.path FROM grp g INNER JOIN url u ON u.id = g.id AND u.type = 'group' AND p_id = 0 ORDER BY g.name ASC");
×
350
                my $sth_node = $dbh->prepare_cached("SELECT n.id, n.name, u.path, n.path FROM node n INNER JOIN url u ON u.id = n.id AND u.type = 'node' AND n.grp_id = ? ORDER BY n.name ASC");
×
351

352
                my $sth_p_id = $dbh->prepare_cached("SELECT g.p_id FROM grp g WHERE g.id = ?");
×
353
                $sth_p_id->execute($id);
×
354
                my ($_p_id) = $sth_p_id->fetchrow_array;
×
355
                $sth_p_id->finish();
×
356
                my $sth_peer;
×
357

358
                # Check for top level groups
359
                if (defined $_p_id) {
×
360
                        $sth_peer = $sth_grp;
×
361
                        $sth_peer->execute($id);
×
362
                } else {
363
                        $sth_peer = $sth_grp_root;
×
364
                        $sth_peer->execute();
×
365
                }
366

367
                # Construct list of peers
368
                my $peers = [];
×
369
                while (my (undef, $_name, $_url) = $sth_peer->fetchrow_array) {
×
370
                        $_url =~ s!/$!!;
×
371
                        push @$peers, { NAME => $_name, LINK => '../' . basename($_url) . '/' };
×
372
                }
373

374
                $template_params{PEERS} = $peers;
×
375
                $template_params{LARGESET} = 1;
×
376
                $template_params{INFO_OPTION} = 'Groups on this level';
×
377

378
                # Generate navigational links
379
                $template_params{PATH} = [
380
                        # first args should have path and r_path for backlink to overview
381
                        { "r_path" => url_absolutize(''), "path" => url_absolutize(''), },
×
382
                        url_to_path($path),
383
                ];
384

385
                # hack: use the last element of the path as the name
386
                $template_params{NAME} = $template_params{PATH}[-1]{'pathname'};
×
387

388
                if ($comparison) {
×
389
                        # Emit group comparison template
390
                        $template_filename = "munin-comparison.tmpl";
×
391

392
                        $template_params{TIMERANGE} = $comparison;
×
393

394
                        # Get all categories in this group
395
                        my $sth_cat = $dbh->prepare_cached(
×
396
                                "SELECT DISTINCT sa_c.category cat FROM node n
397
                                INNER JOIN service s ON s.node_id = n.id
398
                                INNER JOIN service_categories sa_c ON sa_c.id = s.id
399
                                WHERE n.grp_id = ? ORDER BY sa_c.category ASC");
400
                        $sth_cat->execute($id);
×
401

402
                        $template_params{CATEGORIES} = [];
×
403
                        while (my ($cat_name) = $sth_cat->fetchrow_array) {
×
404
                                push @{$template_params{CATEGORIES}}, _get_params_services_for_comparison($path, $dbh, $cat_name, $id, $graph_ext, $comparison);
×
405
                        }
406

407
                        # Force-reduce navigation panel
408
                        $template_params{NAV_PANEL_FOLD} = 1;
×
409
                        $template_params{NAV_PANEL_FOLD_FORCED} = 1;
×
410
                } else {
411
                        # Emit group template
412
                        $template_filename = 'munin-domainview.tmpl';
×
413

414
                        # Main page
415
                        $template_params{GROUPS} = _get_params_groups($path, $dbh, $sth_grp, $sth_grp_root, $sth_node, $id, $graph_ext);
×
416
                        $template_params{NGROUPS} = scalar(@{$template_params{GROUPS}});
×
417

418
                        # Shows "[ d w m y ]"
419
                        # comparison only makes sense if there are 2 or more nodes
420
                        $template_params{COMPARE} = 1 if
421
                                1 < scalar grep { defined($_->{'NCATEGORIES'}) && $_->{'NCATEGORIES'} } @{$template_params{GROUPS}};
×
422
                }
423

424
        } elsif ($type eq "node") {
425
                # Emit node template
426
                $template_filename = 'munin-nodeview.tmpl';
×
427

428
                # Construct list of peers
429
                my $sth_peer = $dbh->prepare_cached(
×
430
                        "SELECT n.name, u.path FROM node n
431
                        INNER JOIN url u ON n.id = u.id AND u.type = 'node'
432
                        WHERE n.grp_id = (SELECT n.grp_id FROM node n WHERE n.id = ?)
433
                        ORDER BY n.name ASC");
434
                $sth_peer->execute($id);
×
435

436
                my $peers = [];
×
437
                while (my ($_name, $_url) = $sth_peer->fetchrow_array) {
×
438
                        push @$peers, { NAME => $_name, LINK => '../' . basename($_url) . "/" };
×
439
                }
440

441
                $template_params{PEERS} = $peers;
×
442
                $template_params{LARGESET} = 1;
×
443
                $template_params{INFO_OPTION} = 'Nodes on this level';
×
444

445
                my $sth_category = $dbh->prepare_cached(
×
446
                        "SELECT DISTINCT sc.category as graph_category FROM service s
447
                        INNER JOIN service_categories sc ON sc.id = s.id
448
                        WHERE s.node_id = ?
449
                        ORDER BY graph_category");
450
                $sth_category->execute($id);
×
451

452
                my $categories = [];
×
453
                while (my ($_category_name) = $sth_category->fetchrow_array) {
×
454
                        push @$categories, _get_params_services($path, $dbh, $_category_name, undef, $id, $graph_ext);
×
455
                }
456

457
                $template_params{CATEGORIES} = $categories;
×
458
                $template_params{NCATEGORIES} = scalar(@$categories);
×
459

460
                $template_params{PATH} = [
461
                        # first args should have path and r_path for backlink to overview
462
                        { "r_path" => url_absolutize(''), "path" => url_absolutize(''), },
×
463
                        url_to_path($path),
464
                ];
465

466
                $template_params{NAME} = $template_params{PATH}[-1]{'pathname'};
×
467

468
        } elsif ($type eq "service") {
469
                # Emit service template
470
                $template_filename = 'munin-serviceview.tmpl';
×
471

472
                my $sth;
×
473

474
                $sth = $dbh->prepare_cached("SELECT name,service_title,graph_info,subgraphs,category,
×
475
                                                                        (SELECT MAX(warning) FROM ds WHERE service_id = service.id) as state_warning,
476
                                                                        (SELECT MAX(critical) FROM ds WHERE service_id = service.id) as state_critical
477
                                                                        FROM service
478
                                                                        LEFT JOIN service_categories ON service.id = service_categories.id
479
                                                                        WHERE service.id = ?");
480
                $sth->execute($id);
×
481
                my ($graph_name, $graph_title, $graph_info, $multigraph, $category, $state_warning, $state_critical) = $sth->fetchrow_array();
×
482
                $sth->finish();
×
483

484
                $sth = $dbh->prepare_cached("SELECT category FROM service_categories WHERE id = ?");
×
485
                $sth->execute($id);
×
486
                my ($graph_category) = $sth->fetchrow_array();
×
487
                $sth->finish();
×
488

489
                $sth = $dbh->prepare_cached("SELECT n.id FROM node n INNER JOIN service s ON s.node_id = n.id WHERE s.id = ?");
×
490
                $sth->execute($id);
×
491
                my ($node_id) = $sth->fetchrow_array();
×
492
                $sth->finish();
×
493

494
                # Generate peers
495
                my ($graph_parent) = ($graph_name =~ /^(.*)\./);
×
496
                $template_params{PEERS} = [ map {
497
                        (my $name = basename($_->{"URLX"}, ".html")) =~ tr/_/ /;
×
498
                        { NAME => $name, LINK => $multigraph ? ("../" . $_->{URLX}) : $_->{URLX}, }
499
                } @{_get_params_services(dirname($path), $dbh, $graph_category, $graph_parent, $node_id, $graph_ext)->{SERVICES}} ];
×
500
                $template_params{LARGESET} = 1;
×
501
                $template_params{INFO_OPTION} = 'Graphs in same category';
×
502

503
                $template_params{PATH} = [
504
                        # first args should have path and r_path for backlink to overview
505
                        { "r_path" => url_absolutize(''), "path" => url_absolutize(''), },
×
506
                        url_to_path($path),
507
                ];
508

509
                $template_params{CATEGORY} = ucfirst($graph_category);
×
510
                $template_params{NAME} = $template_params{PATH}[-1]{'pathname'};
×
511

512
                if ($multigraph) {
×
513
                        # Emit node template for multigraphs
514
                        $template_filename = 'munin-nodeview.tmpl';
×
515

516
                        my @categories = (_get_params_services($path, $dbh, $graph_category, $graph_name, $node_id, $graph_ext));
×
517
                        $template_params{CATEGORIES} = \@categories;
×
518
                        $template_params{NCATEGORIES} = scalar(@categories);
×
519

520
                        goto RENDERING;
×
521
                }
522

523
                # Create the params
524
                my %service_template_params;
×
525
                $service_template_params{FIELDINFO} = _get_params_fields($dbh, $id);
×
526
                my $cgi_graph_url = '/';
×
527
                my $epoch_now = time;
×
528
                my %epoch_start = (
×
529
                        day => $epoch_now - (3600 * 30),
530
                        week => $epoch_now - (3600 * 24 * 8),
531
                        month => $epoch_now - (3600 * 24 * 33),
532
                        year => $epoch_now - (3600 * 24 * 400),
533
                );
534

535
                # Add some more information (graph name, title, category, nodeview path)
536
                $service_template_params{GRAPH_NAME} = $graph_name;
×
537
                $service_template_params{GRAPH_TITLE} = $graph_title;
×
538
                $service_template_params{CATEGORY} = $category;
×
539
                $service_template_params{NODEVIEW_PATH} = "/" . dirname($path);
×
540

541
                # Problems
542
                $service_template_params{STATE_WARNING} = $state_warning;
×
543
                $service_template_params{STATE_CRITICAL} = $state_critical;
×
544

545
                for my $t (@times) {
×
546
                        my $epoch = "start_epoch=$epoch_start{$t}&stop_epoch=$epoch_now";
×
547
                        $service_template_params{"ZOOM$t"} = "/dynazoom.html?cgiurl_graph=$cgi_graph_url" .
×
548
                                "&plugin_name=$path&size_x=800&size_y=400&$epoch";
549
                        $service_template_params{"IMG$t"} = $cgi_graph_url . "$path-$t.$graph_ext";
×
550
                }
551

552
                # template uses loop for no apparent reason
553
                $service_template_params{GRAPHINFO} = [ { info => $graph_info } ];
×
554

555
                $template_params{SERVICES} = [ \%service_template_params,];
×
556
        }
557

558
RENDERING:
559
        if (! $template_filename ) {
×
560
                # Unknown
561
                print "HTTP/1.0 404 Not found\r\n";
×
562
                goto CLEANUP;
×
563
        }
564

565
        # We only cache agressively HTML pages, as they should not move
566
        # ... and a manual refresh is ok if needed
567
        if ($output_format eq "html") {
×
568
                print "HTTP/1.0 200 OK\r\n";
×
569
                print $cgi->header( "-Content-Type" => "text/html",
×
570
                        -Cache_Control => "public, max-age=3600", # 1h for HTML pages
571
                );
572
                my $template = HTML::Template::Pro->new(
×
573
                        filename => "$Munin::Common::Defaults::MUNIN_CONFDIR/templates/$template_filename",
574
                        loop_context_vars => 1,
575
                );
576

577
                my $is_dump_enabled = $cgi->url_param("dump");
×
578
                if ($is_dump_enabled) {
×
579
                        use Data::Dumper;
1✔
580
                        local $Data::Dumper::Terse = 1;
×
581
                        local $Data::Dumper::Sortkeys = 1;
×
582
                        local $Data::Dumper::Sparseseen = 1;
×
583
                        local $Data::Dumper::Deepcopy = 1;
×
584
                        local $Data::Dumper::Indent = 1;
×
585

586
                        $template_params{DEBUG} = Dumper(\%template_params);
×
587
                }
588

589
                $template->param(%template_params);
×
590

591
                # We cannot use "print_to => \*STDOUT" since it does *NOT* work with FastCGI
592
                print $template->output();
×
593
        } elsif ($output_format eq "xml") {
594
                print "HTTP/1.0 200 OK\r\n";
×
595
                print $cgi->header( "-Content-Type" => "text/xml", );
×
596

597
                use XML::Dumper;
1✔
598
                print pl2xml( \%template_params );
×
599
        } elsif ($output_format eq "json") {
600
                print "HTTP/1.0 200 OK\r\n";
×
601
                print $cgi->header( "-Content-Type" => "application/json", );
×
602

603
                use JSON;
1✔
604
                print encode_json( \%template_params );
×
605
        }
606

607
CLEANUP:
608
        $dbh = undef;
×
609
}
610

611
sub _get_params_groups {
612
        my ($path, $dbh, $sth_grp_normal, $sth_grp_root, $sth_node, $g_id, $graph_ext) = @_;
×
613

614
        my $sth_grp;
×
615
        if (defined $g_id) {
×
616
                $sth_grp = $sth_grp_normal;
×
617
                $sth_grp->execute($g_id);
×
618
        } else {
619
                $sth_grp = $sth_grp_root;
×
620
                $sth_grp->execute();
×
621
        }
622

623
        my $groups = [];
×
624

625
        # This function is recursive and reuses the prepared statements,
626
        # so we need to save the results first.
627
        my $_sth_grp_data = $sth_grp->fetchall_arrayref;
×
628
        foreach my $row (@$_sth_grp_data) {
×
629
                my ($_g_id, $_name, $_path) = @$row;
×
630
                my $_groups = _get_params_groups($path, $dbh, $sth_grp_normal, $sth_grp_root, $sth_node, $_g_id, $graph_ext);
×
631
                my $_compare_groups = scalar grep { defined($_->{'NCATEGORIES'}) && $_->{'NCATEGORIES'} } @$_groups;
×
632
                push @$groups, {
×
633
                        NAME => $_name,
634
                        URL => "$_path/",
635
                        GROUPS => $_groups,
636
                        NGROUPS => scalar(@$_groups),
637
                        # comparison only makes sense if there are 2 or more nodes
638
                        COMPARE => ($_compare_groups > 1 ? 1 : 0),
639
                        R_PATH => '',
640
                        PATH => [
641
                                { PATH => '..', PATHNAME => undef, },
642
                                url_to_path($_path),
643
                        ],
644
                };
645
        }
646

647
        # Add the nodes
648
        $sth_node->execute($g_id);
×
649
        while (my ($_n_id, $_name, $_path, $_node_path) = $sth_node->fetchrow_array) {
×
650
                my $sth = $dbh->prepare_cached("SELECT DISTINCT sc.category FROM service s INNER JOIN service_categories sc ON sc.id = s.id WHERE s.node_id = ? ORDER BY sc.category ASC");
×
651
                $sth->execute($_n_id);
×
652

653
                # trim off current path from target's path
654
                substr($_path, 0, 1 + length($path)) = '' if $path;
×
655

656
                my $categories = [];
×
657
                while (my ($_category_name) = $sth->fetchrow_array) {
×
658
                        my $category = _get_params_services($path, $dbh, $_category_name, undef, $_n_id, $graph_ext);
×
659
                        $category->{URLX} = "$_path/" . "#" . $_category_name;
×
660
                        $category->{URL} = $category->{URLX}; # For category in overview
×
661
                        push @$categories, $category;
×
662
                }
663

664
                # No Category found, use a dummy one.
665
                $categories = [ { }, ] unless scalar @$categories;
×
666

667
                push @$groups, {
×
668
                        CATEGORIES => $categories,
669
                        NCATEGORIES => (scalar @$categories), # This is a node.
670
                        NAME => $_name,
671
                        URL => "$_path/",
672
                        URLX => "$_path/",
673
                        GROUPS => [],
674
                };
675
        }
676

677
        return $groups;
×
678
}
679

680
sub _get_params_services_for_comparison {
681
        my ($basepath, $dbh, $category_name, $grp_id, $graph_ext, $comparison) = @_;
×
682

683
        # Get all possible services with the specified category under the specified group
684
        my $sth_srv = $dbh->prepare_cached(
×
685
                "SELECT DISTINCT s.name, s.service_title FROM service s
686
                INNER JOIN node n ON s.node_id = n.id
687
                INNER JOIN service_categories sa_c ON sa_c.id = s.id AND sa_c.category = ?
688
                WHERE n.grp_id = ? ORDER BY s.name ASC");
689

690
        # Get node and service pairs
691
        my $sth_node = $dbh->prepare_cached(
×
692
                "SELECT n.name, u.path, s.path, s.title FROM node n
693
                INNER JOIN url u ON u.id = n.id AND u.type = 'node'
694
                LEFT JOIN
695
                        ( SELECT s.id AS id, s.node_id AS node_id, s.service_title AS title, u_s.path AS path FROM service s
696
                        INNER JOIN url u_s ON s.id = u_s.id AND u_s.type = 'service'
697
                        WHERE s.name = ? ) AS s ON n.id = s.node_id
698
                WHERE n.grp_id = ?
699
                ORDER BY n.name, s.title ASC");
700

701
        my %category = (
×
702
                GROUPNAME => $category_name,
703
                SERVICES => [],
704
        );
705

706
        $sth_srv->execute($category_name, $grp_id);
×
707
        while (my ($service_name, $service_title) = $sth_srv->fetchrow_array) {
×
708
                # Skip multigraph sub-graphs
709
                next if $service_name =~ /\./;
×
710

711
                my @nodes;
×
712
                $sth_node->execute($service_name, $grp_id);
×
713
                while (my ($node_name, $node_url, $srv_url, $srv_label) = $sth_node->fetchrow_array) {
×
714
                        my ($_srv_url, $_img_url);
×
715
                        $_srv_url = "$srv_url.html" if defined $srv_url;
×
716
                        $_img_url = "/$srv_url-$comparison.$graph_ext" if defined $srv_url;
×
717
                        push @nodes, {
×
718
                                R_PATH => '',
719
                                NODENAME => $node_name,
720
                                URL1 => substr($node_url, length($basepath) + 1),
721
                                LABEL => $srv_label,
722
                                URL => $_srv_url,
723
                                CIMG => $_img_url,
724
                        };
725
                }
726

727
                push @{$category{SERVICES}}, { NODES => \@nodes, SERVICENAME => $service_name, SERVICETITLE => $service_title };
×
728
        }
729

730
        return \%category;
×
731
}
732

733
# This is only called for category views, which start with the root URL,
734
# so no need to handle basepath or multigraph parents for relative URLs
735
sub _get_params_services_by_name {
736
        my ($dbh, $service_name, $service_title, $time, $graph_ext) = @_;
×
737

738
        # TODO warning/critical state (use SUM sub-queries?)
739
        # XXX this may be slow
740
        my $sth = $dbh->prepare_cached(
×
741
                "SELECT s.id, s.service_title as service_title, s.subgraphs as subgraphs, u.path AS url,
742
                n.name AS node_name, u_n.path AS node_url
743
                FROM service s
744
                INNER JOIN url u ON u.id = s.id AND u.type = 'service'
745
                INNER JOIN node n ON n.id = s.node_id
746
                INNER JOIN url u_n ON u_n.id = s.node_id AND u_n.type = 'node'
747
                WHERE s.name = ?
748
                ORDER BY node_name ASC");
749
        $sth->execute($service_name);
×
750

751
        my $_url_var = "CIMG" . uc($time);
×
752
        my $_time_var = "TIME" . uc($time);
×
753
        my @graphs;
×
754
        while (my ($_s_id, $_service_title, $_subgraphs, $_url, $_node_name, $_node_url) = $sth->fetchrow_array) {
×
755
                push @graphs, {
×
756
                        HOST_URL => "$_node_url/",
757
                        NODENAME => $_node_name,
758
                        LABEL => $_service_title,
759
                        URLX => $_url . ($_subgraphs ? "/" : ".html"),
760
                        "CIMG$time" => "/$_url-$time.$graph_ext",
761
                        "TIME$time" => 1,
762
                };
763
        }
764

765
        return {
766
                NAME => $service_name,
×
767
                TITLE => $service_title,
768
                GRAPHS => \@graphs,
769
        };
770
}
771

772
sub _get_params_services {
773
        my ($base_path, $dbh, $category_name, $multigraph_parent, $node_id, $graph_ext) = @_;
×
774

775
        my $sth = $dbh->prepare_cached("SELECT s.id, s.name, s.service_title as service_title, s.subgraphs as subgraphs, u.path AS url,
×
776
                                                                        (SELECT MAX(warning) FROM ds WHERE service_id = s.id) as state_warning,
777
                                                                        (SELECT MAX(critical) FROM ds WHERE service_id = s.id) as state_critical
778
                FROM service s
779
                INNER JOIN service_categories sa_c ON sa_c.id = s.id AND sa_c.category = ?
780
                INNER JOIN url u ON u.id = s.id AND u.type = 'service'
781
                WHERE s.node_id = ?
782
                AND EXISTS (select sa.id from service_attr sa where sa.id = s.id)
783
                ORDER BY service_title ASC");
784
        $sth->execute($category_name, $node_id);
×
785

786
        my $services = [];
×
787

788
        # Group-level sums
789
        my $n_warnings = 0;
×
790
        my $n_criticals = 0;
×
791

792
        while (my ($_s_id, $_s_name, $_service_title, $_subgraphs, $_url, $_state_warning, $_state_critical) = $sth->fetchrow_array) {
×
793
                # Skip sub-graphs if not in multigraph
794
                next if not $multigraph_parent and $_s_name =~ /\./;
×
795
                # Skip unrelated graphs if in multigraph
796
                next if $multigraph_parent and $_s_name !~ /^$multigraph_parent\./;
×
797

798
                $n_warnings += $_state_warning || 0;
×
799
                $n_criticals += $_state_critical || 0;
×
800

801
                my %imgs = map { ("IMG$_" => "/$_url-$_.$graph_ext") } @times;
×
802
                push @$services, {
×
803
                        NAME => $_service_title,
804
                        URLX => substr($_url, 1 + length($base_path)) . ($_subgraphs ? "/" : ".html"),
805
                        STATE_WARNING => $_state_warning,
806
                        STATE_CRITICAL => $_state_critical,
807
                        %imgs
808
                };
809
        }
810

811
        return {
812
                NAME => $category_name,
×
813
                SERVICES => $services,
814
                STATE_WARNING => $n_warnings > 0,
815
                STATE_CRITICAL => $n_criticals > 0
816
        };
817
}
818

819
sub _get_params_fields {
820
        my ($dbh, $service_id) = @_;
×
821

822
        my $sth_ds = $dbh->prepare_cached("
×
823
                SELECT ds.name, ds.warning, ds.critical,
824
                a_g.value, a_l.value, a_t.value, a_w.value, a_c.value, a_i.value
825
                FROM ds
826
                LEFT JOIN ds_attr a_g ON ds.id = a_g.id AND a_g.name = 'graph'
827
                LEFT JOIN ds_attr a_l ON ds.id = a_l.id AND a_l.name = 'label'
828
                LEFT JOIN ds_attr a_t ON ds.id = a_t.id AND a_t.name = 'type'
829
                LEFT JOIN ds_attr a_w ON ds.id = a_w.id AND a_w.name = 'warning'
830
                LEFT JOIN ds_attr a_c ON ds.id = a_c.id AND a_c.name = 'critical'
831
                LEFT JOIN ds_attr a_i ON ds.id = a_i.id AND a_i.name = 'info'
832
                WHERE ds.service_id = ?
833
                ORDER BY ds.id ASC");
834
        $sth_ds->execute($service_id);
×
835

836
        my @fields;
×
837
        while (my ($_ds_name, $_ds_s_warn, $_ds_s_crit, $_ds_graph, $_ds_label, $_ds_type, $_ds_warn, $_ds_crit, $_ds_info) =
×
838
                        $sth_ds->fetchrow_array) {
839
                next if $_ds_graph && $_ds_graph eq 'no';
×
840

841
                # GAUGE by default
842
                $_ds_type = 'GAUGE' unless defined $_ds_type;
×
843

844
                push @fields, {
×
845
                        FIELD => $_ds_name,
846
                        STATE_WARNING => $_ds_s_warn,
847
                        STATE_CRITICAL => $_ds_s_crit,
848
                        LABEL => $_ds_label,
849
                        TYPE => lc($_ds_type),
850
                        WARN => $_ds_warn,
851
                        CRIT => $_ds_crit,
852
                        INFO => $_ds_info,
853
                };
854
        }
855

856
        return \@fields;
×
857
}
858

859
sub get_param
860
{
861
        my ($param) = @_;
×
862

863
        # Ok, now SQL is needed to go further
864
        use DBI;
1✔
865
        my $datafilename = $ENV{MUNIN_DBURL} || "$Munin::Common::Defaults::MUNIN_DBDIR/datafile.sqlite";
×
866
        my $dbh = Munin::Master::Update::get_dbh(1);
×
867
        my ($value) = $dbh->selectrow_array("SELECT value FROM param WHERE name = ?", undef, ($param));
×
868
        return $value;
×
869
}
870

871
sub url_to_path
872
{
873
        my ($url) = @_;
×
874

875
        my @paths = split m!/!, $url;
×
876

877
        @paths = map {
878
                (my $name = $paths[$_]) =~ tr/_/ /;
×
879
                {
880
                        'pathname' => $name,
×
881
                        'path' => url_absolutize(join '/', @paths[0..$_]) . '/',
882
                        'switchable' => 1
883
                }
884
        } 0..$#paths;
885

886
        delete $paths[-1]{'path'};
×
887

888
        return @paths;
×
889
}
890

891
sub url_absolutize
892
{
893
        my ($url, $omit_first_slash) = @_;
×
894
        my $url_a = '/' . $url;
×
895
        $url_a = substr($url_a, 1) if $omit_first_slash;
×
896
        return $url_a;
×
897
}
898

899
1;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc