limits.pm 68.9 KB
Newer Older
1
# OpenTrafficShaper webserver module: limits page
2
# Copyright (C) 2007-2015, AllWorldIT
3
#
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

package opentrafficshaper::plugins::webserver::pages::limits;

use strict;
use warnings;


# Exporter stuff
require Exporter;
our (@ISA,@EXPORT,@EXPORT_OK);
@ISA = qw(Exporter);
@EXPORT = qw(
);
@EXPORT_OK = qw(
);


use DateTime;
use HTML::Entities;
Nigel Kukard's avatar
Nigel Kukard committed
35
36
37
38
39
40
use HTTP::Status qw(
	:constants
);
use URI::Escape qw(
	uri_escape
);
41
use URI::QueryParam;
Nigel Kukard's avatar
Nigel Kukard committed
42
43
44
use Storable qw(
	dclone
);
45

Nigel Kukard's avatar
Nigel Kukard committed
46
use awitpt::util qw(
47
48
	parseURIQuery
	parseFormContent
49
	isUsername ISUSERNAME_ALLOW_ATSIGN
Nigel Kukard's avatar
Nigel Kukard committed
50
	isIPv4
51
52
53
	isNumber
	prettyUndef
);
Nigel Kukard's avatar
Nigel Kukard committed
54
55
56
use opentrafficshaper::constants;
use opentrafficshaper::logger;
use opentrafficshaper::plugins;
Nigel Kukard's avatar
Nigel Kukard committed
57
use opentrafficshaper::plugins::configmanager qw(
58
59
	getPools
	getPool
60
	getPoolByName
61
	getPoolShaperState
62
	isPoolOverridden
63
64
65
66
	isPoolReady

	getPoolMembers
	getPoolMember
67
	getAllPoolMembersByInterfaceGroupIP
68
69
	getPoolMemberShaperState
	isPoolMemberReady
Nigel Kukard's avatar
Nigel Kukard committed
70

71
72
	getPoolOverrides
	getPoolOverride
Nigel Kukard's avatar
Nigel Kukard committed
73

74
	getInterfaceGroup
75
76
	getInterfaceGroups
	isInterfaceGroupIDValid
Nigel Kukard's avatar
Nigel Kukard committed
77

78
79
80
	getMatchPriorities
	isMatchPriorityIDValid

81
82
	getTrafficClass
	getTrafficClasses
83
	isTrafficClassIDValid
Nigel Kukard's avatar
Nigel Kukard committed
84
);
85
86
87
88



# Sidebar menu options for this module
89
90
my $menu = [
	{
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
		'name' => 'Pools',
		'items' => [
			{
				'name' => 'List Pools',
				'link' => 'pool-list'
			},
			{
				'name' => 'List Manual Pools',
				'link' => 'pool-list?source=plugin.webserver.limits'
			},
			{
				'name' => 'Add Pool',
				'link' => 'pool-add'
			}
		]
	},
	{
		'name' => 'Pool Overrides',
109
110
		'items' => [
			{
111
				'name' => 'List Overrides',
112
				'link' => 'pool-override-list'
113
114
			},
			{
115
				'name' => 'Add Override',
116
				'link' => 'pool-override-add'
117
118
			}
		]
119
	},
120
121
122
123
124
125
126
127
128
129
	{
		'name' => 'Admin',
		'items' => [
			{
				'name' => 'Add Limit',
				'link' => 'limit-add'
			}
		]
	}
];
130
131
132



133
134
# Pool list page/action
sub pool_list
135
136
137
138
{
	my ($kernel,$globals,$client_session_id,$request) = @_;


139
	my @pools = getPools();
140
141
142

	# Pull in URL params
	my $queryParams = parseURIQuery($request);
143
144
145
146
147
148

	# Build content
	my $content = "";

	# Header
	$content .=<<EOF;
149
		<legend>Pool List</legend>
150
151
152
153
		<table class="table">
			<thead>
				<tr>
					<th></th>
154
155
156
					<th>Friendly Name</th>
					<th>Name</th>
					<th>Expires</th>
157
					<th>Members</th>
158
					<th></th>
159
160
161
					<th>Class</th>
					<th>CIR (Kbps)</th>
					<th>Limit (Kbps)</th>
162
					<th></th>
163
				</tr>
164
165
			</thead>
			<tbody>
166
167
EOF
	# Body
168
169
170
171
	foreach my $pid (@pools) {
		my $pool;
		# If we can't get the pool just move onto the next
		if (!defined($pool = getPool($pid))) {
172
173
			next;
		}
174

175
176
		# Conditionals
		if (defined($queryParams->{'source'})) {
177
			if ($pool->{'Source'} ne $queryParams->{'source'}->{'value'}) {
178
179
180
181
				next;
			}
		}

182
		# Get a nice last update string
183
184
		my $lastUpdate = DateTime->from_epoch( epoch => $pool->{'LastUpdate'} )->iso8601();

185
186
187
188
189
		my $poolCIRStr = encode_entities(sprintf('%s/%s',prettyUndef($pool->{'TxCIR'}),prettyUndef($pool->{'RxCIR'})));
		my $poolLimitStr = encode_entities(sprintf('%s/%s',prettyUndef($pool->{'TxLimit'}),prettyUndef($pool->{'RxLimit'})));
		my $poolFriendlyName = (defined($pool->{'FriendlyName'}) && $pool->{'FriendlyName'} ne "") ? $pool->{'FriendlyName'} :
				$pool->{'Name'};
		my $poolFriendlyNameEncoded = encode_entities($poolFriendlyName);
190

191
		my $poolNameEncoded = encode_entities($pool->{'Name'});
192

193
194
195
		my $poolExpiresStr = encode_entities(
				($pool->{'Expires'} > 0) ? DateTime->from_epoch( epoch => $pool->{'Expires'} )->iso8601() : '-never-'
		);
196

197
		my $poolMemberCount = getPoolMembers($pool->{'ID'});
198

199

200
201
		my $trafficClass = getTrafficClass($pool->{'TrafficClassID'});
		my $trafficClassNameEncoded = encode_entities($trafficClass->{'Name'});
202
203
204

		# Display relevant icons depending on pool status
		my $icons = "";
205
		if (getPoolShaperState($pool->{'ID'}) & SHAPER_NOTLIVE || $pool->{'Status'} == CFGM_CHANGED) {
206
207
208
209
210
211
212
			$icons .= '<span class="glyphicon glyphicon-time" />';
		}
		if ($pool->{'Status'} == CFGM_NEW) {
			$icons .= '<span class="glyphicon glyphicon-import" />';
		}
		if ($pool->{'Status'} == CFGM_OFFLINE) {
			$icons .= '<span class="glyphicon glyphicon-trash" />';
213
		}
214
215
216
#		if ($pool->{'Status'} eq 'conflict') {
#			$icons .= '<span class="glyphicon glyphicon-random" />';
#		}
217
218
219
		if (isPoolOverridden($pool->{'ID'})) {
			$icons .= '<span class="glyphicon glyphicon-edit" />';
		}
220

221
222
223
224
		my $urlStatsPool = sprintf('/statistics/by-pool?pool=%s',uri_escape("$pool->{'InterfaceGroupID'}:$pool->{'Name'}"));
		my $urlPoolEdit = sprintf('/limits/pool-edit?pid=%s',uri_escape($pool->{'ID'}));
		my $urlPoolMemberList = sprintf('/limits/poolmember-list?pid=%s',uri_escape($pool->{'ID'}));
		my $urlPoolRemove = sprintf('/limits/pool-remove?pid=%s',uri_escape($pool->{'ID'}));
225

226
		$content .= <<EOF;
227
228
				<tr>
					<td>$icons</td>
229
230
231
					<td>$poolFriendlyNameEncoded</td>
					<td>$poolNameEncoded</td>
					<td>$poolExpiresStr</td>
232
					<td class="align-right">$poolMemberCount</td>
233
					<td><span class="glyphicon glyphicon-arrow-right" /></td>
234
235
236
					<td class="align-center">$trafficClassNameEncoded</td>
					<td class="align-center">$poolCIRStr</td>
					<td class="align-center">$poolLimitStr</td>
237
					<td>
238
239
240
241
						<a href="$urlStatsPool"><span class="glyphicon glyphicon-stats"></span></a>
						<a href="$urlPoolEdit"><span class="glyphicon glyphicon-wrench"></span></a>
						<a href="$urlPoolMemberList"><span class="glyphicon glyphicon-link"></span></a>
						<a href="$urlPoolRemove"><span class="glyphicon glyphicon-remove"></span></a>
242
243
					</td>
				</tr>
244
245
EOF
	}
246

247
	# No results
248
	if (!@pools) {
249
		$content .=<<EOF;
250
				<tr class="info">
Nigel Kukard's avatar
Nigel Kukard committed
251
					<td colspan="10"><p class="text-center">No Results</p></td>
252
				</tr>
253
254
255
256
257
EOF
	}

	# Footer
	$content .=<<EOF;
258
259
260
261
262
263
264
			</tbody>
		</table>
		<span class="glyphicon glyphicon-time" /> - Processing,
		<span class="glyphicon glyphicon-edit" /> - Override,
		<span class="glyphicon glyphicon-import" /> - Being Added,
		<span class="glyphicon glyphicon-trash" /> - Being Removed,
		<span class="glyphicon glyphicon-random" /> - Conflicts
265
266
EOF

267
	return (HTTP_OK,$content,{ 'menu' => $menu });
268
269
270
}


Nigel Kukard's avatar
Nigel Kukard committed
271

272
273
# Pool add/edit action
sub pool_addedit
274
275
276
277
278
279
280
{
	my ($kernel,$globals,$client_session_id,$request) = @_;


	# Setup our environment
	my $logger = $globals->{'logger'};

281
	# Errors to display above the form
282
	my @errors;
283
284
285
286

	# Items for our form...
	my @formElements = qw(
		FriendlyName
287
		Name
Nigel Kukard's avatar
Nigel Kukard committed
288
		InterfaceGroupID
289
290
291
		TrafficClassID
		TxCIR TxLimit
		RxCIR RxLimit
292
293
294
295
		Expires inputExpires.modifier
		Notes
	);

Nigel Kukard's avatar
Nigel Kukard committed
296
297
298
299
300
301
302
303
304
	# Expires modifier options
	my $expiresModifiers = {
		'm' => "Minutes",
		'h' => "Hours",
		'd' => "Days",
		'n' => "Never",
	};


305
306
307
308
309
	# Title of the form, by default its an add form
	my $formType = "Add";
	my $formNoEdit = "";
	# Form data
	my $formData;
310
311
312
313
314
315
316
317
318
319
	# If we have a pool, this is where its kept
	my $pool;

	# Get query params
	my $queryParams = parseURIQuery($request);

	# If we have a pool ID, pull in the pool
	if (defined($queryParams->{'pid'})) {
		# Check if we can grab the pool
		if (!defined($pool = getPool($queryParams->{'pid'}->{'value'}))) {
320
			return (HTTP_TEMPORARY_REDIRECT,"/limits");
321
322
		}
	}
323
324
325
326

	# If this is a form try parse it
	if ($request->method eq "POST") {
		# Parse form data
327
		my $form = parseFormContent($request->content);
328
329

		# If user pressed cancel, redirect
330
		if (defined($form->{'cancel'})) {
331
			# Redirects to default page
332
			return (HTTP_TEMPORARY_REDIRECT,'/limits');
333
334
		}

335
336
337
338
		# Transform form into form data
		foreach my $key (keys %{$form}) {
			$formData->{$key} = $form->{$key}->{'value'};
		}
339

340
341
342
343
		# Set form type if its edit
		if (defined($form->{'submit'}) && $form->{'submit'}->{'value'} eq "Edit") {
			# Check pool exists
			if (!defined($pool)) {
344
				return (HTTP_TEMPORARY_REDIRECT,'/limits');
345
			}
346
347
348

			$formData->{'ID'} = $pool->{'ID'};

349
350
			$formType = "Edit";
			$formNoEdit = "readonly";
351
352
353
354
355
356
357
358
359
		}

	# Maybe we were given a pool key as a parameter? this would be an edit form
	} elsif ($request->method eq "GET") {
		if (defined($pool)) {
			# Setup form data from pool
			foreach my $key (@formElements) {
				$formData->{$key} = $pool->{$key};
			}
360
361
362

			$formType = "Edit";
			$formNoEdit = "readonly";
363
364
365

		# Woops ... no query string?
		} elsif (keys %{$queryParams} > 0) {
366
			return (HTTP_TEMPORARY_REDIRECT,'/limits');
367
368
369
370
371
		}
	}

	# We only do this if we have hash elements
	if (ref($formData) eq "HASH") {
372
		# Grab friendly name
373
		my $friendlyName = $formData->{'FriendlyName'};
374

375
		# Check POST data
376
		my $name;
377
		if (!defined($name = isUsername($formData->{'Name'},ISUSERNAME_ALLOW_ATSIGN))) {
378
			push(@errors,"Name is not valid");
379
		}
Nigel Kukard's avatar
Nigel Kukard committed
380
		my $interfaceGroupID;
381
		if (!defined($interfaceGroupID = isInterfaceGroupIDValid($formData->{'InterfaceGroupID'}))) {
Nigel Kukard's avatar
Nigel Kukard committed
382
383
			push(@errors,"Interface group is not valid");
		}
384
385
		if ($formType ne "Edit" && getPoolByName($interfaceGroupID,$name)) {
			push(@errors,"A pool with the same name already exists");
Nigel Kukard's avatar
Nigel Kukard committed
386
		}
387
388
		my $trafficClassID;
		if (!defined($trafficClassID = isTrafficClassIDValid($formData->{'TrafficClassID'}))) {
389
390
			push(@errors,"Traffic class is not valid");
		}
391
392
393
		my $txCIR = isNumber($formData->{'TxCIR'});
		my $txLimit = isNumber($formData->{'TxLimit'});
		if (!defined($txCIR) && !defined($txLimit)) {
394
			push(@errors,"A valid download CIR and/or limit is required");
395
		}
396
397
398
		my $rxCIR = isNumber($formData->{'RxCIR'});
		my $rxLimit = isNumber($formData->{'RxLimit'});
		if (!defined($rxCIR) && !defined($rxLimit)) {
399
400
401
			push(@errors,"A valid upload CIR and/or limit is required");
		}

402
403
404
405
406
407
408
		# Make sure pool is not transitioning states
		if ($formType eq "Edit") {
			if (!isPoolReady($pool->{'ID'})) {
				push(@errors,"Pool is not currently in a READY state, please try again");
			}
		}

409
		my $expires = 0;
410
411
		if (defined($formData->{'Expires'}) && $formData->{'Expires'} ne "") {
			if (!defined($expires = isNumber($formData->{'Expires'}))) {
412
413
414
415
				push(@errors,"Expires value is not valid");
			# Check the modifier
			} else {
				# Check if its defined
416
				if (defined($formData->{'inputExpires.modifier'}) && $formData->{'inputExpires.modifier'} ne "") {
Nigel Kukard's avatar
Nigel Kukard committed
417
418
419
420
					# Never
					if ($formData->{'inputExpires.modifier'} eq "n") {
						$expires = 0;
					} elsif ($formData->{'inputExpires.modifier'} eq "m") {
421
422
						$expires *= 60;
					# Hours
423
					} elsif ($formData->{'inputExpires.modifier'} eq "h") {
424
425
						$expires *= 3600;
					# Days
Nigel Kukard's avatar
Nigel Kukard committed
426
					} elsif ($formData->{'inputExpires.modifier'} eq "d") {
427
428
429
430
431
						$expires *= 86400;
					} else {
						push(@errors,"Expires modifier is not valid");
					}
				}
432
433
434
435
				# Base the expiry off now, plus the expiry time
				if ($expires > 0) {
					$expires += time();
				}
436
			}
437
		}
438

439
		# Grab notes
440
		my $notes = $formData->{'Notes'};
441
442

		# If there are no errors we need to push this update
443
		if (!@errors && $request->method eq "POST") {
444
445
			# Build pool details
			my $poolData = {
446
				'FriendlyName' => $friendlyName,
447
				'Name' => $name,
Nigel Kukard's avatar
Nigel Kukard committed
448
				'InterfaceGroupID' => $interfaceGroupID,
449
450
451
452
453
				'TrafficClassID' => $trafficClassID,
				'TxCIR' => $txCIR,
				'TxLimit' => $txLimit,
				'RxCIR' => $rxCIR,
				'RxLimit' => $rxLimit,
454
455
				'Expires' => $expires,
				'Notes' => $notes,
456
457
			};

458
			my $cEvent;
459
			if ($formType eq "Add") {
460
461
462
463
464
465
				$poolData->{'Status'} = CFGM_ONLINE;
				$poolData->{'Source'} = 'plugin.webserver.limits';
				$cEvent = "pool_add";
			} else {
				$poolData->{'ID'} = $formData->{'ID'};
				$cEvent = "pool_change";
466
467
			}

468
			$kernel->post("configmanager" => $cEvent => $poolData);
469

470
			$logger->log(LOG_INFO,"[WEBSERVER/LIMITS] Pool: %s, Name: %s, InterfaceGroup: %s, Class: %s, Limits: %s/%s, ".
471
					"Burst: %s/%s",
472
					$formType,
473
					prettyUndef($name),
Nigel Kukard's avatar
Nigel Kukard committed
474
					prettyUndef($interfaceGroupID),
475
476
477
478
479
					prettyUndef($trafficClassID),
					prettyUndef($txCIR),
					prettyUndef($rxCIR),
					prettyUndef($txLimit),
					prettyUndef($rxLimit)
480
			);
481

482
			return (HTTP_TEMPORARY_REDIRECT,'/limits');
483
484
485
486
		}
	}

	# Sanitize params if we need to
487
488
	foreach my $item (@formElements) {
		$formData->{$item} = defined($formData->{$item}) ? encode_entities($formData->{$item}) : "";
489
490
491
492
493
494
495
	}

	# Build content
	my $content = "";

	# Form header
	$content .=<<EOF;
496
497
		<legend>$formType Pool</legend>
		<form role="form" method="post">
498
499
500
501
502
EOF

	# Spit out errors if we have any
	if (@errors > 0) {
		foreach my $error (@errors) {
503
			$content .= '<div class="alert alert-danger">'.encode_entities($error).'</div>';
504
505
506
		}
	}

Nigel Kukard's avatar
Nigel Kukard committed
507
	# Generate interface group list
508
	my @interfaceGroups = sort(getInterfaceGroups());
Nigel Kukard's avatar
Nigel Kukard committed
509
	my $interfaceGroupStr = "";
510
511
512
513
514
	foreach my $interfaceGroupID (@interfaceGroups) {
		my $interfaceGroup = getInterfaceGroup($interfaceGroupID);
		my $interfaceGroupIDEncoded = encode_entities($interfaceGroupID);
		my $interfaceGroupNameEncoded = encode_entities($interfaceGroup->{'Name'});

515
		# Check if this item is selected
Nigel Kukard's avatar
Nigel Kukard committed
516
517
518
519
		my $selected = "";
		if ($formData->{'InterfaceGroupID'} ne "" && $formData->{'InterfaceGroupID'} eq $interfaceGroupID) {
			$selected = "selected";
		}
520

Nigel Kukard's avatar
Nigel Kukard committed
521
		# And build the options
522
523
		$interfaceGroupStr .= '<option value="'.$interfaceGroupIDEncoded.'" '.$selected.'>'.
				$interfaceGroupNameEncoded.'</option>';
Nigel Kukard's avatar
Nigel Kukard committed
524
525
	}

526
	# Generate traffic class list
527
	my @trafficClasses = sort(getTrafficClasses());
528
	my $trafficClassStr = "";
529
530
531
532
	foreach my $trafficClassID (@trafficClasses) {
		my $trafficClass = getTrafficClass($trafficClassID);
		my $trafficClassIDEncoded = encode_entities($trafficClassID);
		my $trafficClassNameEncoded = encode_entities($trafficClass->{'Name'});
533
534
		# Process selections nicely
		my $selected = "";
535
		if ($formData->{'TrafficClassID'} ne "" && $formData->{'TrafficClassID'} eq $trafficClassID) {
536
537
538
			$selected = "selected";
		}
		# And build the options
539
		$trafficClassStr .= '<option value="'.$trafficClassIDEncoded.'" '.$selected.'>'.$trafficClassNameEncoded.'</option>';
540
541
	}

Nigel Kukard's avatar
Nigel Kukard committed
542
543
544
545
546
547
548
549
550
551
552
553
554
	# Generate expires modifiers list
	my $expiresModifierStr = "";
	foreach my $expireModifier (sort keys %{$expiresModifiers}) {
		# Process selections nicely
		my $selected = "";
		if ($formData->{'inputExpires.modifier'} ne "" && $formData->{'inputExpires.modifier'} eq $expireModifier) {
			$selected = "selected";
		}
		# Default to n if nothing is specified
		if ($formData->{'inputExpires.modifier'} eq "" && $expireModifier eq "n") {
			$selected = "selected";
		}
		# And build the options
555
556
		$expiresModifierStr .= '<option value="'.$expireModifier.'" '.$selected.'>'.
				encode_entities($expiresModifiers->{$expireModifier}).'</option>';
Nigel Kukard's avatar
Nigel Kukard committed
557
558
	}

559
560
561
	# Blank expires if its 0
	if (defined($formData->{'Expires'}) && $formData->{'Expires'} eq "0") {
		$formData->{'Expires'} = "";
562
563
564
	}

	# Page content
565
	$content .=<<EOF;
566
567
568
569
570
571
572
573
			<div class="form-group">
				<label for="FriendlyName" class="col-md-2 control-label">Friendly Name</label>
				<div class="row">
					<div class="col-md-4 input-group">
						<input name="FriendlyName" type="text" placeholder="Opt. Friendly Name" class="form-control"
								value="$formData->{'FriendlyName'}" />
					</div>
				</div>
574
			</div>
575
			<div class="form-group">
576
				<label for="Name" class="col-md-2 control-label">Name</label>
577
578
				<div class="row">
					<div class="col-md-4 input-group">
579
580
						<input name="Name" type="text" placeholder="Name" class="form-control"
								value="$formData->{'Name'}" $formNoEdit />
581
582
583
						<span class="input-group-addon">*</span>
					</div>
				</div>
584
			</div>
585
586
587
588
589
590
591
592
593
			<div class="form-group">
				<label for="InterfaceGroupID" class="col-md-2 control-label">Interface Group</label>
				<div class="row">
					<div class="col-md-2">
						<select name="InterfaceGroupID" class="form-control" $formNoEdit>
							$interfaceGroupStr
						</select>
					</div>
				</div>
594
			</div>
595
			<div class="form-group">
596
				<label for="TrafficClassID" class="col-md-2 control-label">Traffic Class</label>
597
598
				<div class="row">
					<div class="col-md-2">
599
						<select name="TrafficClassID" class="form-control">
600
601
602
603
							$trafficClassStr
						</select>
					</div>
				</div>
Nigel Kukard's avatar
Nigel Kukard committed
604
			</div>
605
			<div class="form-group">
606
				<label for="TxCIR" class="col-md-2 control-label">Download CIR</label>
607
608
609
				<div class="row">
					<div class="col-md-3">
						<div class="input-group">
610
611
							<input name="TxCIR" type="text" placeholder="Download CIR" class="form-control"
									value="$formData->{'TxCIR'}" />
612
613
614
615
							<span class="input-group-addon">Kbps *<span>
						</div>
					</div>
				</div>
616
			</div>
617
			<div class="form-group">
618
				<label for="TxLimit" class="col-md-2 control-label">Download Limit</label>
619
620
621
				<div class="row">
					<div class="col-md-3">
						<div class="input-group">
622
623
							<input name="TxLimit" type="text" placeholder="Download Limit" class="form-control"
									value="$formData->{'TxLimit'}" />
624
625
626
627
							<span class="input-group-addon">Kbps<span>
						</div>
					</div>
				</div>
628
			</div>
629
			<div class="form-group">
630
				<label for="RxCIR" class="col-md-2 control-label">Upload CIR</label>
631
632
633
				<div class="row">
					<div class="col-md-3">
						<div class="input-group">
634
635
							<input name="RxCIR" type="text" placeholder="Upload CIR" class="form-control"
									value="$formData->{'RxCIR'}" />
636
637
638
							<span class="input-group-addon">Kbps *<span>
						</div>
					</div>
639
640
				</div>
			</div>
641
			<div class="form-group">
642
				<label for="RxLimit" class="col-md-2 control-label">Upload Limit</label>
643
644
645
				<div class="row">
					<div class="col-md-3">
						<div class="input-group">
646
647
							<input name="RxLimit" type="text" placeholder="Upload Limit" class="form-control"
									value="$formData->{'RxLimit'}" />
648
649
650
							<span class="input-group-addon">Kbps<span>
						</div>
					</div>
651
652
				</div>
			</div>
653
654
655
656
657
658
659
660
661
662
663
664
			<div class="form-group">
				<label for="Expires" class="col-md-2 control-label">Expires</label>
				<div class="row">
					<div class="col-md-2">
						<input name="Expires" type="text" placeholder="Optional" class="form-control"
								value="$formData->{'Expires'}" />
					</div>
					<div class="col-md-2">
						<select name="inputExpires.modifier" class="form-control" value="$formData->{'inputExpires.modifier'}">
							$expiresModifierStr
						</select>
					</div>
665
666
				</div>
			</div>
667
668
669
670
671
672
673
			<div class="form-group">
				<label for="Notes" class="col-md-2 control-label">Notes</label>
				<div class="row">
					<div class="col-md-4">
						<textarea name="Notes" placeholder="Opt. Notes" rows="3"
								class="form-control">$formData->{'Notes'}</textarea>
					</div>
674
675
				</div>
			</div>
676
677
678
			<div class="form-group">
				<button name="submit" type="submit" value="$formType" class="btn btn-primary">$formType</button>
				<button name="cancel" type="submit" class="btn">Cancel</button>
679
			</div>
680
		</form>
681
682
683
684
685
686
EOF

	return (HTTP_OK,$content,{ 'menu' => $menu });
}


Nigel Kukard's avatar
Nigel Kukard committed
687

688
689
# Pool remove action
sub pool_remove
690
691
692
693
694
695
696
697
{
	my ($kernel,$globals,$client_session_id,$request) = @_;


	# Content to return
	my $content = "";


698
	# Pull in query data
699
700
	my $queryParams = parseURIQuery($request);
	# We need a key first of all...
701
	if (!defined($queryParams->{'pid'})) {
702
703
		$content = <<EOF;
			<div class="alert alert-danger text-center">
704
				No pool ID in query string!
705
706
707
708
709
			</div>
EOF
		goto END;
	}

710
711
	# Grab the pool
	my $pool = getPool($queryParams->{'pid'}->{'value'});
712

713
714
	# Make sure the pool ID is valid... we would have a pool now if it was
	if (!defined($pool)) {
715
716
		$content = <<EOF;
			<div class="alert alert-danger text-center">
717
				Invalid pool ID!
718
719
720
721
722
			</div>
EOF
		goto END;
	}

723
724
	# Make sure its a manual pool we're removing
	if ($pool->{'Source'} ne "plugin.webserver.limits") {
725
726
		$content = <<EOF;
			<div class="alert alert-danger text-center">
727
				Only manual pools can be removed!
728
729
730
731
732
733
			</div>
EOF
		goto END;
	}

	# Pull in POST
734
735
	my $form = parseFormContent($request->content);

736
	# If this is a post, then its probably a confirmation
737
	if (defined($form->{'confirm'})) {
738
		# Check if its a success
739
		if ($form->{'confirm'}->{'value'} eq "Yes") {
740
			# Post the removal
741
			$kernel->post("configmanager" => "pool_remove" => $pool->{'ID'});
742
		}
743
		return (HTTP_TEMPORARY_REDIRECT,'/limits');
744
745
746
	}

	# Make the friendly name HTML safe
747
	my $encodedPoolName = encode_entities($pool->{'Name'});
748
749
750
751

	# Build our confirmation dialog
	$content .= <<EOF;
		<div class="alert alert-danger">
752
			Are you very sure you wish to remove pool for "$encodedPoolName"?
753
754
755
756
757
758
759
760
761
762
763
		</div>
		<form role="form" method="post">
			<input type="submit" class="btn btn-primary" name="confirm" value="Yes" />
			<input type="submit" class="btn btn-default" name="confirm" value="No" />
		</form>
EOF
	# And here is where we return
END:
	return (HTTP_OK,$content,{ 'menu' => $menu });
}

764

Nigel Kukard's avatar
Nigel Kukard committed
765

766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
# Pool member list page/action
sub poolmember_list
{
	my ($kernel,$globals,$client_session_id,$request) = @_;


	# Build content
	my $content = "";
	# Build custom menu
	my $customMenu = $menu;


	# Pull in query params
	my $queryParams = parseURIQuery($request);
	# We need a key first of all...
	if (!defined($queryParams->{'pid'})) {
		$content = <<EOF;
			<div class="alert alert-danger text-center">
				No pool ID in query string!
			</div>
EOF
		goto END;
	}

	# Grab the pool
	my $pool = getPool($queryParams->{'pid'}->{'value'});

	# If pool is not defined, it means we got an invalid pool ID
	if (!defined($pool)) {
		$content = <<EOF;
			<div class="alert alert-danger text-center">
				Invalid pool ID!
			</div>
EOF
		goto END;
	}

	# Grab pools members
	my @poolMembers = getPoolMembers($pool->{'ID'});

	my $poolFriendlyName = (defined($pool->{'FriendlyName'}) && $pool->{'FriendlyName'} ne "") ? $pool->{'FriendlyName'} :
807
			$pool->{'Name'};
808
	my $poolFriendlyNameEncoded = encode_entities($poolFriendlyName);
809
	my $poolNameEncoded = encode_entities($pool->{'Name'});
810

811
	my $urlPoolMemberAdd = sprintf('poolmember-add?pid=%s',uri_escape($pool->{'ID'}));
812

813
814
815
816
817
818
819
	# Menu
	$customMenu = [
		{
			'name' => 'Pool Members',
			'items' => [
				{
					'name' => 'Add Pool Member',
820
					'link' => $urlPoolMemberAdd
821
822
823
824
825
826
827
828
829
				},
			],
		},
		@{$menu}
	];
	# Header
	$content .=<<EOF;
		<legend>
			<a href="pool-list"><span class="glyphicon glyphicon-circle-arrow-left"></span></a>
830
			Pool Member List: '$poolFriendlyNameEncoded' [$poolNameEncoded]
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
		</legend>
		<table class="table">
			<thead>
				<tr>
					<th></th>
					<th>Friendly Name</th>
					<th>Username</th>
					<th>IP</th>
					<th>Created</th>
					<th>Updated</th>
					<th>Expires</th>
					<th></th>
				</tr>
			</thead>
			<tbody>
EOF
	# Body
	foreach my $pmid (@poolMembers) {
		my $poolMember;
		# If we can't get the pool member just move onto the next
		if (!defined($poolMember = getPoolMember($pmid))) {
			next;
		}

		# Get a nice last update string
856
		my $poolMemberFriendlyName = (defined($poolMember->{'FriendlyName'}) && $poolMember->{'FriendlyName'} ne "") ?
857
				$poolMember->{'FriendlyName'} : $poolMember->{'Username'};
858
		my $poolMemberFriendlyNameEncoded = encode_entities($poolMemberFriendlyName);
859

860
		my $poolMemberUsernameEncoded = encode_entities($poolMember->{'Username'});
861

862
		my $poolMemberIPEncoded = encode_entities($poolMember->{'IPAddress'});
863

864
865
866
867
868
869
		my $poolMemberCreatedStr = encode_entities(($poolMember->{'Created'} > 0) ?
				DateTime->from_epoch( epoch => $poolMember->{'Created'} )->iso8601() : '-never-');
		my $poolMemberUpdatedStr = encode_entities(($poolMember->{'LastUpdate'} > 0) ?
				DateTime->from_epoch( epoch => $poolMember->{'LastUpdate'} )->iso8601() : '-never-');
		my $poolMemberExpiresStr = encode_entities(($poolMember->{'Expires'} > 0) ?
				DateTime->from_epoch( epoch => $poolMember->{'Expires'} )->iso8601() : '-never-');
870

871
872
		my $poolMemberShaperState = getPoolMemberShaperState($poolMember->{'ID'});

873
874
		# Display relevant icons depending on pool status
		my $icons = "";
875
		if (!($poolMemberShaperState & SHAPER_LIVE)) {
876
877
878
879
880
881
882
883
			$icons .= '<span class="glyphicon glyphicon-time" />';
		}
		if ($poolMember->{'Status'} == CFGM_NEW) {
			$icons .= '<span class="glyphicon glyphicon-import" />';
		}
		if ($poolMember->{'Status'} == CFGM_OFFLINE) {
			$icons .= '<span class="glyphicon glyphicon-trash" />';
		}
884
885
886
		if ($poolMemberShaperState & SHAPER_CONFLICT) {
			$icons .= '<span class="glyphicon glyphicon-random" />';
		}
887

888
889
890
		my $urlPoolMemberEdit = sprintf('/limits/poolmember-edit?pmid=%s',uri_escape($poolMember->{'ID'}));
		my $urlPoolMemberRemove = sprintf('/limits/poolmember-remove?pmid=%s',uri_escape($poolMember->{'ID'}));

891
892
893
		$content .= <<EOF;
				<tr>
					<td>$icons</td>
894
895
896
897
898
899
					<td>$poolMemberFriendlyNameEncoded</td>
					<td>$poolMemberUsernameEncoded</td>
					<td>$poolMemberIPEncoded</td>
					<td>$poolMemberCreatedStr</td>
					<td>$poolMemberUpdatedStr</td>
					<td>$poolMemberExpiresStr</td>
900
					<td>
901
902
						<a href="$urlPoolMemberEdit"><span class="glyphicon glyphicon-wrench"></span></a>
						<a href="$urlPoolMemberRemove"><span class="glyphicon glyphicon-remove"></span></a>
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
					</td>
				</tr>
EOF
	}

	# No results
	if (!@poolMembers) {
		$content .=<<EOF;
				<tr class="info">
					<td colspan="8"><p class="text-center">No Results</p></td>
				</tr>
EOF
	}

	# Footer
	$content .=<<EOF;
			</tbody>
		</table>
		<span class="glyphicon glyphicon-time" /> - Processing,
		<span class="glyphicon glyphicon-edit" /> - Override,
		<span class="glyphicon glyphicon-import" /> - Being Added,
		<span class="glyphicon glyphicon-trash" /> - Being Removed,
		<span class="glyphicon glyphicon-random" /> - Conflicts
EOF

END:
	return (HTTP_OK,$content,{ 'menu' => $customMenu });
}


Nigel Kukard's avatar
Nigel Kukard committed
933

934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
# Pool member add/edit action
sub poolmember_addedit
{
	my ($kernel,$globals,$client_session_id,$request) = @_;


	# Setup our environment
	my $logger = $globals->{'logger'};

	# Errors to display above the form
	my @errors;

	# Items for our form...
	my @formElements = qw(
		FriendlyName
		Username IPAddress
		MatchPriorityID
		Expires inputExpires.modifier
		Notes
	);

	# Expires modifier options
	my $expiresModifiers = {
		'm' => "Minutes",
		'h' => "Hours",
		'd' => "Days",
		'n' => "Never",
	};


	# Title of the form, by default its an add form
	my $formType = "Add";
	my $formNoEdit = "";
	# Form data
	my $formData;
	# Pool
	my $pool;
	my $poolMember;


	# Parse query params
	my $queryParams = parseURIQuery($request);

	# If we have a pool member ID, pull in the pool member
	if (defined($queryParams->{'pmid'})) {
		# Check if we can grab the pool member
		if (!defined($poolMember = getPoolMember($queryParams->{'pmid'}->{'value'}))) {
981
			return (HTTP_TEMPORARY_REDIRECT,"/limits");
982
983
984
985
986
987
988
989
		}

		$pool = getPool($poolMember->{'PoolID'});

	# If we have a pool ID, pull in the pool
	} elsif (defined($queryParams->{'pid'})) {
		# Check if we can grab the pool
		if (!defined($pool = getPool($queryParams->{'pid'}->{'value'}))) {
990
			return (HTTP_TEMPORARY_REDIRECT,"/limits");
991
992
993
994
995
996
997
998
999
1000
		}
	}

	# If this is a form try parse it
	if ($request->method eq "POST") {
		# Parse form data
		my $form = parseFormContent($request->content);

		# If user pressed cancel, redirect
		if (defined($form->{'cancel'})) {
For faster browsing, not all history is shown. View entire blame