├── AllScan-logo.png
├── AllScan.png
├── AllScanInstallUpdate.php
├── LICENSE
├── README.md
├── _tools
├── agi-bin
│ ├── getip.sh
│ ├── poweroff.sh
│ ├── poweroff.ul
│ ├── reboot.sh
│ ├── reboot.ul
│ ├── sayip.sh
│ ├── test.ul
│ ├── wifidisabled.ul
│ ├── wifidown.sh
│ ├── wifienabled.ul
│ └── wifiup.sh
├── cgrep.php
├── checkFs.php
├── copyFromWww.php
├── copyToWww.php
├── diffWww.php
├── excludeFiles.txt
├── setUpDtmfCmds.php
└── wgrep.php
├── api
└── index.php
├── astapi
├── AMI.php
├── cmd.php
├── connect.php
├── nodeInfo.php
└── server.php
├── cfg
├── CfgView.php
└── index.php
├── css
└── main.css
├── docs
├── extensions.conf
├── favorites.ini.sample
├── global.inc.sample
├── rpt.conf
└── screenshots
│ ├── cfgs.png
│ ├── init.png
│ ├── settings.png
│ ├── user-edit.png
│ └── users.png
├── favicon.ico
├── favorites-Sample.ini
├── include
├── CfgModel.php
├── DB.php
├── DataSource.php
├── Html.php
├── UserModel.php
├── apiInit.php
├── common.php
├── commonForms.php
├── dbUtils.php
├── favsUtils.php
├── hwUtils.php
├── logUtils.php
├── timeUtils.php
├── timezones.php
├── version.txt
└── viewUtils.php
├── index.php
├── js
└── main.js
├── screenshot.png
├── stats
└── stats.php
└── user
├── UserView.php
├── index.php
└── settings
└── index.php
/AllScan-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidgsd/AllScan/3cc2a49cc08766d87e304a5bbd677e12a95a38f8/AllScan-logo.png
--------------------------------------------------------------------------------
/AllScan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidgsd/AllScan/3cc2a49cc08766d87e304a5bbd677e12a95a38f8/AllScan.png
--------------------------------------------------------------------------------
/AllScanInstallUpdate.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php
2 | \n";
15 | exit(0);
16 | }
17 |
18 | // Check if this file is the most recent version
19 | checkInstallerVersion();
20 |
21 | // Determine web server folder
22 | $dirs = ['/var/www/html', '/srv/http'];
23 | foreach($dirs as $d) {
24 | if(is_dir($d)) {
25 | $webdir = $d;
26 | break;
27 | }
28 | }
29 | msg("Web Server Folder: " . (isset($webdir) ? $webdir : "Not Found."));
30 |
31 | // Determine web server group name
32 | $name = ['www-data', 'http', 'apache'];
33 | foreach($name as $n) {
34 | if(`grep "^$n:" /etc/group`) {
35 | $group = $n;
36 | break;
37 | }
38 | }
39 | msg("Web Group Name: " . (isset($group) ? $group : "Not Found."));
40 |
41 | if(!isset($webdir) || !isset($group))
42 | exit();
43 |
44 | // cd to web root folder
45 | $cwd = getcwd();
46 | if($cwd !== $webdir) {
47 | msg("Changing dir from $cwd to $webdir");
48 | chdir($webdir);
49 | $cwd = getcwd();
50 | if($cwd !== $webdir)
51 | errExit("cd failed.");
52 | }
53 |
54 | // Destination dir in web root folder:
55 | $asdir = 'allscan';
56 | // Enable mkdir(..., 0775) to work properly
57 | umask(0002);
58 | clearstatcache();
59 | // Check if dir exists. If so, see if an update is needed. If not, install AllScan
60 | $dlfiles = true;
61 | $ver = 'Unknown';
62 | if(is_dir($asdir)) {
63 | msg("$asdir dir exists. Checking if update needed...");
64 | if(checkUpdate($ver)) {
65 | msg("AllScan is out-of-date.");
66 | $s = readline("Ready to Update AllScan. Enter 'y' to confirm, any other key to exit: ");
67 | if($s !== 'y')
68 | exit();
69 | $bak = "$asdir.bak.$ver";
70 | msg("Moving $asdir/ to $bak/...");
71 | if(is_dir($bak))
72 | execCmd("rm -rf $bak");
73 | execCmd("mv $asdir $bak");
74 | } else {
75 | msg("AllScan is up-to-date.");
76 | $dlfiles = false;
77 | }
78 | } else {
79 | msg("$asdir dir not found.");
80 | $s = readline("Ready to Install AllScan. Enter 'y' to confirm, any other key to exit: ");
81 | if($s !== 'y')
82 | exit();
83 | }
84 |
85 | if($dlfiles) {
86 | $fname = 'main.zip';
87 | $url = 'https://github.com/davidgsd/AllScan/archive/refs/heads/' . $fname;
88 | $zdir = 'AllScan-main';
89 | if(file_exists($fname))
90 | unlink($fname);
91 | if(is_dir($zdir))
92 | exec("rm -rf $zdir");
93 | if(!execCmd("wget -q '$url'") || !file_exists($fname))
94 | errExit("Retrieve $fname from github failed. Try executing \"wget '$url'\" and check error messages. Also confirm that your node supports https and that its system time/RTC is set correctly.");
95 | if(!execCmd("unzip -q $fname"))
96 | errExit('Unzip failed. Check that you have unzip installed. Try "sudo apt-get install unzip" to install');
97 | unlink($fname);
98 | if(!rename($zdir, $asdir))
99 | msg("ERROR: mv($zdir, $asdir) failed");
100 | // Copy any user .ini files from old version backup folder
101 | if(isset($bak) && is_dir($bak)) {
102 | msg("Checking for .ini files in $bak/...");
103 | execCmd("cp -n $bak/*.ini $asdir/");
104 | }
105 | }
106 |
107 | msg("Verifying $asdir dir has $group group writable permissions");
108 | if((fileperms($asdir) & 0777) != 0775)
109 | execCmd("chmod 775 $asdir");
110 | if(getGroupName($asdir) !== $group)
111 | execCmd("chgrp $group $asdir");
112 | $inis = glob("$asdir/*{.ini,.ini.bak}", GLOB_BRACE);
113 | if(!empty($inis)) {
114 | foreach($inis as $f) {
115 | if((fileperms($f) & 0777) != 0664)
116 | execCmd("chmod 664 $f");
117 | if(getGroupName($f) !== $group)
118 | execCmd("chgrp $group $f");
119 | }
120 | }
121 |
122 | checkDbDir();
123 | checkSmDir();
124 |
125 | msg("PHP Version: " . phpversion());
126 | $astver = trim(shell_exec('asterisk -V') ?? 'Unknown');
127 | msg("Asterisk/ASL Version: " . $astver);
128 |
129 | // Confirm necessary php extensions are installed
130 | msg("Checking OS packages and PHP extensions...");
131 | if(!class_exists('SQLite3')) {
132 | msg("Required SQLite3 Class not found." . NL
133 | ."Try running the update commands below to update your OS and php-sqlite3 package." . NL
134 | ."You may also need to enable the pdo_sqlite and sqlite3 extensions in php.ini.");
135 | }
136 |
137 | msg("Would you like to check for OS/package updates?" . NL
138 | ."If you recently updated your system or if everything is working great you do not need to do this now. " . NL
139 | ."The update process can cause some software packages to stop working or require config file updates." . NL
140 | ."DO NOT PROCEED WITH THIS STEP IF YOU'RE NOT SURE OR ARE NOT AN AUTHORIZED SERVER ADMIN." . NL
141 | ."Otherwise it is recommended to ensure your system is up-to-date.");
142 | $s = readline("Enter 'y' to proceed, any other key to skip this step: ");
143 | if($s === 'y') {
144 | if(is_executable('/usr/bin/apt-get')) {
145 | passthruCmd("apt-get -y update");
146 | passthruCmd("apt-get upgrade");
147 | } elseif(is_executable('/usr/bin/yum')) {
148 | passthruCmd("yum -y update");
149 | passthruCmd("yum upgrade");
150 | } elseif(is_executable('/usr/bin/pacman')) {
151 | passthruCmd("pacman -Syu");
152 | passthruCmd("pacman -S php-sqlite");
153 | }
154 | restartWebServer();
155 | }
156 |
157 | // if ASL3, make sure astdb.txt is available, and sqlite3 and avahi-daemon are set up
158 | if(is_executable('/usr/bin/apt')) {
159 | if(!is_file('/etc/systemd/system/asl3-update-astdb.service')) {
160 | passthruCmd("sudo apt install -y asl3-update-nodelist 2> /dev/null");
161 | }
162 | $fc = exec("find /var/lib/php -name sqlite3 -type f -printf '.'| wc -c");
163 | if(!$fc) {
164 | passthruCmd("sudo apt install -y php-sqlite3 2> /dev/null");
165 | restartWebServer();
166 | }
167 | if(!is_executable('/usr/bin/asl-tts')) {
168 | passthruCmd("sudo apt install -y asl3-tts 2> /dev/null");
169 | }
170 | if(!is_executable('/usr/sbin/avahi-daemon')) {
171 | passthruCmd("sudo apt install -y avahi-daemon 2> /dev/null");
172 | }
173 | sleep(1);
174 | if(is_file('/etc/systemd/system/asl3-update-astdb.service')) {
175 | if(exec('systemctl is-enabled asl3-update-astdb.service') !== "enabled")
176 | passthruCmd("systemctl enable asl3-update-astdb.service 2> /dev/null");
177 | if(exec('systemctl is-enabled asl3-update-astdb.timer') !== "enabled") {
178 | passthruCmd("systemctl enable asl3-update-astdb.timer 2> /dev/null");
179 | passthruCmd("systemctl start asl3-update-astdb.timer 2> /dev/null");
180 | }
181 | // Make a readable copy of allmon3.ini (Allmon3 updates can reset the file permissions)
182 | $fname = '/etc/allmon3/allmon3.ini';
183 | if(file_exists($fname)) {
184 | $fname2 = '/etc/asterisk/allmon.ini.php';
185 | if(!file_exists($fname2) || exec("diff $fname $fname2"))
186 | execCmd("cp $fname $fname2");
187 | if((fileperms($fname2) & 0777) != 0660)
188 | execCmd("chmod 660 $fname2");
189 | if(getGroupName($fname2) !== $group)
190 | execCmd("chgrp $group $fname2");
191 | }
192 | }
193 | }
194 |
195 | // Confirm SQLite3 is enabled in php.ini
196 | $fn = exec('sudo find /etc/php -name php.ini |grep -v cli');
197 | if($fn) {
198 | msg("php.ini location: $fn");
199 | $lcgood = exec("grep sqlite /etc/php/8.2/apache2/php.ini | grep '^extension=' | wc -l");
200 | $lcbad = exec("grep sqlite /etc/php/8.2/apache2/php.ini | grep '^;extension=' | wc -l");
201 | if($lcgood >= 2)
202 | msg("php.ini appears to have SQLite3 enabled");
203 | elseif($lcbad < 2) {
204 | msg("\nWARNING: SQLite3 extension does not appear to be enabled in php.ini.\n"
205 | ."You may need to manually edit the file and uncomment (remove leading ';') "
206 | ."the lines that say 'extension=pdo_sqlite' and 'extension=sqlite3'.");
207 | $s = readline("Hit any key to confirm");
208 | } else {
209 | msg("Backing up php.ini -> $fn.bak");
210 | execCmd("cp $fn $fn.bak");
211 | msg("Enabling SQLite3 extension in php.ini");
212 | execCmd("sed -i 's/;extension=pdo_sqlite/extension=pdo_sqlite/g' $fn");
213 | execCmd("sed -i 's/;extension=sqlite3/extension=sqlite3/g' $fn");
214 | restartWebServer();
215 | }
216 | } else {
217 | msg("php.ini not found in /etc/php/");
218 | }
219 |
220 | // Check DTMF command script and audio files
221 | $d0 = "$webdir/$asdir/_tools/agi-bin";
222 | $d1 = "/usr/share/asterisk/agi-bin";
223 | $ls0 = trim(shell_exec("ls $d0 2>/dev/null"));
224 | if(!$ls0) {
225 | msg("Error reading $d0/.");
226 | } else {
227 | if(!file_exists($d1)) {
228 | msg("$d1/ not found.");
229 | } else {
230 | $f0 = explode(NL, $ls0);
231 | sort($f0);
232 | $ls1 = trim(shell_exec("ls $d1 2>/dev/null"));
233 | $f1 = $ls1 ? explode(NL, $ls1) : [];
234 | sort($f1);
235 | $fcp = [];
236 | $ov = 0;
237 | foreach($f0 as $f) {
238 | if(array_search($f, $f1) === false) {
239 | $fcp[] = $f;
240 | } elseif(exec("diff $d0/$f $d1/$f")) {
241 | $fcp[] = $f;
242 | $ov++;
243 | }
244 | }
245 | if(count($fcp)) {
246 | msg("Copy DTMF command support files to /usr/share/asterisk/agi-bin/?\n"
247 | ."Files to be copied: " . implode(', ', $fcp));
248 | if($ov)
249 | msg("Warning: This will result in $ov file(s) being overwritten");
250 | if(count($f1))
251 | msg("Existing files in directory: " . implode(', ', $f1));
252 | $s = readline("Enter 'y' to confirm, any other key to skip: ");
253 | if($s === 'y') {
254 | foreach($fcp as $f)
255 | execCmd("cp $d0/$f $d1/$f");
256 | }
257 | }
258 | }
259 | msg("See $webdir/$asdir/docs/rpt.conf and extensions.conf for DTMF command setup notes.");
260 | }
261 |
262 | msg("Install/Update Complete.");
263 |
264 | // Show URLs where AllScan can be accessed and other notes
265 | $nn = exec("grep '^NODE = ' /etc/asterisk/extensions.conf");
266 | $bp = exec("grep '^bindport = ' /etc/asterisk/iax.conf");
267 | $ip = exec("wget -t 1 -T 3 -q -O- http://checkip.dyndns.org:8245 | cut -d':' -f2 | cut -d' ' -f2 | cut -d'<' -f1");
268 | $hn = exec('hostname');
269 | $lanip = exec("hostname -I | cut -f1 -d' '");
270 | if(!filter_var($lanip, FILTER_VALIDATE_IP)) {
271 | $lanip = exec("ifconfig | grep inet | head -1 | awk '{print $2}'");
272 | if($lanip === '127.0.0.1')
273 | $lanip = exec("ifconfig | grep inet | tail -1 | awk '{print $2}'");
274 | }
275 | $lip = '';
276 | if($lanip)
277 | $lip = "http://$lanip/$asdir/";
278 | if($hn) {
279 | if($lip)
280 | $lip .= ' or ';
281 | $lip .= "http://$hn.local/$asdir/";
282 | }
283 | if(!$lip)
284 | $lip = '[Local IPV4 address / hostname could not be determined]';
285 |
286 | $wip = "http://$ip/$asdir/";
287 | if(preg_match('/NODE = ([0-9]{4,6})/', $nn, $m) == 1) {
288 | $node = $m[1];
289 | $wip .= " or http://$node.nodes.allstarlink.org/$asdir/";
290 | }
291 | $port = 4569;
292 | if(preg_match('/bindport = ([0-9]{4,5})/', $bp, $m) == 1) {
293 | $port = $m[1];
294 | }
295 |
296 | msg("AllScan can be accessed at:\n\t$lip on the local network, or\n"
297 | ."\t$wip remotely\n\tif your router has port $port forwarded to this node.");
298 |
299 | if($dlfiles) {
300 | msg("Be sure to bookmark the above URL(s) in your browser.");
301 | msg("IMPORTANT: After updates do a CTRL-F5 in your browser (or long-press the reload button in mobile\n"
302 | ."browsers), or clear the browser cache, so that CSS and JavaScript files will properly update.");
303 | }
304 |
305 | exit();
306 |
307 | // ---------------------------------------------------
308 |
309 | function checkUpdate(&$ver) {
310 | global $asdir;
311 | $fname = "include/common.php";
312 | $vpat = '/^\$AllScanVersion = "v([0-9\.]{3,4})"/';
313 | if(!file_exists("$asdir/$fname"))
314 | return true;
315 | $file = file("$asdir/$fname");
316 | if(empty($file))
317 | return true;
318 | foreach($file as $line) {
319 | if(preg_match($vpat, $line, $m) == 1) {
320 | $ver = $m[1];
321 | break;
322 | }
323 | }
324 | msg("AllScan current version: $ver");
325 | if($ver < 0.93)
326 | return true;
327 |
328 | $url = "https://raw.githubusercontent.com/davidgsd/AllScan/main/$fname";
329 | $file = file($url);
330 | if(empty($file))
331 | errExit("Retrieve $fname from github failed. Try executing \"wget '$url'\" and check error messages. Also confirm that your node supports https and that its system time/RTC is set correctly.");
332 |
333 | foreach($file as $line) {
334 | if(preg_match($vpat, $line, $m) == 1) {
335 | $gver = $m[1];
336 | break;
337 | }
338 | }
339 | if(empty($gver))
340 | errExit("Error parsing $url. Please visit $asurl and follow the install/update instructions.");
341 |
342 | msg("AllScan github version: $gver");
343 | return ($ver < $gver);
344 | }
345 |
346 | function checkInstallerVersion() {
347 | global $AllScanInstallerUpdaterVersion;
348 | msg("AllScan Installer/Updater Version: $AllScanInstallerUpdaterVersion");
349 | $fname = basename(__FILE__);
350 | msg("Checking github version...");
351 | $url = "https://raw.githubusercontent.com/davidgsd/AllScan/main/$fname";
352 | $asurl = 'https://github.com/davidgsd/AllScan';
353 | $file = file($url);
354 | if(empty($file))
355 | errExit("Retrieve $fname from github failed. Try executing \"wget '$url'\" and check error messages. Also confirm that your node supports https and that its system time/RTC is set correctly.");
356 |
357 | foreach($file as $line) {
358 | if(preg_match('/^\$AllScanInstallerUpdaterVersion = "([0-9\.]{3,4})"/', $line, $m) == 1) {
359 | $gver = $m[1];
360 | break;
361 | }
362 | }
363 | if(empty($gver))
364 | errExit("Error parsing $url. Please visit $asurl and follow the install/update instructions there.");
365 |
366 | msg("AllScan Installer/Updater github version: $gver");
367 | if($AllScanInstallerUpdaterVersion != $gver)
368 | errExit("This file is out-of-date. Please visit $asurl and follow the install/update instructions there.");
369 | }
370 |
371 | // Execute command, show the command, show the output and return val
372 | function execCmd($cmd) {
373 | echo "Executing cmd: $cmd ... ";
374 | $out = '';
375 | $res = 0;
376 | $ok = (exec($cmd, $out, $res) !== false && !$res);
377 | $s = $ok ? 'OK' : 'ERROR';
378 | msg($s);
379 | return $ok;
380 | }
381 |
382 | function passthruCmd($cmd) {
383 | msg("Executing cmd: $cmd");
384 | $res = 0;
385 | $ok = (passthru($cmd, $res) !== false && !$res);
386 | $s = $ok ? 'OK' : 'ERROR';
387 | msg("Return Code: $s");
388 | return $ok;
389 | }
390 |
391 | function checkDbDir() {
392 | global $group, $ver;
393 | // Confirm /etc/allscan dir exists and is writable by web server
394 | $asdbdir = '/etc/allscan';
395 | if(!is_dir($asdbdir))
396 | execCmd("mkdir $asdbdir");
397 | if((fileperms($asdbdir) & 0777) != 0775)
398 | execCmd("chmod 775 $asdbdir");
399 | if(getGroupName($asdbdir) !== $group)
400 | execCmd("chgrp $group $asdbdir");
401 | // Backup DB file
402 | $dbfile = $asdbdir . '/allscan.db';
403 | if(!$ver)
404 | $ver = 'bak';
405 | $bakfile = "$dbfile.$ver";
406 | if(file_exists($dbfile) && !file_exists($bakfile))
407 | execCmd("cp $dbfile $bakfile");
408 | }
409 |
410 | function checkSmDir() {
411 | global $group;
412 | // Verify supermon folder favorites.ini and favorites.ini.bak writable by web server
413 | $smdir = 'supermon';
414 | if(is_dir($smdir)) {
415 | $favsini = 'favorites.ini';
416 | $favsbak = "$favsini.bak";
417 | msg("Confirming supermon $favsini and $favsbak writable by web server");
418 | chdir($smdir);
419 |
420 | if(!file_exists($favsini))
421 | execCmd("touch $favsini");
422 | if(!file_exists($favsbak))
423 | execCmd("touch $favsbak");
424 |
425 | if((fileperms($favsini) & 0777) != 0664)
426 | execCmd("chmod 664 $favsini");
427 | if((fileperms($favsbak) & 0777) != 0664)
428 | execCmd("chmod 664 $favsbak");
429 | if((fileperms('.') & 0777) != 0775)
430 | execCmd("chmod 775 .");
431 |
432 | if(getGroupName($favsini) !== $group)
433 | execCmd("chgrp $group $favsini");
434 | if(getGroupName($favsbak) !== $group)
435 | execCmd("chgrp $group $favsbak");
436 | if(getGroupName('.') !== $group)
437 | execCmd("chgrp $group .");
438 |
439 | chdir('..');
440 | }
441 | }
442 |
443 | function restartWebServer() {
444 | msg("Restarting web server...");
445 | if(is_executable('/usr/bin/apachectl') || is_executable('/usr/sbin/apachectl'))
446 | $cmd = "apachectl restart 2> /dev/null";
447 | else
448 | $cmd = "systemctl restart lighttpd.service 2> /dev/null";
449 | if(!execCmd($cmd))
450 | msg("Restart webserver or restart node now");
451 | }
452 |
453 | function getGroupName($f) {
454 | if(!($id = filegroup($f)))
455 | return '';
456 | $a = posix_getgrgid($id);
457 | return $a['name'] ?? '';
458 | }
459 |
460 | function msg($s) {
461 | echo $s . NL;
462 | }
463 |
464 | function errExit($s) {
465 | msg("\nERROR: $s\n");
466 | exit();
467 | }
468 |
--------------------------------------------------------------------------------
/_tools/agi-bin/getip.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | echo SET VARIABLE result `hostname -I | cut -f1 -d' '`
3 |
--------------------------------------------------------------------------------
/_tools/agi-bin/poweroff.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | sudo /usr/sbin/poweroff
3 |
--------------------------------------------------------------------------------
/_tools/agi-bin/poweroff.ul:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidgsd/AllScan/3cc2a49cc08766d87e304a5bbd677e12a95a38f8/_tools/agi-bin/poweroff.ul
--------------------------------------------------------------------------------
/_tools/agi-bin/reboot.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | sudo /usr/sbin/reboot
3 |
--------------------------------------------------------------------------------
/_tools/agi-bin/reboot.ul:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidgsd/AllScan/3cc2a49cc08766d87e304a5bbd677e12a95a38f8/_tools/agi-bin/reboot.ul
--------------------------------------------------------------------------------
/_tools/agi-bin/sayip.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php
2 | \n";
17 | exit(0);
18 | }
19 | $exts = ['c', 'h', 'php', 'txt', 'md', 'sh', 'html', 'conf'];
20 | $recursive = false;
21 | // Drop first element off of argv and argc (command text)
22 | $argc--;
23 | array_shift($argv);
24 | // Check for -r option
25 | if(isset($argv[0]) && $argv[0] === '-r') {
26 | $recursive = true;
27 | $argc--;
28 | array_shift($argv);
29 | }
30 | // Get search words
31 | $n = count($argv);
32 | if($n < 1) {
33 | echo "No search words specified.\n";
34 | exit(0);
35 | }
36 | $searchwords = $argv;
37 | // Change to pwd (script starts in dir where it is located, not where called from)
38 | $pwd = getcwd();
39 | chdir($pwd);
40 | //echo "pwd: $pwd\n";
41 | $subdirs = [];
42 | // Combine words into search pattern
43 | function escapeVal(&$val, $key) { $val = preg_quote($val, '/'); }
44 | array_walk($searchwords, 'escapeVal');
45 | $pattern = '('.implode('|', $searchwords).')';
46 | $level=0;
47 | searchDir('.', $subdirs, $pattern, $recursive);
48 | exit(0);
49 |
50 | function searchDir($dir, $subdirs, $pattern, $recursive) {
51 | global $exts, $pwd, $level;
52 | $level++;
53 | if($handle = opendir($dir)) {
54 | while(($file = readdir($handle)) !== false) {
55 | // If is a directory, dir not excluded, and recursion enabled, call this function recursively
56 | if(is_dir($file)) {
57 | if($recursive && !in_array($file, ['.', '..']) && filetype($file) !== 'link' &&
58 | (empty($subdirs) || in_array($file, $subdirs))) {
59 | chdir($file);
60 | // Do not recurse more than 3 levels
61 | searchDir('.', null, $pattern, ($level < 3));
62 | chdir("..");
63 | }
64 | } else {
65 | // Check file extension
66 | $path_parts = pathinfo($file);
67 | if(isset($path_parts['extension']) && in_array($path_parts['extension'], $exts)) {
68 | // Search file
69 | $contents = file($file);
70 | $results = preg_grep("/$pattern/", $contents);
71 | if(!empty($results)) {
72 | // Output results
73 | //var_dump($results);
74 | $path = getcwd() . "/" . $file;
75 | if(strpos($path, $pwd) === 0) {
76 | $path = '.' . substr($path, strlen($pwd));
77 | } elseif(preg_match("|/var/www/html/(.*)|", $path, $matches) == 1 ||
78 | preg_match("|/home/allscan/(.*)|", $path, $matches) == 1) {
79 | $path = $matches[1];
80 | }
81 | $lines = array_keys($results);
82 | foreach($lines as $line)
83 | printf("%s:%s: %s\n", $path, $line, trim($results[$line]));
84 | }
85 | }
86 | }
87 | }
88 | }
89 | closedir($handle);
90 | $level--;
91 | }
92 |
--------------------------------------------------------------------------------
/_tools/checkFs.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php
2 | 50MB in size in /var/log/ or /var/log/asterisk/
20 | checkDiskSpace($msg);
21 | if($showMsgs)
22 | echo implode(NL, $msg) . NL;
23 | if($cwd !== __DIR__)
24 | chdir($cwd);
25 | exit();
26 |
--------------------------------------------------------------------------------
/_tools/copyFromWww.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php
2 | \n";
5 | exit(0);
6 | }
7 | $webRoot = '/var/www/html/allscan/';
8 | // Look in web folder and copy all files to local dir, with exception of the following dirs:
9 | $excludes = ['test', 'old', '*.tmp', '*.bak', 'log.txt'];
10 | // Note: pwd starts in dir where script is located, not where called from
11 | // Do below if might be called from somewhere else
12 | $pwd = getcwd();
13 | chdir($pwd);
14 | echo "pwd = $pwd, webRoot = $webRoot\n";
15 | if(!is_dir($webRoot)) {
16 | echo "webRoot dir not found\n";
17 | exit(1);
18 | }
19 | if($webRoot === $pwd) {
20 | echo "Can't run script from web root folder. Go to dev folder.\n";
21 | exit(1);
22 | }
23 |
24 | copyFiles($excludes, false, false);
25 |
26 | //--- functions ---
27 | function copyFiles($excludes, $test=false, $toWww=true) {
28 | global $webRoot;
29 | echo "Copying development files ". ($toWww ? 'TO' : 'FROM') . " www dir...\n\n";
30 | $exclude = '';
31 | if(!empty($excludes)) {
32 | foreach($excludes as $dir)
33 | $exclude .= "--exclude='$dir' ";
34 | }
35 | $opts = $test ? '--list-only' : '';
36 | $dirs = $toWww ? '. ' . $webRoot : $webRoot . ' .';
37 | $cmd = "rsync -avC $exclude $opts $dirs";
38 | passthru($cmd);
39 | echo "\nDone\n";
40 | }
41 |
--------------------------------------------------------------------------------
/_tools/copyToWww.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php
2 | \n";
5 | exit(0);
6 | }
7 | $webRoot = '/var/www/html/allscan/';
8 | // Copy files to webroot, with exception of the following dirs/files:
9 | $excludes = array('.git', '.gitignore');
10 |
11 | // Note: pwd starts in dir where script is located, not where called from
12 | // Do below if might be called from somewhere else
13 | $pwd = getcwd();
14 | chdir($pwd);
15 | echo "pwd = $pwd, webRoot = $webRoot\n";
16 | if($webRoot === $pwd) {
17 | echo "Can't run script from web root folder. Go to dev folder.\n";
18 | exit(1);
19 | }
20 | copyFiles($excludes, false);
21 |
22 | //--- functions ---
23 | function copyFiles($excludes, $test=false, $toWww=true) {
24 | global $webRoot;
25 | $exclude = '';
26 | if(!empty($excludes)) {
27 | foreach($excludes as $pat)
28 | $exclude .= "--exclude='$pat' ";
29 | }
30 | $opts = $test ? '--list-only' : '';
31 | $dirs = $toWww ? '. ' . $webRoot : $webRoot . ' .';
32 | $cmd = "rsync -avC $exclude $opts $dirs";
33 | passthru($cmd);
34 | echo "Done\n";
35 | }
36 |
--------------------------------------------------------------------------------
/_tools/diffWww.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php
2 | \n";
5 | exit(0);
6 | }
7 | $webRoot = '/var/www/html/allscan/';
8 | // Note: pwd starts in dir where script is located, not where called from
9 | // Do below if might be called from somewhere else
10 | $pwd = getcwd();
11 | chdir($pwd);
12 | echo "pwd = $pwd, webRoot = $webRoot\n";
13 | if(!is_dir($webRoot)) {
14 | echo "webRoot dir not found\n";
15 | exit(1);
16 | }
17 | if($webRoot === $pwd) {
18 | echo "Can't run script from web root. Go to dev folder.\n";
19 | exit(1);
20 | }
21 |
22 | diffFiles();
23 |
24 | //--- functions ---
25 | function diffFiles() {
26 | global $webRoot;
27 | echo "Diff'ing development files vs. www dir...\n\n";
28 | $exclude = '-X _tools/excludeFiles.txt';
29 | $dirs = '. ' . $webRoot;
30 | $cmd = "diff -r $exclude $dirs";
31 | passthru($cmd);
32 | echo "\nDone\n";
33 | }
34 |
--------------------------------------------------------------------------------
/_tools/excludeFiles.txt:
--------------------------------------------------------------------------------
1 | msg
2 | old
3 | downloads
4 | *.tmp
5 | *.bak
6 | *log.txt
7 | *Log.txt
8 |
--------------------------------------------------------------------------------
/_tools/setUpDtmfCmds.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php
2 | \n";
18 | exit(0);
19 | }
20 |
21 | $dir1 = 'agi-bin';
22 | $dir2 = '/usr/share/asterisk/agi-bin';
23 | if(!is_dir($dir1))
24 | errExit("Dir '$dir1' not found.");
25 |
26 | if(!is_dir($dir2))
27 | errExit("Dir '$dir2' not found.");
28 |
29 | msg("Copying .sh and .ul files from '$dir1' to '$dir2'...");
30 |
31 | if(!passthruCmd("ls -l $dir1/"))
32 | errExit('ls failed');
33 |
34 | if(!passthruCmd("ls -l $dir2/"))
35 | errExit('ls failed');
36 |
37 | if(!passthruCmd("cp $dir1/* $dir2/"))
38 | errExit('cp failed');
39 |
40 | msg("Updating file permissions...");
41 |
42 | if(!passthruCmd("chmod 755 $dir2/*.sh"))
43 | errExit('chmod failed');
44 |
45 | if(!passthruCmd("chmod 644 $dir2/*.ul"))
46 | errExit('chmod failed');
47 |
48 | if(!passthruCmd("ls -l $dir2/"))
49 |
50 | msg('Files copied successfully. See comments in this file for text to add to rpt.conf and extensions.conf');
51 |
52 | exit();
53 |
54 | // ---------------------------------------------------
55 | // Execute command, show the command, show the output and return val
56 | function execCmd($cmd) {
57 | msg("Executing cmd: $cmd");
58 | $out = '';
59 | $res = 0;
60 | $ok = (exec($cmd, $out, $res) !== false && !$res);
61 | $s = $ok ? 'OK' : 'ERROR';
62 | msg("Return Code: $s");
63 | return $ok;
64 | }
65 |
66 | function passthruCmd($cmd) {
67 | msg("Executing cmd: $cmd");
68 | $res = 0;
69 | $ok = (passthru($cmd, $res) !== false && !$res);
70 | $s = $ok ? 'OK' : 'ERROR';
71 | msg("Return Code: $s");
72 | return $ok;
73 | }
74 |
75 | function msg($s) {
76 | echo $s . NL;
77 | }
78 |
79 | function errExit($s) {
80 | msg('ERROR: ' . $s);
81 | msg('Check directory permissions and that this script was run as sudo/root.');
82 | exit();
83 | }
84 |
--------------------------------------------------------------------------------
/_tools/wgrep.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php
2 | \n";
19 | exit(0);
20 | }
21 | $exts = ['htm', 'html', 'php', 'txt', 'css', 'js'];
22 | $recursive = false;
23 | // Drop first element off of argv and argc (command text)
24 | $argc--;
25 | array_shift($argv);
26 | // Check for -r option
27 | if(isset($argv[0]) && $argv[0] === '-r') {
28 | $recursive = true;
29 | $argc--;
30 | array_shift($argv);
31 | }
32 | // Get search words
33 | $n = count($argv);
34 | if($n < 1) {
35 | echo "No search words specified.\n";
36 | exit(0);
37 | }
38 | $searchwords = $argv;
39 | // Change to pwd (script starts in dir where it is located, not where called from)
40 | $pwd = getcwd();
41 | chdir($pwd);
42 | //echo "pwd: $pwd\n";
43 | $subdirs = [];
44 | // Combine words into search pattern
45 | function escapeVal(&$val, $key) { $val = preg_quote($val, '/'); }
46 | array_walk($searchwords, 'escapeVal');
47 | $pattern = '('.implode('|', $searchwords).')';
48 | searchDir('.', $subdirs, $pattern, $recursive);
49 | exit(0);
50 |
51 | function searchDir($dir, $subdirs, $pattern, $recursive) {
52 | global $exts;
53 | if($handle = opendir($dir)) {
54 | while(($file = readdir($handle)) !== false) {
55 | // If is a directory, dir not excluded, and recursion enabled, call this function recursively
56 | if(is_dir($file)) {
57 | if($recursive && !in_array($file, ['.', '..']) && filetype($file) !== 'link' &&
58 | (empty($subdirs) || in_array($file, $subdirs))) {
59 | chdir($file);
60 | // For now do not recurse more than 1 level deep
61 | searchDir('.', null, $pattern, false);
62 | chdir("..");
63 | }
64 | } else {
65 | // Check file extension
66 | $path_parts = pathinfo($file);
67 | if(isset($path_parts['extension']) && in_array($path_parts['extension'], $exts)) {
68 | // Search file
69 | $contents = file($file);
70 | $results = preg_grep("/$pattern/", $contents);
71 | if(!empty($results)) {
72 | // Output results
73 | //var_dump($results);
74 | $path = getcwd() . "/" . $file;
75 | if(preg_match("|/var/www/html/(.*)|", $path, $matches) == 1)
76 | $path = $matches[1];
77 | else if(preg_match("|/home/repeater/(.*)|", $path, $matches) == 1)
78 | $path = $matches[1];
79 | $lines = array_keys($results);
80 | foreach($lines as $line)
81 | printf("%s:%s: %s\n", $path, $line, trim($results[$line]));
82 | }
83 | }
84 | }
85 | }
86 | }
87 | closedir($handle);
88 | }
89 |
--------------------------------------------------------------------------------
/api/index.php:
--------------------------------------------------------------------------------
1 | true, 'data' => cpuTemp()], $f);
28 | break;
29 | }
30 | }
31 |
32 | function sendData($data, $event='errMsg') {
33 | $resp = ['event' => $event, 'data' => $data];
34 | echo json_encode($resp);
35 | ob_flush();
36 | flush();
37 | }
38 |
--------------------------------------------------------------------------------
/astapi/AMI.php:
--------------------------------------------------------------------------------
1 | getResponse($fp, $actionID);
19 | // logToFile('RES: ' . varDumpClean($res, true), AMI_DEBUG_LOG);
20 | $ok = (strpos($res[2], "Authentication accepted") !== false);
21 | // Determine App-rpt version. ASL3 and Asterisk 20 have some differences in AMI commands
22 | // eg. in ASL2 restart command is "restart now" but in ASL3 it's "core restart now".
23 | $s = $this->command($fp, 'rpt show version');
24 | if(preg_match('/app_rpt version: ([0-9\.]{1,9})/', $s, $m) == 1)
25 | $this->aslver = $m[1];
26 | }
27 |
28 | function command($fp, $cmdString, $debug=false) {
29 | // Generate ActionID to associate with response
30 | $actionID = 'cpAction_' . mt_rand();
31 | $ok = true;
32 | $msg = [];
33 | if((fwrite($fp, "ACTION: COMMAND\r\nCOMMAND: $cmdString\r\nActionID: $actionID\r\n\r\n")) > 0) {
34 | if($debug)
35 | logToFile('CMD: ' . $cmdString . ' - ' . $actionID, AMI_DEBUG_LOG);
36 | $res = $this->getResponse($fp, $actionID, $debug);
37 | if(!is_array($res))
38 | return $res;
39 | // Check for Asterisk AMI Success/Error response
40 | foreach($res as $r) {
41 | if($r === 'Response: Error')
42 | $ok = false;
43 | elseif(preg_match('/Output: (.*)/', $r, $m) == 1)
44 | $msg[] = $m[1];
45 | }
46 | if(_count($msg))
47 | return implode(NL, $msg);
48 | if($ok)
49 | return 'OK';
50 | return 'ERROR';
51 | }
52 | return "Get node $cmdString failed";
53 | }
54 |
55 | /* Example ASL2 AMI response:
56 | Response: Follows
57 | Privilege: Command
58 | ActionID: cpAction_...
59 | --END COMMAND--
60 | Example ASL3 AMI response:
61 | Response: Success
62 | Command output follows
63 | Output:
64 | ActionID: cpAction_...
65 | => "Response:" line indicates success of associated ActionID.
66 | */
67 |
68 | function getResponse($fp, $actionID, $debug=false) {
69 | $ignore = ['Privilege: Command', 'Command output follows'];
70 | $t0 = time();
71 | $response = [];
72 | if($debug)
73 | $sn = getScriptName();
74 | while(time() - $t0 < 20) {
75 | $str = fgets($fp);
76 | if($str === false)
77 | return $response;
78 | $str = trim($str);
79 | if($str === '')
80 | continue;
81 | if($debug)
82 | logToFile("$sn 1: $str", AMI_DEBUG_LOG);
83 | if(strpos($str, 'Response: ') === 0) {
84 | $response[] = $str;
85 | } elseif($str === "ActionID: $actionID") {
86 | $response[] = $str;
87 | while(time() - $t0 < 20) {
88 | $str = fgets($fp);
89 | if($str === "\r\n" || $str[0] === "\n" || $str === false)
90 | return $response;
91 | $str = trim($str);
92 | if($str === '' || in_array($str, $ignore))
93 | continue;
94 | $response[] = $str;
95 | if($debug)
96 | logToFile("$sn 2: $str", AMI_DEBUG_LOG);
97 | }
98 | }
99 | }
100 | if(count($response))
101 | return $response;
102 | logToFile("$sn: Timeout", AMI_DEBUG_LOG);
103 | return 'Timeout';
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/astapi/cmd.php:
--------------------------------------------------------------------------------
1 | connect($cfg[$localnode]['host']);
26 | if($fp === false)
27 | exit("Could not connect\n");
28 |
29 | $amiuser = $cfg[$localnode]['user'];
30 | $pass = $cfg[$localnode]['passwd'];
31 | if($ami->login($fp, $amiuser, $pass) === false)
32 | exit("Could not login\n");
33 |
34 | switch($button) {
35 | case 'dtmf':
36 | if(!preg_match("/^[\d*#,A-Da-d]+$/", $cmd))
37 | exit("Invalid command value\n");
38 | echo "Executing cmd \"$cmd\" on $localnode...";
39 | $ctxt = "rpt fun $localnode $cmd";
40 | break;
41 | case 'restart':
42 | echo "Restarting Asterisk...";
43 | $ctxt = ($ami->aslver >= 3) ? "core restart now" : "restart now";
44 | break;
45 | default:
46 | exit("Invalid command\n");
47 | }
48 | $resp = $ami->command($fp, $ctxt);
49 | fclose($fp);
50 | echo $resp;
51 |
--------------------------------------------------------------------------------
/astapi/connect.php:
--------------------------------------------------------------------------------
1 | connect($cfg[$localnode]['host']);
31 | if($fp === false)
32 | exit("Could not connect\n");
33 |
34 | $amiuser = $cfg[$localnode]['user'];
35 | $pass = $cfg[$localnode]['passwd'];
36 | if($ami->login($fp, $amiuser, $pass) === false)
37 | exit("Could not login\n");
38 |
39 | switch($button) {
40 | case 'connect':
41 | if($autodisc) {
42 | echo "Disconnect all nodes from $localnode...";
43 | $resp = $ami->command($fp, "rpt cmd $localnode ilink 6 0");
44 | echo $resp . BR;
45 | usleep(500000);
46 | }
47 | if($perm) {
48 | $ilink = 13;
49 | echo "Permanently Connect $localnode to $remotenode...";
50 | } else {
51 | $ilink = 3;
52 | echo "Connect $localnode to $remotenode...";
53 | }
54 | break;
55 | case 'monitor':
56 | if($perm) {
57 | $ilink = 12;
58 | echo "Permanently Monitor $remotenode from $localnode...";
59 | } else {
60 | $ilink = 2;
61 | echo "Monitor $remotenode from $localnode...";
62 | }
63 | break;
64 | case 'localmonitor':
65 | if($perm) {
66 | $ilink = 18;
67 | echo "Permanently Local Monitor $remotenode from $localnode...";
68 | } else {
69 | $ilink = 8;
70 | echo "Local Monitor $remotenode from $localnode...";
71 | }
72 | break;
73 | case 'disconnect':
74 | if($remotenode === '0') {
75 | $ilink = 6;
76 | echo "Disconnect all nodes from $localnode...";
77 | } else {
78 | $ilink = 11;
79 | echo "Disconnect $remotenode from $localnode...";
80 | }
81 | break;
82 | }
83 | $resp = $ami->command($fp, "rpt cmd $localnode ilink $ilink $remotenode");
84 | fclose($fp);
85 | echo $resp;
86 |
--------------------------------------------------------------------------------
/astapi/nodeInfo.php:
--------------------------------------------------------------------------------
1 | $info";
11 | } elseif($nodeNum > 3000000) {
12 | $info = getEchoLinkInfo($fp, $nodeNum);
13 | } elseif(!empty($node['ip'])) {
14 | if(strlen(trim($node['ip'])) > 3) {
15 | $info = 'Web Txcvr / Phone Portal (' . $node['ip'] . ')';
16 | } else {
17 | $info = 'Unknown Mode';
18 | }
19 | } elseif(is_numeric($nodeNum)) {
20 | $info = 'Node not in database';
21 | } elseif(`echo $nodeNum |egrep -c "\-P"` > 0) {
22 | $info = 'AllStar Phone Portal user';
23 | } else {
24 | $info = 'IaxRpt / Web Transceiver client';
25 | }
26 | return $info;
27 | }
28 |
29 | $elnk_cache = [];
30 |
31 | function getEchoLinkInfo($fp, $echonode) {
32 | global $elnk_cache, $ami;
33 | $lookup = (int)substr($echonode, 1); // Strip leading '3' and zeros
34 | if(isset($elnk_cache[$lookup])) {
35 | $column = $elnk_cache[$lookup];
36 | $time = time();
37 | if($time > $column[0])
38 | unset($elnk_cache[$lookup]);
39 | } else {
40 | $AMI = $ami->command($fp, "echolink dbget nodename $lookup"); // Get EchoLink data from Asterisk
41 | $rows = explode("\n", $AMI);
42 | $column = explode("|", $rows[0]);
43 | if($column[0] == $lookup) {
44 | $column[0] = time() + 300;
45 | } else {
46 | $column = [];
47 | $column[0] = time() + 30;
48 | $column[1] = $column[2] = '-';
49 | }
50 | $elnk_cache[$lookup] = $column;
51 | }
52 | // column[2] is EL node's IP Address which doesn't seem useful to show
53 | //$info = $column[1] . " [EchoLink $lookup] (" . $column[2] . ")";
54 | $info = $column[1] . " [EchoLink $lookup]";
55 | return $info;
56 | }
57 |
--------------------------------------------------------------------------------
/astapi/server.php:
--------------------------------------------------------------------------------
1 | 'Insufficient user permission to retrieve data.']);
14 | exit();
15 | }
16 |
17 | // Validate request
18 | if(empty($_GET['nodes'])) {
19 | sendData(['status' => 'Unknown request!']);
20 | exit();
21 | }
22 |
23 | // Read parms
24 | $passedNodes = explode(',', trim(strip_tags($_GET['nodes'])));
25 |
26 | chdir('..');
27 |
28 | // Load allmon.ini
29 | $cfg = readNodeCfg();
30 | if($cfg === false) {
31 | sendData(['status' => "allmon.ini not found."]);
32 | exit();
33 | }
34 |
35 | // Load ASL DB
36 | $astdb = readAstDb2();
37 |
38 | // Verify nodes are in ini file
39 | $nodes = [];
40 | foreach($passedNodes as $i => $node) {
41 | if(isset($cfg[$node])) {
42 | $nodes[] = $node;
43 | } else {
44 | sendData(['node'=>$node, 'status'=>"Node $node not in allmon.ini"], 'nodes');
45 | }
46 | }
47 |
48 | // Do not time out
49 | set_time_limit(0);
50 |
51 | // Open a socket to each Asterisk Manager
52 | $ami = new AMI();
53 | $servers = [];
54 | $fp = [];
55 | $chandriver = 'Unknown';
56 | $amicd = '';
57 | $rxstatssupported = false;
58 |
59 | foreach($nodes as $node) {
60 | $host = $cfg[$node]['host'];
61 | if(!$host) {
62 | $data['status'] = "Invalid host setting in allmon.ini [$node]";
63 | sendData($data, 'connection');
64 | continue;
65 | }
66 | $data = ['host'=>$host, 'node'=>$node];
67 | // Connect and login to each manager only once
68 | if(!array_key_exists($host, $servers)) {
69 | $data['status'] = "Connecting to Asterisk Manager $node $host...";
70 | $fp[$host] = $ami->connect($host);
71 | if($fp[$host] === false) {
72 | $data['status'] .= 'Connect Failed. Check allmon.ini settings.';
73 | } else {
74 | // Try to login
75 | $amiuser = $cfg[$node]['user'];
76 | $pass = $cfg[$node]['passwd'];
77 | if($ami->login($fp[$host], $amiuser, $pass) !== false) {
78 | $servers[$host] = 'y';
79 | $data['status'] .= 'Login OK';
80 | } else {
81 | $data['status'] .= "Login Failed. Check allmon.ini settings.";
82 | }
83 | // Log version info
84 | $data['status'] .= "
ASL Ver: $ami->aslver, AllScan Ver: "
85 | . substr($AllScanVersion, 1);
86 | // Check if rxaudiostats supported
87 | $msg = checkRxStatsSupport($ami, $fp[$host]);
88 | if(_count($msg))
89 | $data['status'] .= BR . implode(BR, $msg);
90 | }
91 | sendData($data, 'connection');
92 | }
93 | }
94 |
95 | // Main loop - build $data array and output as a json object
96 | $current = [];
97 | $saved = [];
98 | $nodeTime = [];
99 | //$n = 0;
100 | while(1) {
101 | foreach($nodes as $node) {
102 | // Is host of this node logged in?
103 | if(!isset($servers[$cfg[$node]['host']]))
104 | continue;
105 | $connectedNodes = getNode($fp[$cfg[$node]['host']], $node);
106 | $sortedConnectedNodes = sortNodes($connectedNodes);
107 | $info = getAstInfo($fp[$cfg[$node]['host']], $node);
108 | // Build array of time values
109 | $nodeTime[$node]['node'] = $node;
110 | $nodeTime[$node]['info'] = $info;
111 | // Build array
112 | $current[$node]['node'] = $node;
113 | $current[$node]['info'] = $info;
114 | // Save remote nodes
115 | $current[$node]['remote_nodes'] = [];
116 | $i = 0;
117 | foreach($sortedConnectedNodes as $arr) {
118 | // Store remote nodes time values
119 | $nodeTime[$node]['remote_nodes'][$i]['elapsed'] = $arr['elapsed'] ?? 0;
120 | $nodeTime[$node]['remote_nodes'][$i]['last_keyed'] = $arr['last_keyed'];
121 | // Store remote nodes other than time values
122 | // Array key of remote_nodes is not node number to prevent javascript (for in) sorting
123 | $current[$node]['remote_nodes'][$i]['node'] = $arr['node'] ?? '';
124 | $current[$node]['remote_nodes'][$i]['info'] = $arr['info'] ?? '';
125 | $current[$node]['remote_nodes'][$i]['link'] = ucwords(strtolower($arr['link'] ?? ''));
126 | $current[$node]['remote_nodes'][$i]['ip'] = $arr['ip'] ?? '';
127 | $current[$node]['remote_nodes'][$i]['direction'] = $arr['direction'] ?? '';
128 | $current[$node]['remote_nodes'][$i]['keyed'] = $arr['keyed'] ?? '';
129 | $current[$node]['remote_nodes'][$i]['mode'] = $arr['mode'] ?? '';
130 | $current[$node]['remote_nodes'][$i]['elapsed'] = ' ';
131 | $current[$node]['remote_nodes'][$i]['last_keyed'] = $arr['last_keyed'] === 'Never' ? 'Never' : NBSP;
132 | $current[$node]['remote_nodes'][$i]['cos_keyed'] = $arr['cos_keyed'] ?? 0;
133 | $current[$node]['remote_nodes'][$i]['tx_keyed'] = $arr['tx_keyed'] ?? 0;
134 | $current[$node]['remote_nodes'][$i]['lnodes'] = $arr['lnodes'] ?? [];
135 | $i++;
136 | }
137 | }
138 | // Send current nodes only when data changes
139 | if($current !== $saved) {
140 | sendData($current, 'nodes');
141 | // if($n++ == 5)
142 | // logToFile($current, 'log.txt');
143 | $saved = $current;
144 | }
145 | // Send times every cycle
146 | sendData($nodeTime, 'nodetimes');
147 | // Wait 500mS
148 | usleep(500000);
149 | }
150 |
151 | fwrite($fp, "ACTION: Logoff\r\n\r\n");
152 |
153 | exit();
154 |
155 | function checkRxStatsSupport($ami, $fp) {
156 | global $chandriver, $amicd, $rxstatssupported;
157 | $res = $ami->command($fp, "susb tune menu-support Y");
158 | if(strpos($res, 'RxAudioStats') === 0) {
159 | $rxstatssupported = true;
160 | $chandriver = 'Simpleusb';
161 | $amicd = 'susb';
162 | }
163 | if(!$rxstatssupported) {
164 | $res = $ami->command($fp, "radio tune menu-support Y");
165 | if(strpos($res, 'RxAudioStats') === 0) {
166 | $rxstatssupported = true;
167 | $chandriver = 'Usbradio';
168 | $amicd = 'radio';
169 | }
170 | }
171 | if(!$rxstatssupported) {
172 | $msg[] = "ASL version does not support RxAudioStats";
173 | } else {
174 | $msg[] = "RxAudioStats supported, $chandriver driver";
175 | $res = $ami->command($fp, "$amicd show settings");
176 | if(preg_match('/Card is ([-0-9]{1,2})/', $res, $m) == 1 && $m[1] >= 0) {
177 | $msg[] = "Channel driver settings:";
178 | $ra = explode(NL, $res);
179 | foreach($ra as $m) {
180 | if(strposa($m, ['Output ', 'Rx ', 'Tx ']))
181 | $msg[] = $m;
182 | }
183 | }
184 | }
185 | return $msg;
186 | }
187 |
188 | // Get status for this $node
189 | function getNode($fp, $node) {
190 | global $ami;
191 | static $errCnt=0;
192 | $actionRand = mt_rand(); // Asterisk Manger Interface an actionID so we can find our own response
193 | $actionID = 'xstat' . $actionRand;
194 | if(fwrite($fp, "ACTION: RptStatus\r\nCOMMAND: XStat\r\nNODE: $node\r\nActionID: $actionID\r\n\r\n") !== false) {
195 | $rptStatus = $ami->getResponse($fp, $actionID);
196 | } else {
197 | sendData(['status'=>'XStat failed!']);
198 | // On ASL3 if Asterisk restarts above error repeats indefinitely. Let client JS reinit connection.
199 | if(++$errCnt > 9)
200 | exit();
201 | }
202 | // format of Conn lines: Node# isKeyed lastKeySecAgo lastUnkeySecAgo
203 | $actionID = 'sawstat' . $actionRand;
204 | if(fwrite($fp, "ACTION: RptStatus\r\nCOMMAND: SawStat\r\nNODE: $node\r\nActionID: $actionID\r\n\r\n") !== false) {
205 | $sawStatus = $ami->getResponse($fp, $actionID);
206 | } else {
207 | sendData(['status'=>'sawStat failed!']);
208 | // On ASL3 if Asterisk restarts above error repeats indefinitely. Let client JS reinit connection.
209 | if(++$errCnt > 9)
210 | exit();
211 | }
212 | // Returns an array of currently connected nodes
213 | $current = parseNode($fp, $rptStatus, $sawStatus);
214 | return $current;
215 | }
216 |
217 | function sendData($data, $event='errMsg') {
218 | echo "event: $event\n";
219 | echo 'data: ' . json_encode($data) . "\n\n";
220 | ob_flush();
221 | flush();
222 | }
223 |
224 | function sortNodes($nodes) {
225 | $arr = [];
226 | $notHeard = [];
227 | $sortedNodes = [];
228 | // Build arrays of heard and unheard
229 | foreach($nodes as $nodeNum => $row) {
230 | if(!isset($row['last_keyed']) || $row['last_keyed'] == '-1') {
231 | $notHeard[$nodeNum] = 'Never heard';
232 | } else {
233 | $arr[$nodeNum] = $row['last_keyed'];
234 | }
235 | }
236 | // Sort nodes that have been heard
237 | if(count($arr) > 0) {
238 | asort($arr, SORT_NUMERIC);
239 | }
240 | // Add in nodes that have not been heard
241 | if(count($notHeard) > 0) {
242 | ksort($notHeard, SORT_NUMERIC);
243 | foreach($notHeard as $nodeNum => $row) {
244 | $arr[$nodeNum] = $row;
245 | }
246 | }
247 | // Build sorted node array
248 | foreach($arr as $nodeNum => $row) {
249 | // Build last_keyed string. Converts seconds to hours, minutes, seconds.
250 | if(isset($nodes[$nodeNum]['last_keyed']) && $nodes[$nodeNum]['last_keyed'] > -1) {
251 | $t = $nodes[$nodeNum]['last_keyed'];
252 | $h = floor($t / 3600);
253 | $m = floor(($t / 60) % 60);
254 | $s = $t % 60;
255 | $nodes[$nodeNum]['last_keyed'] = sprintf("%02d:%02d:%02d", $h, $m, $s);
256 | } else {
257 | $nodes[$nodeNum]['last_keyed'] = 'Never';
258 | }
259 | $sortedNodes[$nodeNum] = $nodes[$nodeNum];
260 | }
261 | return $sortedNodes;
262 | }
263 |
264 | function parseNode($fp, $rptStatus, $sawStatus) {
265 | $curNodes = [];
266 | $conns = []; // Directly connected nodes
267 | $lnodes = []; // All connected nodes
268 | $modes = [];
269 | // Parse 'rptStat Conn:' lines
270 | foreach($rptStatus as $line) {
271 | if(preg_match('/Conn: (.*)/', $line, $matches)) {
272 | $arr = preg_split("/\s+/", trim($matches[1]));
273 | if(is_numeric($arr[0]) && $arr[0] > 3000000) {
274 | // No IP w/EchoLink
275 | $conns[] = [$arr[0], "", $arr[1], $arr[2], $arr[3], $arr[4]];
276 | } else {
277 | $conns[] = $arr;
278 | }
279 | }
280 | if(preg_match('/Var: RPT_RXKEYED=(.)/', $line, $matches)) {
281 | $rxKeyed = $matches[1];
282 | }
283 | if(preg_match('/Var: RPT_TXKEYED=(.)/', $line, $matches)) {
284 | $txKeyed = $matches[1];
285 | }
286 | if(preg_match("/LinkedNodes: (.*)/", $line, $matches)) {
287 | $longRangeLinks = preg_split("/, /", trim($matches[1]));
288 | foreach($longRangeLinks as $line) {
289 | $n = substr($line, 1);
290 | $modes[$n]['mode'] = substr($line, 0, 1);
291 | if(is_numeric($n) && $n >= 2000 && $n < 1000000)
292 | $lnodes[] = $n;
293 | }
294 | }
295 | }
296 | // Parse 'sawStat Conn:' lines
297 | $keyups = [];
298 | foreach($sawStatus as $line) {
299 | if(preg_match('/Conn: (.*)/', $line, $matches)) {
300 | $arr = preg_split("/\s+/", trim($matches[1]));
301 | $keyups[$arr[0]] = ['node' => $arr[0], 'isKeyed' => $arr[1], 'keyed' => $arr[2], 'unkeyed' => $arr[3]];
302 | }
303 | }
304 | // Combine above arrays
305 | if(count($conns)) {
306 | // Local connects
307 | foreach($conns as $node) {
308 | $n = $node[0];
309 | $curNodes[$n]['node'] = $node[0];
310 | $curNodes[$n]['info'] = getAstInfo($fp, $node[0]);
311 | $curNodes[$n]['ip'] = $node[1];
312 | if(isset($node[5])) {
313 | $curNodes[$n]['direction'] = $node[3];
314 | $curNodes[$n]['elapsed'] = $node[4];
315 | $curNodes[$n]['link'] = $node[5];
316 | } else {
317 | $curNodes[$n]['direction'] = $node[2];
318 | $curNodes[$n]['elapsed'] = $node[3];
319 | if(isset($modes[$n]['mode']))
320 | $curNodes[$n]['link'] = ($modes[$n]['mode'] === 'C') ? "Connecting" : "Established";
321 | }
322 | $curNodes[$n]['keyed'] = 'N/A';
323 | $curNodes[$n]['last_keyed'] = 'N/A';
324 | $curNodes[$n]['mode'] = isset($modes[$n]) ? $modes[$n]['mode'] : 'Local Monitor';
325 | $n++;
326 | }
327 | // Pull in keyed
328 | foreach($keyups as $node => $arr) {
329 | $curNodes[$node]['keyed'] = $arr['isKeyed'] ? 'yes' : 'no';
330 | $curNodes[$node]['last_keyed'] = $arr['keyed'];
331 | }
332 | $curNodes[1]['node'] = 1;
333 | } else {
334 | $curNodes[1]['info'] = "NO CONNECTION";
335 | }
336 | $curNodes[1]['cos_keyed'] = ($rxKeyed === "1") ? 1 : 0;
337 | $curNodes[1]['tx_keyed'] = ($txKeyed === "1") ? 1 : 0;
338 | // Add list of all connected nodes
339 | $curNodes[1]['lnodes'] = $lnodes;
340 | return $curNodes;
341 | }
342 |
--------------------------------------------------------------------------------
/cfg/CfgView.php:
--------------------------------------------------------------------------------
1 | tableOpen($hdrCols, null, 'favs', null);
18 | $nullVal = '-';
19 | foreach($cfgs as $id => $val) {
20 | $name = $gCfgName[$id];
21 | $updated = $gCfgUpdated[$id] ?? null;
22 | if($updated) {
23 | $updated = getTimestamp($updated, $user->timezone_id);
24 | } else {
25 | $updated = $nullVal;
26 | }
27 | $def = $gCfgDef[$id];
28 | if($gCfgVals[$id]) {
29 | $val = $gCfgVals[$id][$val];
30 | $def = $gCfgVals[$id][$def];
31 | } elseif(is_array($val)) {
32 | $val = implode(', ', $val);
33 | $def = implode(', ', $def);
34 | }
35 | if($val === $def)
36 | $val = '[Default]';
37 | if(empty($def))
38 | $def = '-';
39 | $row = [$id, $name, $val, $def, $updated];
40 | $out .= $html->tableRow($row, null);
41 | }
42 | $out .= $html->tableClose();
43 | echo $out;
44 | }
45 |
46 | function showFiles($files, $activeFile) {
47 | global $html, $wwwroot, $asdir;
48 | $hdrCols = ['File', 'Size (Bytes)', 'Last Modified', 'Options'];
49 | $out = $html->tableOpen($hdrCols, null, 'favs', null);
50 | foreach($files as $f) {
51 | $parms = ['Submit'=>DOWNLOAD_FILE, 'file'=>$f->name];
52 | $info = ($f->name === $activeFile) ? ' [Default]' : '';
53 | $dl = $html->a(getScriptName(), $parms, $f->name);
54 | $parms = ['Submit'=>DELETE_FILE, 'file'=>$f->name];
55 | $del = $html->a(getScriptName(), $parms, 'Delete');
56 | $row = [$dl . $info, $f->size, $f->mtime, $del];
57 | $out .= $html->tableRow($row, null);
58 | }
59 | $out .= $html->tableClose();
60 | echo $out;
61 | }
62 |
63 | function showForms($cfg) {
64 | global $html, $user, $gCfgName, $gCfgVals;
65 | $form = new stdClass();
66 | if($cfg !== null) {
67 | $id = $cfg->cfg_id;
68 | $val = $cfg->val;
69 | // Show Edit form
70 | $form->fieldsetLegend = EDIT_CFG;
71 | $form->submit = [EDIT_CFG, CANCEL];
72 | if($gCfgVals[$id]) {
73 | $ctrl = ['select' => ['val', $gCfgVals[$id], $val]];
74 | } else {
75 | if(is_array($val))
76 | $val = implode(', ', $val);
77 | $ctrl = ['text' => ['val', $val]];
78 | }
79 | $form->fields = [
80 | 'Cfg Name' => ['r' => ['name', $gCfgName[$id]]],
81 | 'Value' => $ctrl];
82 | $form->id = 'editCfgForm';
83 | $form->hiddenFields['cfg_id'] = $id;
84 | echo htmlForm($form) . BR;
85 | } else {
86 | // Show Edit request form
87 | $list = [];
88 | foreach($gCfgName as $k => $name)
89 | $list[$k] = $name;
90 | $form->fieldsetLegend = EDIT_CFG;
91 | $form->submit = [EDIT_CFG, DEFAULT_CFG];
92 | $form->id = 'editCfgForm';
93 | $form->fields = ['Cfg Name' => ['select'=> ['cfg_id', $list]]];
94 | echo htmlForm($form);
95 | }
96 | }
97 |
98 | function showFavsCopyForm($files) {
99 | global $html;
100 | $form = new stdClass();
101 | // Show Edit form
102 | $form->fieldsetLegend = COPY_FILE;
103 | $form->submit = COPY_FILE;
104 | //$localdir = $wwwroot . '/' . $asdir . '/';
105 | $fl = [];
106 | foreach($files as $f)
107 | $fl[$f] = $f;
108 | $dl = [];
109 | $dirs = [asDir(), asDir(false)];
110 | foreach($dirs as $d)
111 | $dl[$d] = $d;
112 | $form->fields = [
113 | 'File to Copy' => ['select' => ['file', $fl]],
114 | 'Destination Dir' => ['select' => ['dir', $dl]],
115 | 'Name Suffix (optional)' => ['text' => ['suffix']]
116 | ];
117 | $form->id = 'copyFileForm';
118 | //$form->hiddenFields['cfg_id'] = $id;
119 | echo htmlForm($form) . BR;
120 | }
121 |
122 | function showFavsUploadForm() {
123 | global $html;
124 | $form = new stdClass();
125 | $form->fieldsetLegend = 'Upload File';
126 | $form->submit = UPLOAD_FILE;
127 | $dl = [];
128 | $dirs = [asDir(), asDir(false)];
129 | foreach($dirs as $d)
130 | $dl[$d] = $d;
131 | $form->fields = [
132 | 'Favorites File' => ['f' => ['fileupload']],
133 | 'Destination Dir' => ['select' => ['dir', $dl]],
134 | ];
135 | echo htmlForm($form) . BR;
136 | }
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/cfg/index.php:
--------------------------------------------------------------------------------
1 | validate();
22 | if(!readOk())
23 | redirect('user/');
24 |
25 | $msg = [];
26 | $parms = getRequestParms();
27 | // Ignore Add/Edit requests if not Admin user
28 | if(isset($parms['Submit']) && adminUser() && $parms['Submit'] !== CANCEL) {
29 | $formvars = ['cfg_id', 'val', 'file', 'dir', 'suffix', 'confirm'];
30 | $cfg = processForm($parms['Submit'], arrayToObj($parms, $formvars), $msg);
31 | }
32 |
33 | pageInit();
34 | h1("Manage Cfgs");
35 | $view = new CfgView();
36 |
37 | if(!empty($msg)) {
38 | h3("Process Form Results:");
39 | echo implode(BR, $msg) . BR;
40 | }
41 |
42 | // Show Cfgs
43 | h2("Configuration Parameters");
44 | $view->showCfgs($gCfg);
45 |
46 | // Show Edit forms
47 | if(adminUser())
48 | $view->showForms($cfg ?? null);
49 |
50 | if(adminUser()) {
51 | h1("Manage Favorites");
52 | h2("View/Download/Delete Favorites Files");
53 | chdir('..'); // NOTE: chdir back to pwd later if needed for other file system operations
54 | $activeFile = '';
55 | $files = findFavsFiles($activeFile);
56 | $cnt = 0;
57 | $fl = [];
58 | foreach($files as $f) {
59 | $r = new stdClass();
60 | $r->name = $f;
61 | $r->size = filesize($f);
62 | $r->mtime = getTimestamp(filemtime($f));
63 | $fl[] = $r;
64 | $cnt++;
65 | }
66 | if($cnt) {
67 | $view->showFiles($fl, $activeFile);
68 | }
69 |
70 | if(!$cnt) {
71 | p("No Favorites files found.");
72 | } else {
73 | h2("Copy/Backup Favorites Files");
74 |
75 | $view->showFavsCopyForm($files);
76 |
77 | p('The Favorites file select control on the main page enables easy switching between files, supporting grouping of favorites by location, type, interests, etc. A new favorites file can be created by copying and then editing an existing file, or uploading a new file. Favorites files can be stored in the AllScan web folder or in /etc/allscan/. If you have multiple AllScan instances installed (eg. for different node #s) files in /etc/allscan/ can be used by all instances.', 'w800', false);
78 | }
79 |
80 | h2("Upload Favorites File");
81 |
82 | $view->showFavsUploadForm();
83 |
84 | p('Favorites file names must be in the format favorites[-*].ini, ie. with an optional suffix before the .ini extension. Example valid filenames: favorites.ini, favorites-WestCoast.ini, favorites-UK.ini, favorites-nets.ini, etc.', 'w800', false);
85 | }
86 |
87 | asExit();
88 |
89 | //-------- functions --------
90 | function processForm($Submit, $cfg, &$msg) {
91 | global $cfgModel, $gCfg, $gCfgDef, $gCfgUpdated, $gCfgVals;
92 |
93 | if($Submit === DELETE_FILE || $Submit === CONFIRM_DELETE_FILE) {
94 | if(!isset($cfg->file))
95 | return null;
96 | $file = trim($cfg->file);
97 | if(!validateFavsFile($file, $msg))
98 | return null;
99 | if($Submit !== CONFIRM_DELETE_FILE) {
100 | pageInit();
101 | echo confirmForm("Confirm Delete", "Permanently delete \"$file\"?",
102 | (array)$cfg, null, false, [CONFIRM_DELETE_FILE, 'Cancel']);
103 | exit();
104 | }
105 | if(unlink($file))
106 | $msg[] = ok("Deleted $file OK");
107 | else
108 | $msg[] = error("Delete $file Failed, check directory permissions");
109 | return;
110 | }
111 |
112 | if($Submit === COPY_FILE) {
113 | if(!isset($cfg->file) || !isset($cfg->dir))
114 | return null;
115 | $file = trim($cfg->file);
116 | if(!validateFavsFile($file, $msg))
117 | return null;
118 | $dir = trim($cfg->dir);
119 | if(!preg_match('/^[\/\w\-. ]+$/', $dir)) {
120 | $msg[] = error("Invalid directory name");
121 | return null;
122 | }
123 | $suffix = $cfg->suffix ? trim($cfg->suffix, " \n\r\t\v\x00-") : '';
124 | if($suffix && !preg_match('/^[\w\-. ]+$/', $suffix)) {
125 | $msg[] = error("Invalid characters in suffix");
126 | return null;
127 | }
128 | //$name = basename($file, '.ini');
129 | //$new = $dir . trim($name);
130 | $new = $dir . 'favorites';
131 | if($suffix)
132 | $new .= '-' . $suffix;
133 | $new .= '.ini';
134 | if(file_exists($new))
135 | $msg[] = error("File $new already exists. Use Delete option to remove existing file before copying, or add a suffix to the name");
136 | elseif(copy($file, $new))
137 | $msg[] = ok("Copied $file to $new OK");
138 | else
139 | $msg[] = error("Copy to $new Failed, check directory permissions");
140 | return;
141 | }
142 |
143 | if($Submit === DOWNLOAD_FILE) {
144 | if(!isset($cfg->file))
145 | return null;
146 | $file = trim($cfg->file);
147 | if(!validateFavsFile($file, $msg))
148 | return null;
149 | $name = basename($cfg->file);
150 | outputTxtHeader($name);
151 | readfile($file);
152 | exit();
153 | }
154 |
155 | if($Submit === UPLOAD_FILE) {
156 | if(!isset($_FILES['fileupload'])) {
157 | return null;
158 | }
159 | $f = (object)$_FILES['fileupload'];
160 | /* [name] => favorites.ini
161 | [full_path] => favorites.ini
162 | [type] => application/octet-stream
163 | [tmp_name] => /tmp/phpCdcA9I
164 | [error] => 0
165 | [size] => 3107 */
166 | if($f->error || !$f->size) {
167 | //$msg[] = varDump($f, true);
168 | $msg[] = error("File upload error $f->error on \"$f->name\"");
169 | return null;
170 | }
171 | if(!validateFavsFile($f->name, $msg, false)) {
172 | unlink($f->tmp_name);
173 | return null;
174 | }
175 | if(!isset($cfg->dir)) {
176 | unlink($f->tmp_name);
177 | return null;
178 | }
179 | $dir = trim($cfg->dir);
180 | if(!preg_match('/^[\/\w\-. ]+$/', $dir)) {
181 | $msg[] = error("Invalid directory name");
182 | unlink($f->tmp_name);
183 | return null;
184 | }
185 | $new = $dir . $f->name;
186 | if(file_exists($new))
187 | $msg[] = error("File $new already exists. Use Delete option to remove existing file before uploading");
188 | elseif(copy($f->tmp_name, $new))
189 | $msg[] = ok("Uploaded $new OK");
190 | else
191 | $msg[] = error("Upload $new Failed, check directory permissions");
192 | unlink($f->tmp_name);
193 | return;
194 | }
195 |
196 | $id = $cfg->cfg_id;
197 | $val = $cfg->val ?? null;
198 | if(!array_key_exists($id, $gCfg)) {
199 | $msg[] = error('Cfg not found');
200 | return;
201 | }
202 | if($Submit === EDIT_CFG) {
203 | if($val === null) {
204 | $cfg->val = $gCfg[$cfg->cfg_id];
205 | return $cfg;
206 | } else {
207 | // Convert array cfgs from csv / validate enumerated cfgs
208 | if(is_array($gCfgDef[$id])) {
209 | $val = csvToArray($val);
210 | } elseif($gCfgVals[$id] !== null && !array_key_exists($val, $gCfgVals[$id])) {
211 | $msg[] = error('Invalid Cfg value');
212 | return;
213 | }
214 | // Return now if cfg val did not change
215 | if(cfgCompare($val, $gCfg[$id])) {
216 | $msg[] = 'Cfg val unchanged';
217 | return null;
218 | } else {
219 | //$msg[] = "cfg={$gCfg[$id]} val=$val";
220 | }
221 | // Set Cfg
222 | $msg[] = 'Setting Cfg';
223 | $gCfg[$id] = $val;
224 | $gCfgUpdated[$id] = time();
225 | }
226 | } elseif($Submit === DEFAULT_CFG) {
227 | // Return now if cfg val is already the default
228 | if(cfgCompare($gCfg[$id], $gCfgDef[$id])) {
229 | $msg[] = 'Cfg val already = default val';
230 | return null;
231 | } else {
232 | //$msg[] = "cfg={$gCfg[$id]} Defval={$gCfgDef[$id]}";
233 | }
234 | // Set Cfg to default val
235 | $msg[] = 'Defaulting Cfg';
236 | $gCfg[$id] = $gCfgDef[$id];
237 | unset($gCfgUpdated[$id]);
238 | } else {
239 | return null;
240 | }
241 | $msg[] = 'Saving Cfgs';
242 | $cfgModel->saveCfgs();
243 | if($cfgModel->error)
244 | $msg[] = error($cfgModel->error);
245 | return null;
246 | }
247 |
--------------------------------------------------------------------------------
/css/main.css:
--------------------------------------------------------------------------------
1 | html {
2 | background-color:hsl(240,25%,12%);
3 | scrollbar-color:gray #444;
4 | width:100%;
5 | }
6 | ::-webkit-scrollbar {
7 | width:15px;
8 | height:15px;
9 | background-color:#444;
10 | }
11 | ::-webkit-scrollbar-thumb {
12 | background:gray;
13 | border-radius:5px;
14 | }
15 | body {
16 | margin:0px auto 7px;
17 | padding:0 5px;
18 | color:rgb(200,195,188);
19 | min-width:350px;
20 | max-width:1800px;
21 | text-align:center;
22 | }
23 | body, textarea, input, select {
24 | font:12px/1.25 Verdana,Arial,Helvetica,sans-serif;
25 | }
26 | input[type=text], input[type=number], input[type=password], select, textarea {
27 | background-color:hsl(240,50%,30%);
28 | color:#fff;
29 | margin:1px 4px;
30 | padding:2px 4px;
31 | border:2px;
32 | }
33 | input[type=text], input[type=number], input[type=password], textarea, fieldset select {
34 | width:95%;
35 | }
36 | input[type=button], input[type=submit], input::file-selector-button {
37 | color:#fff;
38 | margin:2px 1px;
39 | background-color:#444;
40 | border-radius:3px;
41 | }
42 | input[type=button]:hover, input[type=submit]:hover, input::file-selector-button:hover {
43 | background-color:#777;
44 | }
45 | input.ctrl {
46 | font-size:14px;
47 | line-height:1;
48 | vertical-align:middle;
49 | padding:1px;
50 | }
51 | input[type=number] {
52 | -moz-appearance:textfield;
53 | }
54 | ul, ol {
55 | padding:0;
56 | margin:0 0.5em 0 1.5em;
57 | }
58 | a, a:link, a:visited, .nodeNum {
59 | color:hsl(130,50%,60%);
60 | text-decoration:none;
61 | }
62 | a:active, a:hover, .nodeNum:hover {
63 | color:hsl(130,50%,80%);
64 | text-decoration:none;
65 | }
66 | a.logo {
67 | color:hsl(210,30%,70%);
68 | font-weight:bold;
69 | }
70 | form, fieldset {
71 | display:inline-block;
72 | }
73 | fieldset, #statmsg {
74 | border:solid 3px rgb(51,51,119);
75 | padding:3px;
76 | margin:3px;
77 | border-radius:10px;
78 | }
79 | fieldset legend {
80 | font-size:16px;
81 | color:hsl(180,75%,50%);
82 | }
83 | form + h2 {
84 | margin-top:3px;
85 | }
86 | header {
87 | display:inline-block;
88 | color:hsl(210,30%,70%);
89 | background-color:rgba(51,51,119,0.75);
90 | padding:3px 7px 4px;
91 | border-bottom-left-radius:10px;
92 | border-bottom-right-radius:10px;
93 | font-size:125%;
94 | }
95 | h1 {
96 | font-size:18px;
97 | }
98 | h1, a.h1 {
99 | color:hsl(270,30%,70%);
100 | font-weight:bold;
101 | }
102 | h2 {
103 | color:hsl(240,30%,70%);
104 | font-size:16px;
105 | font-style:italic;
106 | margin-top:0.5em;
107 | margin-bottom:0.4em;
108 | }
109 | h3 {
110 | font-size:14px;
111 | }
112 | h4 {
113 | font-size:14px;
114 | margin:1px 0px;
115 | color:#5a5;
116 | }
117 | h3,h4 {
118 | color:hsl(270,20%,60%);
119 | margin-top:0.75em;
120 | margin-bottom:0;
121 | }
122 | h5 {
123 | margin:1px 0px;
124 | font-size:14px;
125 | display:inline-block;
126 | color:#5a5;
127 | }
128 | table {
129 | margin:0 auto;
130 | clear:both;
131 | border-collapse:separate;
132 | border-spacing:0;
133 | border-radius:10px;
134 | overflow:hidden;
135 | }
136 | td {
137 | max-width:32em;
138 | }
139 | p {
140 | max-width:80%;
141 | margin:0.5em auto;
142 | font-size:13px;
143 | }
144 | p.w800 {
145 | max-width:800px;
146 | }
147 | ul, ol {
148 | display:inline-block;
149 | max-width:80%;
150 | margin:0.5em auto;
151 | font-size:13px;
152 | }
153 | pre {
154 | text-align:left;
155 | white-space:pre-wrap;
156 | background-color:#223;
157 | padding:0.9em;
158 | margin-left:0.6em;
159 | margin-right:0.6em;
160 | max-width:800px;
161 | }
162 |
163 | #hb img {
164 | vertical-align:text-bottom;
165 | border-radius:0;
166 | }
167 | #node {
168 | border:2px solid hsl(240,40%,60%);
169 | margin:2px;
170 | padding:0 2px;
171 | font-size:14px;
172 | width:6em;
173 | }
174 | #statmsg {
175 | display:inline-block;
176 | margin:2px;
177 | min-width:460px;
178 | max-width:750px;
179 | height:4.5em;
180 | font-size:0.9em;
181 | line-height:1.2;
182 | overflow-y:scroll;
183 | resize:both;
184 | color:#aaa;
185 | }
186 | #scanmsg {
187 | color:#999;
188 | margin:2px auto;
189 | font-size:0.9em;
190 | line-height:1.2;
191 | font-family:"Lucida Console",Courier,monospace;
192 | max-width:95%;
193 | }
194 |
195 | table tr th, table tr td {
196 | border-right:1px solid #555;
197 | border-bottom:1px solid #555;
198 | }
199 | table tr th:first-child, table tr td:first-child {
200 | border-left:1px solid #555;
201 | }
202 | table tr:first-child th {
203 | border-top:1px solid #555;
204 | }
205 | table tr:first-child th:first-child {
206 | border-top-left-radius:10px;
207 | }
208 | table tr:first-child th:last-child {
209 | border-top-right-radius:10px;
210 | }
211 | table tr:last-child td:first-child {
212 | border-bottom-left-radius:10px;
213 | }
214 | table tr:last-child td:last-child {
215 | border-bottom-right-radius:10px;
216 | }
217 |
218 | table.noborder, table.noborder tr th, table.noborder tr td {
219 | border:0;
220 | }
221 | table.favs {
222 | margin:0 auto 0.5em;
223 | }
224 | table.favs thead tr {
225 | background-color:rgba(51,51,119,0.75);
226 | }
227 | table.favs tbody tr {
228 | line-height:100%;
229 | }
230 | table.favs tbody tr:hover {
231 | background-color:rgba(51,51,119,0.67);
232 | }
233 | table.favs th, table.favs td {
234 | padding:3px;
235 | }
236 |
237 | small {
238 | font:11px/1.25 "Helvetica Narrow","Arial Narrow",Verdana,Arial,Helvetica,sans-serif;
239 | }
240 |
241 | table.grid {
242 | min-width:460px;
243 | }
244 | table.grid thead tr {
245 | background-color:rgba(51,51,119,0.75);
246 | line-height:100%;
247 | }
248 | table.grid th, table.grid td {
249 | padding:4px;
250 | }
251 | table.grid tr.cColor td { /* Link Connecting */
252 | background-color:purple;
253 | color:yellow;
254 | }
255 | table.grid tr.rColor td, table.grid tr.rColor td a { /* Link Keyed */
256 | background-color:maroon;
257 | color:yellow;
258 | }
259 | table.grid tr.gColor td { /* Node Idle */
260 | background-color:hsl(150,50%,15%);
261 | }
262 | table.grid tr.tColor td { /* Node PTT */
263 | background-color:maroon;
264 | }
265 | table.grid tr.lColor td { /* Node COS */
266 | background-color:green;
267 | }
268 | table.grid tr.bColor td { /* Node COS+PTT */
269 | background-color:#660;
270 | }
271 | div.twrap {
272 | display:inline-block;
273 | border:solid 2px hsl(0,0%,40%);
274 | padding:0;
275 | border-radius:12px;
276 | }
277 | label, input[type=button].small, input[type=submit].small {
278 | font-size:11px;
279 | }
280 | a.menu {
281 | display:inline-block;
282 | padding:3px;
283 | text-decoration:none;
284 | color:#fff;
285 | background-color:rgb(51,51,119);
286 | }
287 | a.menu:link, a.menu:visited {
288 | color:#fff;
289 | }
290 | a.menu:hover {
291 | background-color:hsl(120,50%,33%);
292 | }
293 |
294 | .ok { color:#7f7; }
295 | .error { color:#f77;}
296 | .left { text-align:left; }
297 | .right { text-align:right; }
298 | .center { text-align:center; vertical-align:middle; }
299 | .floatleft { position:relative; float:left; }
300 | .floatright { position:relative; float:right; }
301 | .gray { color:#888; }
302 | .green { color:#7e7; }
303 | .ib { display:inline-block; }
304 | .m5 { margin:5px; }
305 | .nodeNum { cursor:pointer; }
306 | .w600 { max-width:600px; }
307 |
308 | .button1 {
309 | font-size:12px;
310 | background-color:#448;
311 | color:#fff;
312 | }
313 | .greenborder {
314 | border:solid 3px rgb(39,144,39);
315 | border-radius:15px;
316 | }
317 |
318 | pre.error {
319 | font-weight:bold;
320 | font-size:15px;
321 | }
322 | a.play::after {
323 | color:#0f0;
324 | font-size:11px;
325 | content:"\00A0\25B6";
326 | }
327 | a.dl::after {
328 | color:#0f0;
329 | font-size:12px;
330 | content:"\200A\25BC";
331 | }
332 |
--------------------------------------------------------------------------------
/docs/extensions.conf:
--------------------------------------------------------------------------------
1 | [my-ip]
2 | ;exten => s,1,Wait(1)
3 | ; same => n,SayAlpha(${CURL(http://myip.vg)})
4 | ; same => n,Hangup
5 |
6 | ; *690: Say LAN IP Address
7 | exten => 0,1,Wait(2)
8 | exten => 0,n,AGI(getip.sh)
9 | exten => 0,n,SayAlpha(${result})
10 | exten => 0,n,Hangup
11 |
12 | ; *691: Say WAN IP Address
13 | exten => 1,1,Wait(2)
14 | exten => 1,n,Set(result=${CURL(https://api.ipify.org)})
15 | exten => 1,n,SayAlpha(${result})
16 | exten => 1,n,Hangup
17 |
18 | ; *692: Turn Off Wi-Fi
19 | exten => 2,1,AGI(wifidown.sh)
20 | exten => 2,n,Wait(2)
21 | exten => 2,n,Playback(/usr/share/asterisk/agi-bin/wifidisabled)
22 | exten => 2,n,Hangup
23 |
24 | ; *693: Turn On Wi-Fi
25 | exten => 3,1,AGI(wifiup.sh)
26 | exten => 3,n,Wait(2)
27 | exten => 3,n,Playback(/usr/share/asterisk/agi-bin/wifienabled)
28 | exten => 3,n,Wait(1)
29 | exten => 3,n,Hangup
30 |
31 | ; *694: Reboot Node
32 | exten => 4,1,Wait(2)
33 | exten => 4,n,Playback(/usr/share/asterisk/agi-bin/reboot)
34 | exten => 4,n,Wait(1)
35 | exten => 4,n,AGI(reboot.sh)
36 | exten => 4,n,Hangup
37 |
38 | ; *695: Power Off Node
39 | exten => 5,1,Wait(2)
40 | exten => 5,n,Playback(/usr/share/asterisk/agi-bin/poweroff)
41 | exten => 5,n,Wait(1)
42 | exten => 5,n,AGI(poweroff.sh)
43 | exten => 5,n,Hangup
44 |
45 | ; *698: Announce LAN IP Address & mDNS URL using asl-tts
46 | exten => 8,1,AGI(sayip.sh,${NODE})
47 | exten => 8,n,Hangup
48 |
49 | ; *699: Play test file
50 | exten => 9,1,Wait(2)
51 | exten => 9,n,Playback(/usr/share/asterisk/agi-bin/test)
52 | exten => 9,n,Hangup
53 |
--------------------------------------------------------------------------------
/docs/favorites.ini.sample:
--------------------------------------------------------------------------------
1 | ; The %node% template will be replaced with your node number at run time.
2 | ; Put favorites in the [general] section that will show for all nodes.
3 | ;
4 | [general]
5 | ;
6 |
7 | label[] = " === SELECT ==="
8 | cmd[] = " "
9 |
10 | label[] = "AA6DP, Catalina, CA 51597"
11 | cmd[] = "rpt cmd %node% ilink 3 51597"
12 |
13 | label[] = "AB9S HUB1-ASL2, Chicago,IL 47487"
14 | cmd[] = "rpt cmd %node% ilink 3 47487"
15 |
16 | label[] = "AG4VA SOTA Hub, Cloud, VA 46969"
17 | cmd[] = "rpt cmd %node% ilink 3 46969"
18 |
19 | label[] = "East Coast Reflector 27339"
20 | cmd[] = "rpt cmd %node% ilink 3 27339"
21 |
22 | label[] = "GB3CR North Wales UK 512511"
23 | cmd[] = "rpt cmd %node% ilink 3 512511"
24 |
25 | label[] = "GB3EG 430.9125, wigan, greater manchester 54318"
26 | cmd[] = "rpt cmd %node% ilink 3 54318"
27 |
28 | label[] = "GB3PI Barkway, Hertfordshire, UK 50683"
29 | cmd[] = "rpt cmd %node% ilink 3 50683"
30 |
31 | label[] = "GB3ZB 430.825, Dundry, Bristol 2514"
32 | cmd[] = "rpt cmd %node% ilink 3 2514"
33 |
34 | label[] = "GB7SJ 433.175 Northwich Cheshire UK 41360"
35 | cmd[] = "rpt cmd %node% ilink 3 41360"
36 |
37 | label[] = "K4JDR 441.725+Backbone HUB, Raleigh, NC 42235"
38 | cmd[] = "rpt cmd %node% ilink 3 42235"
39 |
40 | label[] = "K6RRR San Diego Hangout 28404"
41 | cmd[] = "rpt cmd %node% ilink 3 28404"
42 |
43 | label[] = "K6TZ SBARC Hub, Santa Barbara, CA 43763"
44 | cmd[] = "rpt cmd %node% ilink 3 43763"
45 |
46 | label[] = "K9IIK Schaumburg, IL 27833"
47 | cmd[] = "rpt cmd %node% ilink 3 27833"
48 |
49 | label[] = "K9SA 443.250, Round Lake, IL 59246"
50 | cmd[] = "rpt cmd %node% ilink 3 59246"
51 |
52 | label[] = "K9TAY CFMC 144 - 146.760, Chicago, IL 555491"
53 | cmd[] = "rpt cmd %node% ilink 3 555491"
54 |
55 | label[] = "K9TAY CFMC 440 - 443.750, Chicago, IL 555492"
56 | cmd[] = "rpt cmd %node% ilink 3 555492"
57 |
58 | label[] = "KR8T 443.800+, Grand Rapids, MI 47243"
59 | cmd[] = "rpt cmd %node% ilink 3 47243"
60 |
61 | label[] = "KR9RK 442.000, Racine,Wisconsin 511681"
62 | cmd[] = "rpt cmd %node% ilink 3 511681"
63 |
64 | label[] = "M0HOY HUBNet, Manchester, UK 51288"
65 | cmd[] = "rpt cmd %node% ilink 3 51288"
66 |
67 | label[] = "M0JKT FreeSTAR UK HUB 1 2196"
68 | cmd[] = "rpt cmd %node% ilink 3 2196"
69 |
70 | label[] = "N0ECT HUB/Server, Glenwood Springs, Colorado 45479"
71 | cmd[] = "rpt cmd %node% ilink 3 45479"
72 |
73 | label[] = "N4LMC W4GTA 145.350, Lookout Mtn, GA - EL W4EDP-R 46331"
74 | cmd[] = "rpt cmd %node% ilink 3 46331"
75 |
76 | label[] = "N6LXX MAIN HUB 29833"
77 | cmd[] = "rpt cmd %node% ilink 3 29883"
78 |
79 | label[] = "N8UKF 443.200 Fusion, Muskegon Michigan 54576"
80 | cmd[] = "rpt cmd %node% ilink 3 54576"
81 |
82 | label[] = "N9GMT FM38 Network Hub, Milwaukee, WI 2495"
83 | cmd[] = "rpt cmd %node% ilink 3 2495"
84 |
85 | label[] = "NWAG NW AllStar Group, Morecambe, UK 53573"
86 | cmd[] = "rpt cmd %node% ilink 3 53573"
87 |
88 | label[] = "Parrot+ enhanced parrot, Dallas, TX 55553"
89 | cmd[] = "rpt cmd %node% ilink 3 55553"
90 |
91 | label[] = "W2JLD Hub 1, Rochester, NY 47620"
92 | cmd[] = "rpt cmd %node% ilink 3 47620"
93 |
94 | label[] = "W3QV/R 147.03 PMRC, Roxborough Philadelphia PA 47970"
95 | cmd[] = "rpt cmd %node% ilink 3 47970"
96 |
97 | label[] = "W4KEV/ R 145.370 Knoxville, TN 50015"
98 | cmd[] = "rpt cmd %node% ilink 3 50015"
99 |
100 | label[] = "W6EK 145.430, Auburn, CA 51018"
101 | cmd[] = "rpt cmd %node% ilink 3 51018"
102 |
103 | label[] = "W7HEN HUB, Henderson, NV 44045"
104 | cmd[] = "rpt cmd %node% ilink 3 44045"
105 |
106 | label[] = "W7YRC 146.880, Prescott, AZ 603231"
107 | cmd[] = "rpt cmd %node% ilink 3 603231"
108 |
109 | label[] = "W8IRA 147.160+, Grand Rapids, MI 472440"
110 | cmd[] = "rpt cmd %node% ilink 3 472440"
111 |
112 | label[] = "W8MW SOUTHCARS HUB, US 27404"
113 | cmd[] = "rpt cmd %node% ilink 3 27404"
114 |
115 | label[] = "W9BMK P25 Bridge Astro DIU, Chicago, IL 598730"
116 | cmd[] = "rpt cmd %node% ilink 3 598730"
117 |
118 | label[] = "W9HHX 145.270-, Milwaukee, WI 42742"
119 | cmd[] = "rpt cmd %node% ilink 3 42742"
120 |
121 | label[] = "W9SBE Lkpt Hub, Lockport, IL 43628"
122 | cmd[] = "rpt cmd %node% ilink 3 43628"
123 |
124 | label[] = "WA5AIR HUB#1, SouthCoastReflector.com [USA] 48752"
125 | cmd[] = "rpt cmd %node% ilink 3 48752"
126 |
127 | label[] = "WB9YPA/R 223.980 (-1.6MHz), South Bend, Indiana 51560"
128 | cmd[] = "rpt cmd %node% ilink 3 51560"
129 |
130 | label[] = "WM9W Chicago 458800"
131 | cmd[] = "rpt cmd %node% ilink 3 458800"
132 |
133 | label[] = "WW7PSR 146.960 Seattle, WA 2462"
134 | cmd[] = "rpt cmd %node% ilink 3 2462"
135 |
136 | ;
137 | ; Put favorites for specific nodes here.
138 | ;
139 | ;[1999]
140 | ;
141 |
--------------------------------------------------------------------------------
/docs/global.inc.sample:
--------------------------------------------------------------------------------
1 | PERMISSION_READ_ONLY,
19 | favsIniLoc => ['favorites.ini', '../supermon/favorites.ini', '/etc/allscan/favorites.ini'],
20 | call => '',
21 | location => '',
22 | title => '',
23 | autodisc_def => 1
24 | ];
25 |
26 | $gCfgName = [
27 | publicPermission => 'Public Permission',
28 | favsIniLoc => 'Favorites.ini Locations',
29 | call => 'Call Sign',
30 | location => 'Location',
31 | title => 'Node Title',
32 | autodisc_def => 'DiscBeforeConn Default'
33 | ];
34 |
35 | $publicPermissionVals = [
36 | PERMISSION_NONE => 'None (No Access)',
37 | PERMISSION_READ_ONLY => 'Read Only',
38 | PERMISSION_READ_MODIFY => 'Read/Modify',
39 | PERMISSION_FULL => 'Full'];
40 |
41 | $checkboxVals = [0=>'Off', 1=>'On'];
42 |
43 | // Value definition arrays for enumerated cfgs. Specify null for plain text/numeric cfgs
44 | $gCfgVals = [
45 | publicPermission => $publicPermissionVals,
46 | favsIniLoc => null,
47 | call => null,
48 | location => null,
49 | title => null,
50 | autodisc_def => $checkboxVals
51 | ];
52 |
53 | // Global Cfgs structure
54 | $gCfg = $gCfgDef;
55 | // Last update time of each gCfg (unix tstamp)
56 | $gCfgUpdated = [];
57 |
58 | // Below functions used to enable/disable site functions based on user and global permission settings
59 | // CfgModel and UserModel classes must be instantiated before below are called.
60 | // If readOk() returns false user is not allowed access to any pages or data.
61 | function readOk() {
62 | global $user, $gCfg;
63 | return (isset($gCfg[publicPermission]) && $gCfg[publicPermission] >= PERMISSION_READ_ONLY)
64 | || (isset($user) && userPermission() >= PERMISSION_READ_ONLY);
65 | }
66 |
67 | function modifyOk() {
68 | global $user, $gCfg;
69 | return (isset($gCfg[publicPermission]) && $gCfg[publicPermission] >= PERMISSION_READ_MODIFY)
70 | || (isset($user) && userPermission() >= PERMISSION_READ_MODIFY);
71 | }
72 |
73 | function writeOk() {
74 | global $user, $gCfg;
75 | return (isset($gCfg[publicPermission]) && $gCfg[publicPermission] >= PERMISSION_FULL)
76 | || (isset($user) && userPermission() >= PERMISSION_FULL);
77 | }
78 |
79 | function adminUser() {
80 | global $user;
81 | return (isset($user) && userPermission() >= PERMISSION_ADMIN);
82 | }
83 | function superUser() {
84 | global $user;
85 | return (isset($user) && userPermission() >= PERMISSION_SUPERUSER);
86 | }
87 |
88 | function cfgCompare($a, $b) {
89 | if(is_numeric($a) && is_numeric($b))
90 | return ($a == $b);
91 | return ($a === $b);
92 | }
93 |
94 | class CfgModel {
95 | const TABLENAME = 'cfg';
96 | public $db;
97 | public $error;
98 |
99 | function __construct($db) {
100 | global $msg, $asdbfile;
101 | $this->db = $db;
102 | // Read global cfgs
103 | $this->readCfgs();
104 | if($this->error) {
105 | pageInit();
106 | if(!empty($msg) && is_array($msg))
107 | echo implode(BR, $msg) . BR;
108 | msg("Cfg Init failed. $asdbfile may be corrupted. Try copying .bak over .db if exists or delete .db file.");
109 | asExit($this->error);
110 | }
111 | }
112 |
113 | // Read global cfgs from DB into $gCfg
114 | function readCfgs() {
115 | global $gCfg, $gCfgDef, $gCfgUpdated;
116 | $ids = implode(',', array_keys($gCfg));
117 | $where = "cfg_id IN($ids)";
118 | $cfgs = $this->getCfgs($where);
119 | if(empty($cfgs))
120 | return;
121 | foreach($cfgs as $c) {
122 | $k = $c->cfg_id;
123 | $gCfg[$k] = is_array($gCfgDef[$k]) ? explode(',', $c->val) : $c->val;
124 | //msg("Cfg $k val=" . $gCfg[$k]);
125 | $gCfgUpdated[$k] = $c->updated;
126 | }
127 | }
128 |
129 | // Save global cfgs. Caller will have updated $gCfg. Loop through cfgs, compare vals to DB & Def Vals
130 | function saveCfgs() {
131 | global $gCfg, $gCfgDef, $gCfgUpdated;
132 | $ids = array_keys($gCfg);
133 | $where = "cfg_id IN(" . arrayToCsv($ids). ")";
134 | $cfgs = $this->getCfgs($where);
135 | foreach($ids as $k) {
136 | // If val=DBval nothing to be done
137 | $val = is_array($gCfgDef[$k]) ? arrayToCsv($gCfg[$k]) : $gCfg[$k];
138 | if(isset($cfgs[$k])) {
139 | $cVal = $cfgs[$k]->val;
140 | $dbVal = is_array($gCfgDef[$k]) ? arrayToCsv($cVal) : $cVal;
141 | } else {
142 | $dbVal = null;
143 | }
144 | if(cfgCompare($val, $dbVal)) {
145 | //msg("Cfg $k val=DBval");
146 | continue;
147 | }
148 | // If val=DefVal delete from DB, else write to DB
149 | $defVal = is_array($gCfgDef[$k]) ? arrayToCsv($gCfgDef[$k]) : $gCfgDef[$k];
150 | if(cfgCompare($val, $defVal)) {
151 | //msg("Cfg $k val=defVal");
152 | if($dbVal !== null) {
153 | //msg("Deleting Cfg $k");
154 | $this->delete($k);
155 | unset($gCfgUpdated[$k]);
156 | }
157 | } else {
158 | // Add if not in DB / Update otherwise
159 | $c = (object)['cfg_id'=>$k, 'val'=>$val, 'updated'=>$gCfgUpdated[$k]];
160 | //msg("Cfg $k val!=defVal, updating");
161 | $this->update($c, !isset($cfgs[$k]));
162 | }
163 | }
164 | }
165 |
166 | private function getCfgs($where=null, $orderBy=null) {
167 | $cfgs = $this->db->getRecords(self::TABLENAME, $where, $orderBy);
168 | $this->checkDbError(__METHOD__);
169 | if(!_count($cfgs))
170 | return null;
171 | // Index by ID
172 | $a = [];
173 | foreach($cfgs as $c) {
174 | // Validate update time
175 | if($c->updated < 1672964000 || !is_numeric($c->updated))
176 | $c->updated = 0;
177 | $a[$c->cfg_id] = $c;
178 | }
179 | return $a;
180 | }
181 | private function getCfg($id) {
182 | if(!validDbID($id))
183 | return null;
184 | $where = "cfg_id='$id'";
185 | $cfgs = $this->getCfgs($where);
186 | return empty($cfgs) ? null : $cfgs[$id];
187 | }
188 | private function add($c) {
189 | return $this->update($c, true);
190 | }
191 | private function update($c, $add=false) {
192 | if(!$add && !validDbID($c->cfg_id))
193 | return null;
194 | if(!$this->validateVal($c->val))
195 | return null;
196 | $cols = ['cfg_id', 'val', 'updated'];
197 | $vals = [$c->cfg_id, $c->val, time()];
198 | if($add) {
199 | $retval = $this->db->insertRow(self::TABLENAME, $cols, $vals);
200 | } else {
201 | $retval = $this->db->updateRow(self::TABLENAME, $cols, $vals, "cfg_id=$c->cfg_id");
202 | }
203 | $this->checkDbError(__METHOD__);
204 | return $retval;
205 | }
206 | private function delete($id) {
207 | if(!validDbID($id))
208 | return null;
209 | $retval = $this->db->deleteRows(self::TABLENAME, "cfg_id=$id");
210 | $this->checkDbError(__METHOD__);
211 | return $retval;
212 | }
213 |
214 | function getCount($where=null) {
215 | $retval = $this->db->getRecordCount(self::TABLENAME, $where);
216 | $this->checkDbError(__METHOD__);
217 | return $retval;
218 | }
219 |
220 | function validateVal($c) {
221 | if(strlen($c) > 65535) {
222 | $this->error = 'Invalid Cfg Val. Must be <= 64K chars';
223 | return false;
224 | }
225 | return true;
226 | }
227 |
228 | // Do not call below prior to htmlInit(), global.inc include may cause whitespace to be output
229 | function checkGlobalInc() {
230 | global $gCfg, $subdir, $userModel;
231 | if($gCfg[call] && $gCfg[location] || !isset($userModel))
232 | return true;
233 | // If Call and Location cfgs not set try importing from ../supermon/global.inc
234 | $loc = '../supermon/global.inc';
235 | if($subdir)
236 | $loc = "../$loc";
237 | if(strpos($subdir, '/'))
238 | $loc = "../$loc";
239 | if(file_exists($loc)) {
240 | include($loc);
241 | if(!$CALL || !$LOCATION || strlen($CALL) > 9 ||
242 | !$userModel->validateName($CALL) || !$userModel->validateLocation($LOCATION)) {
243 | unset($userModel->error);
244 | return false;
245 | }
246 | $gCfg[call] = $CALL;
247 | $gCfg[location] = $LOCATION;
248 | if($TITLE2)
249 | $gCfg[title] = $TITLE2;
250 | $this->saveCfgs();
251 | return true;
252 | }
253 | return false;
254 | }
255 |
256 | private function checkDbError($method, $extraTxt='') {
257 | if(isset($this->db->error)) {
258 | if($extraTxt !== '')
259 | $extraTxt = "($extraTxt)";
260 | $this->error = $method . $extraTxt . ': ' . $this->db->error;
261 | unset($this->db->error);
262 | return true;
263 | }
264 | return false;
265 | }
266 |
267 | }
268 |
--------------------------------------------------------------------------------
/include/DB.php:
--------------------------------------------------------------------------------
1 | dbName = $dbName;
15 | }
16 |
17 | // init() is called automatically the first time a query is done
18 | function init() {
19 | if(!$this->dataSrc) {
20 | $this->dataSrc = new DataSource();
21 | if($this->dataSrc->connectToDb($this->dbName))
22 | $this->connected = true;
23 | else
24 | $this->error = $this->dataSrc->error;
25 | }
26 | return $this->connected;
27 | }
28 |
29 | function getRecords($table, $where=null, $orderBy=null, $limit=null, $select='*', $join=null, $groupBy=null) {
30 | if(!$this->init())
31 | return null;
32 | $query = "SELECT $select FROM `$table`";
33 | if($join)
34 | $query .= " $join";
35 | if($where)
36 | $query .= " WHERE $where";
37 | if($groupBy)
38 | $query .= " GROUP BY $groupBy";
39 | if($orderBy)
40 | $query .= " ORDER BY $orderBy";
41 | if($limit)
42 | $query .= " LIMIT $limit";
43 | $result = $this->dataSrc->getRecordSet($query);
44 | if(!$result) {
45 | $this->error = $this->dataSrc->error;
46 | return null;
47 | }
48 | $rows = [];
49 | while(($row = $this->fetchObject($result))) {
50 | $rows[] = $row;
51 | }
52 | return $rows;
53 | }
54 | function fetchObject($res) {
55 | $row = $res->fetchArray(SQLITE3_ASSOC);
56 | return empty($row) ? null : (object)$row;
57 | }
58 | function getRecord($table, $where=null, $orderBy=null, $select='*', $join=null) {
59 | $row = $this->getRecords($table, $where, $orderBy, 1, $select, $join);
60 | if(empty($row))
61 | return null;
62 | return $row[0];
63 | }
64 |
65 | function getRecordCount($table, $where=null) {
66 | if(!$this->init())
67 | return null;
68 | $query = "SELECT COUNT(*) AS cnt FROM `$table`";
69 | if($where)
70 | $query .= " WHERE $where";
71 | $result = $this->dataSrc->getRecordSet($query);
72 | if(!$result) {
73 | $this->error = $this->dataSrc->error;
74 | return null;
75 | }
76 | $row = $this->fetchObject($result);
77 | if(!$row)
78 | return null;
79 | return $row->cnt;
80 | }
81 | function getMaxFieldVal($table, $field, $where=null) {
82 | if(!$this->init())
83 | return null;
84 | $query = "SELECT MAX(`$field`) AS max FROM `$table`";
85 | if($where)
86 | $query .= " WHERE $where";
87 | $result = $this->dataSrc->getRecordSet($query);
88 | if(!$result) {
89 | $this->error = $this->dataSrc->error;
90 | return null;
91 | }
92 | $row = $this->fetchObject($result);
93 | if(!$row)
94 | return null;
95 | return $row->max;
96 | }
97 | function getDistinctFieldList($table, $field, $where=null, $orderBy=null) {
98 | $select = "DISTINCT `$field`";
99 | if($orderBy === null)
100 | $orderBy = "`$field` ASC";
101 | $rows = $this->getRecords($table, $where, $orderBy, null, $select);
102 | if($rows === null)
103 | return null;
104 | $list = [];
105 | foreach($rows as $li) {
106 | $list[$li->$field] = $li->$field;
107 | }
108 | return $list;
109 | }
110 |
111 | function exec($sql) {
112 | if(!$this->init())
113 | return null;
114 | if(!$this->dataSrc->exec($sql)) {
115 | $this->error = $this->dataSrc->error;
116 | return false;
117 | }
118 | return true;
119 | }
120 | // Returns id of the new object or null on error
121 | // (Check retval with '=== null' in case a new table's first id is 0)
122 | function insertRow($table, $cols, $vals) {
123 | if(!$this->init())
124 | return null;
125 | $vals = $this->dataSrc->escapeArray($vals);
126 | $cols = "`" . implode("`, `", $cols) . "`";
127 | $vals = "'" . implode("', '", $vals) . "'";
128 | $vals = $this->unquoteSqlFunctions($vals);
129 | $query = "INSERT into `$table` ($cols) values ($vals)";
130 | if(!$this->dataSrc->getRecordSet($query)) {
131 | $this->error = $this->dataSrc->error;
132 | return null;
133 | }
134 | return $this->getLastInsertId();
135 | }
136 | function updateRow($table, $cols, $vals, $where) {
137 | if(!$this->init())
138 | return null;
139 | $vals = $this->dataSrc->escapeArray($vals);
140 | for($i=0; $i < count($cols); $i++)
141 | $parms[] = "`{$cols[$i]}`='{$vals[$i]}'";
142 | $parms = implode(", ", $parms);
143 | $parms = $this->unquoteSqlFunctions($parms);
144 | $query = "UPDATE `$table` SET $parms WHERE $where";
145 | if(!$this->dataSrc->getRecordSet($query)) {
146 | $this->error = $this->dataSrc->error;
147 | return null;
148 | }
149 | return $this->dataSrc->getRowsAffected();
150 | }
151 | function deleteRows($table, $where) {
152 | if(!$this->init())
153 | return null;
154 | $query = "DELETE FROM `$table` WHERE $where";
155 | if(!$this->dataSrc->getRecordSet($query)) {
156 | $this->error = $this->dataSrc->error;
157 | return null;
158 | }
159 | return $this->dataSrc->getRowsAffected();
160 | }
161 | function getRecordSet($sql) {
162 | if(!$this->init())
163 | return null;
164 | $result = $this->dataSrc->getRecordSet($sql);
165 | if($this->dataSrc->error) {
166 | $this->error = $this->dataSrc->error;
167 | return null;
168 | }
169 | $rows = [];
170 | while(($row = $this->fetchObject($result))) {
171 | $rows[] = $row;
172 | }
173 | return $rows;
174 | }
175 |
176 | function getTableList($colName=null) {
177 | $rows = $this->getRecordSet("SELECT name FROM sqlite_master WHERE type='table'");
178 | if($this->error)
179 | return null;
180 | $a = [];
181 | foreach($rows as $r) {
182 | // If colName set, check if table contains specified col and return only those tables
183 | if($colName) {
184 | $colList = $this->getColList($r->name);
185 | if($this->error)
186 | return null;
187 | if(!in_array($colName, $colList))
188 | continue;
189 | }
190 | $a[] = $r->name;
191 | }
192 | return $a;
193 | }
194 | function getColList($table) {
195 | // Below fails on old SQLite versions
196 | //$rows = $this->getRecordSet("SELECT name FROM PRAGMA_TABLE_INFO('$table')");
197 | $rows = $this->getRecordSet("PRAGMA table_info('$table')");
198 | if($this->error)
199 | return null;
200 | $a = [];
201 | foreach($rows as $r)
202 | $a[] = $r->name;
203 | return $a;
204 | }
205 |
206 | function getValCount($table, $col, $val) {
207 | $where = "`$col`='$val'";
208 | $select = "COUNT(*)";
209 | $res = $this->getRecord($table, $where, $select);
210 | if($this->error)
211 | return null;
212 | return $res;
213 | }
214 |
215 | /* function getLastQueryCount() {
216 | if(!$this->init())
217 | return null;
218 | return $this->dataSrc->getLastQueryCount();
219 | } */
220 | function getRowsAffected() {
221 | if(!$this->init())
222 | return null;
223 | return $this->dataSrc->getRowsAffected();
224 | }
225 | function getLastInsertId() {
226 | if(!$this->init())
227 | return null;
228 | return $this->dataSrc->getLastInsertId();
229 | }
230 |
231 | function unquoteSqlFunctions($vals) {
232 | $search = array("'NOW()'", "'NULL'");
233 | $replace = array('NOW()', 'NULL');
234 | $vals = str_replace($search, $replace, $vals);
235 | return $vals;
236 | }
237 |
238 | function close() {
239 | if($this->connected) {
240 | $this->dataSrc->closeDb();
241 | $this->connected = false;
242 | }
243 | if($this->dataSrc)
244 | unset($this->dataSrc);
245 | }
246 |
247 | function __destruct() {
248 | $this->close();
249 | }
250 |
251 | }
252 |
--------------------------------------------------------------------------------
/include/DataSource.php:
--------------------------------------------------------------------------------
1 | dbName = $dbName;
14 | $this->db = new SQLite3($dbName);
15 | if(!$this->db) {
16 | $this->setError('Connect');
17 | return false;
18 | }
19 | // Wait up to 2 secs for DB file to be unlocked if in use by another client
20 | if(!$this->db->busyTimeout(2000)) {
21 | $this->setError('setBusyTimeout');
22 | return false;
23 | }
24 | return true;
25 | }
26 | function closeDb() {
27 | if(isset($this->db)) {
28 | $this->db->close();
29 | unset($this->db);
30 | $this->lastQueryCount = 0;
31 | }
32 | }
33 | function getRecordSet($sql) {
34 | if(!isset($this->db))
35 | return null;
36 | $recordSet = $this->db->query($sql);
37 | if(!$recordSet) {
38 | $this->setError("Query ($sql)");
39 | $this->lastQueryCount = 0;
40 | return null;
41 | }
42 | if(preg_match('/^(SELECT|SHOW|EXPLAIN)/', $sql, $matches) == 1) {
43 | // Apparently there's no way to get the returned row count from SQLite3
44 | // other than loop through results or do separate COUNT query
45 | //$this->lastQueryCount = $recordSet->num_rows;
46 | } else {
47 | $this->lastQueryCount = $this->db->changes();
48 | }
49 | return $recordSet;
50 | }
51 | function exec($sql) {
52 | if(!isset($this->db))
53 | return null;
54 | $res = $this->db->exec($sql);
55 | if(!$res) {
56 | $this->setError("Exec ($sql)");
57 | $this->lastQueryCount = 0;
58 | return false;
59 | }
60 | $this->lastQueryCount = $this->db->changes();
61 | return true;
62 | }
63 | /* function getLastQueryCount() {
64 | return $this->lastQueryCount;
65 | } */
66 | function getRowsAffected() {
67 | return $this->lastQueryCount;
68 | }
69 | function getLastInsertId() {
70 | return $this->db->lastInsertRowID();
71 | }
72 |
73 | function escapeString($string) {
74 | $escaped = $this->db->escapeString($string);
75 | if(!$escaped)
76 | return $string;
77 | return $escaped;
78 | }
79 | function escapeArray($arr) {
80 | foreach($arr as &$string)
81 | $string = $this->escapeString($string);
82 | return $arr;
83 | }
84 |
85 | private function setError($errTypeStr) {
86 | $this->error = "Error($errTypeStr) errno " . $this->db->lastErrorCode() . ", desc " . $this->db->lastErrorMsg();
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/include/Html.php:
--------------------------------------------------------------------------------
1 | \n");
6 | define('NBSP', ' ');
7 | define('EMSP', ' ');
8 | define('ENSP', ' ');
9 | function nbsp($count=1) { $s=''; for($n=0;$n<$count;$n++) $s.= NBSP; return $s; }
10 | function htmlspecial($txt) { // Does not escape quotes. Do not use for attributes
11 | return htmlspecialchars($txt, ENT_NOQUOTES | ENT_HTML5 | ENT_IGNORE, 'UTF-8', false);
12 | }
13 | function htmlattr($txt) { // Escape an attribute (no double quotes)
14 | return htmlspecialchars($txt, ENT_COMPAT | ENT_HTML5 | ENT_IGNORE, 'UTF-8', false);
15 | }
16 |
17 | class Html
18 | {
19 | var $checkBoxId=1;
20 | function tag($tag, $txt, $class=null, $escapeTxt=false, $style=null, $id=null) {
21 | $out = "<$tag";
22 | if($id)
23 | $out .= " id=\"$id\"";
24 | if($class)
25 | $out .= " class=\"$class\"";
26 | if($style)
27 | $out .= " style=\"$style\"";
28 | $out .= '>';
29 | $out .= ($escapeTxt ? htmlspecial($txt) : $txt);
30 | $out .= "$tag>";
31 | return $out;
32 | }
33 | function span($txt, $class=null, $escapeTxt=false, $style=null) {
34 | return $this->tag('span', $txt, $class, $escapeTxt, $style);
35 | }
36 | function h($num, $txt, $class=null, $escapeTxt=true) {
37 | return $this->tag("h$num", $txt, $class, $escapeTxt) . NL;
38 | }
39 | function pre($txt, $class=null, $escapeTxt=false, $style=null) {
40 | return $this->tag('pre', $txt, $class, $escapeTxt, $style) . NL;
41 | }
42 | function div($txt, $class=null, $id=null, $style=null) {
43 | return $this->tag('div', $txt, $class, false, $style, $id) . NL;
44 | }
45 | function header($txt, $class=null) {
46 | return $this->tag('header', $txt, $class) . NL;
47 | }
48 | function article($txt, $class=null) {
49 | return $this->tag('article', $txt, $class) . NL;
50 | }
51 | function p($txt, $class=null, $escapeTxt=true, $id=null) {
52 | return $this->tag('p', $txt, $class, $escapeTxt, null, $id) . NL;
53 | }
54 | function a($url, $parms=null, $title=null, $class=null, $target=null, $escapeTitle=true, $onclick=null, $style=null) {
55 | $props = [];
56 | $props[] = "$title";
75 | return $out;
76 | }
77 | // build a url from base url and parms array, format for JS ('&' not escaped)
78 | function buildUrl($url, $parms) {
79 | if(isset($url) && strlen($url)) {
80 | if(is_array($parms))
81 | $url .= "?". http_build_query($parms);
82 | }
83 | return $url;
84 | }
85 | function br($num=1) {
86 | for($i=0; $i < $num; $i++)
87 | $out .= BR;
88 | return $out;
89 | }
90 | function nbsp($num=1) {
91 | for($i=0; $i < $num; $i++)
92 | $out .= NBSP;
93 | return $out;
94 | }
95 | function img($url, $alt='', $class=null, $width=null, $height=null) {
96 | $out = "$text\n";
112 | return $out;
113 | }
114 | // FORMS
115 | function formOpen($scriptName=null, $method='post', $id=null, $class=null, $target=null) {
116 | if(!$scriptName)
117 | $scriptName = getScriptName();
118 | if($id)
119 | $id = " id=\"$id\"";
120 | if($class)
121 | $class = " class=\"$class\"";
122 | if($target)
123 | $target = " target=\"$target\"";
124 | return "
" . $this->escape($col, $escFunc) . " | \n"; 347 | $out .= "
---|
xxx
: htmlspecial() for non-html contents, none otherwise 430 | - Within a property e.g. alt="xxx": htmlattr(). Properties cannot contain html or double-quotes 431 | - Within a url: rawurlencode(). 432 | */ 433 | function escape($txt, $escFunc=null) { 434 | static $func = ['htmlspecial', 'htmlentities', 'urlencode', 'rawurlencode']; 435 | if($escFunc === null or $escFunc >= count($func)) 436 | return $txt; 437 | return $func[$escFunc]($txt); 438 | } 439 | function hr($width="100%") { 440 | return "' . $title2 . ' | |||||
---|---|---|---|---|---|
Node | Node Info | Received | Dir | Connected | Mode |
Waiting... |