Compare commits

..

313 commits

Author SHA1 Message Date
billz
c3175459ab Update release version 2025-04-17 05:44:30 -07:00
Bill Zimmerman
3bf682d752
Merge pull request #1811 from RaspAP/feat/csrf-tokenizer
Feature: CSRF tokenizer class
2025-04-17 14:32:58 +02:00
Bill Zimmerman
9e0801aa0c
Merge pull request #1825 from RaspAP/fix/custom-blocklists
Fix: Check for presence of custom blocklist
2025-04-17 10:07:12 +02:00
billz
a21b0a8f99 Check for presence of custom blocklist in RASPI_ADBLOCK_LISTPATH 2025-04-16 23:33:13 -07:00
billz
5f1b16bc74 Remove instantiation of $token 2025-03-26 11:19:00 -07:00
billz
deba5e1e74 When session token expires, redirect instead of returning a 500 error 2025-03-26 09:51:39 -07:00
billz
0960e8bac9 Add CSRF protection include 2025-03-26 04:05:39 -07:00
billz
4a4506a913 Update with CSRF::hiddenField() 2025-03-26 04:03:37 -07:00
billz
bfab3d7441 Revise CSRFValidate() 2025-03-26 04:02:48 -07:00
billz
47d7c121de Generate new session id on logout() 2025-03-26 04:01:09 -07:00
billz
484b89718a Add validateRequest(), token check outside class 2025-03-26 04:00:34 -07:00
billz
d6c8ac32a7 Fix call to class method 2025-03-25 14:05:34 -07:00
billz
3d6b4e1f15 Simplify getVpnManaged check with ternary operator 2025-03-25 14:04:29 -07:00
billz
8d482845b0 Replace explicit token instantiation w/ includes/CSRF.php 2025-03-25 14:02:51 -07:00
billz
a1550d8049 Refactor templates to use CSRF facade 2025-03-25 14:00:24 -07:00
billz
5584e3b72c Initial commit 2025-03-25 13:58:52 -07:00
billz
b0ba029c66 Revert "Accept $token object, pass to renderTemplate()"
This reverts commit b3c6178274.
2025-03-25 13:23:58 -07:00
billz
48e492bf10 Ensure a CSRF token exists in session 2025-03-25 06:49:18 -07:00
billz
821ac9c1f8 Numerous fixes to class methods 2025-03-25 05:26:53 -07:00
billz
49cb3911b8 Fixup misnamed method getVpnManged -> getVpnManaged 2025-03-25 05:25:11 -07:00
billz
8ad582b3b2 Instantiate ExceptionHandler, CSRFTokenizer objects 2025-03-25 05:23:52 -07:00
billz
bbf1caf777 Update with $token->CSRFTokenFieldTag() 2025-03-25 05:23:06 -07:00
billz
b3c6178274 Accept $token object, pass to renderTemplate() 2025-03-25 05:22:29 -07:00
billz
a5907d8f7f Class loading handled by autoloader, objects instantiated by index.php 2025-03-25 05:21:35 -07:00
billz
8569c2b4d5 Remove CSRF related functions (made obsolete by Token class) 2025-03-25 05:17:58 -07:00
billz
2a70f6ee11 Replace csrf and explicit HTTPAuth includes with autoload.php 2025-03-25 05:15:34 -07:00
billz
c8b0408bd5 Refactor + fixup autoloader 2025-03-25 05:14:29 -07:00
Bill Zimmerman
125ae7a39a
Merge pull request #1810 from RaspAP/fix/php74-compatibility
Fix: php7.4 compatibility
2025-03-25 08:36:03 +01:00
billz
20fe5fc5a7 Fix: php7.4 compatibility 2025-03-24 23:58:30 -07:00
billz
168ed2448f Update release version 2025-03-24 01:51:08 -07:00
Bill Zimmerman
a2b8dfe551
Merge pull request #1806 from RaspAP/fix/fetch-update-response
Fix: Revise fetchUpdateResponse() and modal dialog
2025-03-24 09:45:33 +01:00
billz
4aee1e49d9 Revise fetchUpdateResponse() without endPolling flag 2025-03-24 01:37:16 -07:00
billz
16d92bb486 Set fa-check visible for updateStep1 2025-03-24 01:35:40 -07:00
billz
de376d04d1 Initial commit 2025-03-23 23:13:29 -07:00
Bill Zimmerman
d730c174d4
Update README.md 2025-03-24 06:29:58 +01:00
billz
c798f5fd69 Update BACKERS.md 2025-03-23 22:28:03 -07:00
Bill Zimmerman
3a73916206
Merge pull request #1805 from RaspAP/fix/connection-type
Update getConnectionType() regexes to match MAC-derived names
2025-03-24 06:24:33 +01:00
billz
f46be0139e Update getConnectionType() regexes to match MAC-derived names 2025-03-23 10:52:46 -07:00
Bill Zimmerman
a6295aef6e
Merge pull request #1799 from RaspAP/feat/dashboard-devices
Feature: Dashboard + System device illustrations
2025-03-23 17:51:20 +01:00
Bill Zimmerman
556c2be855
Merge pull request #1802 from RaspAP/fix/dhcp-settings-focus
Fix: Links to dhcp client-list tab
2025-03-23 17:51:05 +01:00
Bill Zimmerman
80e8178384
Update issue_form.yml 2025-03-23 17:13:44 +01:00
billz
b1c429f404 Fix: remove link to client-list tab 2025-03-23 08:45:21 -07:00
Bill Zimmerman
f0b992b9be
Merge pull request #1800 from RaspAP/fix/band-association
Fix: Update 802.11n band associations
2025-03-23 15:23:45 +01:00
billz
084b2e1268 Update 802.11n bands, resolves #1797 2025-03-23 07:22:15 -07:00
billz
0fd60e3730 Update device illustration w/ w/ $deviceImage 2025-03-23 07:18:55 -07:00
billz
cfc6644087 Merge branch 'master' into feat/dashboard-devices 2025-03-23 07:08:04 -07:00
Bill Zimmerman
2cd4abc3c2
Merge pull request #1798 from RaspAP/feat/tabbed-dashboard
Feature: Tabbed dashboard
2025-03-23 15:01:12 +01:00
Bill Zimmerman
8bd5b1b988
Merge pull request #1796 from RaspAP/feat/auth-enhancements
Feature: Auth enhancements
2025-03-23 15:00:57 +01:00
billz
2b8f7fd6d8 Initial commit 2025-03-23 02:18:50 -07:00
billz
b99addef4a Replace static device illustration w/ $deviceImage 2025-03-23 01:30:32 -07:00
billz
96ada80ce1 Update RPi models from latest revision codes 2025-03-23 01:28:51 -07:00
billz
5d33c79369 Return default if revision type not matched 2025-03-23 01:25:45 -07:00
billz
645ab89437 Update w/ getDeviceImage() 2025-03-23 01:22:54 -07:00
billz
62b342cdbb Set device image w/ getDeviceImage($revision) 2025-03-23 01:22:26 -07:00
billz
c235a4ff16 Create zero.php, move under /devices 2025-03-23 01:21:28 -07:00
billz
a24516a4d5 Tweak divDBChartBandwidthhourly 2025-03-23 00:33:27 -07:00
billz
483b1fc27d Split single template into tabs 2025-03-23 00:28:31 -07:00
billz
4d0de82986 Add &$extraFooterScripts to support data usage graph 2025-03-23 00:27:39 -07:00
billz
85663341eb Initial commit 2025-03-23 00:26:48 -07:00
billz
734043dee6 Define button object, add logout btn 2025-03-22 03:17:23 -07:00
billz
af2927f05b Update w/ qr code doc link 2025-03-22 03:16:53 -07:00
billz
3e54b1d7bb Remove authentication sidebar item (redundant) 2025-03-22 03:14:11 -07:00
billz
51a0ce220c Update w/ disableValidation() to support logout btn 2025-03-22 03:12:46 -07:00
billz
05e20e3bab Handle logout action 2025-03-22 03:12:09 -07:00
billz
03fc2c42ad Update w/ logout() method 2025-03-22 03:11:47 -07:00
billz
401172eb36 Initial commit 2025-03-22 03:11:03 -07:00
Bill Zimmerman
71f1132bc8
Update plugin_helper.sh 2025-03-20 18:50:02 +01:00
Bill Zimmerman
605486feda
Update w/ strict check on config destination path 2025-03-20 18:08:29 +01:00
Bill Zimmerman
23597e800d
Fix: Revert set permissions on $destination 2025-03-20 12:05:30 +01:00
Bill Zimmerman
3218d87b1b
Update dashboard screenshot 2025-03-20 11:13:32 +01:00
billz
0c58a6c92c Update release version 2025-03-20 03:11:31 -07:00
billz
afa3006de2 Minor: code cleanup 2025-03-20 02:54:30 -07:00
Bill Zimmerman
03b9bf9e7a
Merge pull request #1793 from RaspAP/maint/php-warnings
Resolves numerous PHP warnings
2025-03-20 10:35:58 +01:00
billz
028c0d3e06 Move template handling of wg_log to include/wireguard 2025-03-20 02:12:47 -07:00
billz
0005488884 Fix: Use null coalescing operator on $_POST object, fetch wg_log, set $peer_id 2025-03-20 02:12:00 -07:00
billz
f73f25708c Fix: Cleanup, resolve warnings in getWifiInterface() 2025-03-20 02:10:40 -07:00
billz
4e411aaa6b Fix: Use null coalescing operator on user_id 2025-03-20 02:09:39 -07:00
billz
6adeab7586 Fix: Evaluate HTTP_ACCEPT_LANGUAGE, check ['locale'] 2025-03-20 02:08:54 -07:00
billz
16f6b7f979 Fix numerous php warnings for undefined vars 2025-03-20 02:07:51 -07:00
billz
068f363f09 Remove obsolete toggleState functions, add file_exists() check 2025-03-20 02:06:11 -07:00
billz
d4554c6429 Update w/ getBridgedState() global function 2025-03-20 02:05:11 -07:00
billz
a3caa6485c Fix: Use null coalescing operator to prevent warnings 2025-03-20 02:04:40 -07:00
billz
795d55a2cd Fix: Sanitize $peer_conf before output to header 2025-03-20 02:04:00 -07:00
billz
c53c1a27a4 Fix: Specify global Exception class by adding backslash 2025-03-20 02:02:35 -07:00
Bill Zimmerman
903cc6bd8e
Merge pull request #1787 from RaspAP/maint/plugin-installer-keys
Updates PluginInstaller to handle signed third-party packages
2025-03-20 08:11:08 +01:00
Bill Zimmerman
13929acbd1
Merge pull request #1775 from RaspAP/fix/plugin-helper-config
Set permissions on parent directory when handling config files
2025-03-20 08:04:40 +01:00
Bill Zimmerman
f6da130fce
Merge pull request #1792 from RaspAP/fix/installer-submodules
Updates plugin submodules after cloning parent repo
2025-03-20 08:01:10 +01:00
billz
1fc793c3fd Exec git submodule update --remote plugins on $source_dir 2025-03-19 12:00:07 -07:00
Bill Zimmerman
4b6ac1a415
Merge pull request #1748 from RaspAP/feat/dashboard-redesign
Dashboard redesign
2025-03-19 17:02:48 +01:00
billz
304010db40 Update client lease ouput w/ human-readable timestamps 2025-03-18 12:28:06 -07:00
billz
66563c9d95 Update hrefs w/ tab param to load specific tabs on page load 2025-03-18 12:11:50 -07:00
billz
5eca4c045b Add event listener to activate tab on page load 2025-03-18 12:10:47 -07:00
billz
fd953e7a71 Remove obsolete svg file (replaced by device.php) 2025-03-18 08:56:32 -07:00
billz
52b20cb491 Revise getEthernetClients() w/ more robust method 2025-03-18 08:55:35 -07:00
billz
c987d2800d Fix php warning: define $firewallManaged 2025-03-18 08:54:47 -07:00
billz
6d6bacd6a1 Update en_US locale msgs + compile .mo 2025-03-18 04:12:34 -07:00
billz
f0a0c9228f Replace static labels w/ gettext() for locale support 2025-03-18 04:11:47 -07:00
billz
dfef9e5233 Define firewallManaged when Firewall plugin is installed 2025-03-18 03:23:25 -07:00
billz
ab77af9e5d Update firewallEnabled() config check 2025-03-18 03:22:03 -07:00
billz
5ce06c6214 Revise dashed svg stroke width, color 2025-03-18 03:20:46 -07:00
billz
d5cc80d1f2 Update en_US locale w/ new dashboard msgs + compile .mo 2025-03-18 02:15:09 -07:00
billz
e3f04192b8 Replace static labels w/ gettext() for locale support 2025-03-18 02:14:02 -07:00
billz
cfb8435373 Revise formatClientLabel() w/ ngettext 2025-03-18 02:13:14 -07:00
billz
615f2abed1 Minor: formatting 2025-03-18 01:45:56 -07:00
billz
ac926e84d7 Style tweak for fa-stack status-item 2025-03-18 01:45:38 -07:00
billz
8846b96905 Update function mappings for absolute paths 2025-03-18 01:45:10 -07:00
billz
677e99ffd9 Update w/ relevant href links, indicate active/inactive states 2025-03-18 00:57:04 -07:00
billz
a82b30179b Add device, clients active/inactive states 2025-03-18 00:55:56 -07:00
billz
ae4e9be739 Change default SSID to RaspAP (finally) 2025-03-18 00:53:59 -07:00
billz
7091a4016a Update media query to reflect markup changes, add .inactive 2025-03-18 00:53:29 -07:00
billz
24b292bf33 Replace static color values w/ --raspap-* vars 2025-03-17 11:42:20 -07:00
billz
c00f006cdd Migrate inline functions to class methods in RaspAP\UI 2025-03-17 11:41:22 -07:00
billz
ed47a41c9c Initial commit 2025-03-17 11:38:17 -07:00
billz
39cc92853a Set z-index for solid + dashed-lines 2025-03-17 10:32:32 -07:00
billz
34f7563bed Replace static placeholder with renderClientConnections 2025-03-17 10:25:04 -07:00
billz
964d7b38a8 Create getConnectionIcon(), renderClientConnections() 2025-03-17 10:24:32 -07:00
billz
17fbbca046 Create renderConnection(), update template 2025-03-17 07:33:37 -07:00
billz
64faf296a6 Apply dynamic theme color w/ rendered svg lines 2025-03-17 07:32:24 -07:00
billz
f1c404a443 Rename file to device.php 2025-03-17 07:09:10 -07:00
billz
5ed1312406 Revise getEthernetClients() w/ more robust method comparing arp + dhcp 2025-03-17 07:08:29 -07:00
billz
00c18451cc Replace static svg w/ device.php 2025-03-17 07:07:25 -07:00
billz
b737b5c748 Use dynamic theme color w/ device svg 2025-03-17 07:06:21 -07:00
billz
d852990314 Replace var raspap-brand-color w/ raspap-theme-color 2025-03-17 07:05:03 -07:00
billz
182073f41e Update placeholder values w/ template data + active route states 2025-03-17 06:04:02 -07:00
billz
64d3a11866 Create getConnectionType(), getWirelessClients() getEthernetClients() 2025-03-17 06:02:48 -07:00
billz
4865e85655 Various minor tweaks 2025-03-17 04:07:27 -07:00
billz
3d6095d652 Define buttons w/ ob_start(), network details block + wifi bands 2025-03-17 04:07:05 -07:00
billz
bd0e379d01 Create getActiveVpnInterface(), update revision strings 2025-03-17 04:05:23 -07:00
billz
93395a8aa5 Refactor DisplayDashboard() 2025-03-17 04:04:27 -07:00
billz
339437f47f Set hostapd_led to warn when status is down 2025-03-17 03:28:32 -07:00
billz
80db7edf18 Compatibility fix for php 7.4 2025-03-16 12:38:22 -07:00
Bill Zimmerman
41e86a9e51
Merge pull request #1789 from RaspAP/fix/installer-update
Fix for automated version update on About page
2025-03-16 14:01:59 +01:00
billz
ef04678947 Get installed plugins, add status checks for services 2025-03-16 03:33:10 -07:00
billz
1a964a283f Replace static hex values w/ css var, style tweaks 2025-03-16 03:30:26 -07:00
billz
6c47375d18 Create adBlockStatus() 2025-03-16 03:29:19 -07:00
billz
c2abce1e35 Add -p flag to mkdir in _create_plugin_scripts() 2025-03-16 01:11:21 -07:00
billz
b4b715f6e4 Add --check 0 option for update 2025-03-16 01:10:26 -07:00
billz
8a7c954d88 Cleanup interface statistics code (obsolete) 2025-03-16 01:04:48 -07:00
billz
639f7605d1 Template tweaks, update labels w/ gettext 2025-03-16 01:01:23 -07:00
billz
fbcf9809c5 Update hotspot icon > fa-bullseye 2025-03-15 16:00:27 -07:00
billz
2474765820 Add device illustration to system basic tab 2025-03-15 15:18:24 -07:00
Bill Zimmerman
ba4507bd22
Merge branch 'master' into feat/dashboard-redesign 2025-03-15 19:54:34 +01:00
billz
2610c44ac9 Adjust stroke-width, remove rect w/ fill for dark mode support 2025-03-15 11:34:49 -07:00
billz
f608282aa5 Define --raspap-text-light, replace hex colors w/ vars, tweaks 2025-03-15 11:33:03 -07:00
billz
781f376bea Standardize revision descriptions 2025-03-15 11:31:59 -07:00
Bill Zimmerman
6e1c3b95c2 Revise dashed .svg files 2025-03-15 17:44:31 +01:00
billz
0331eb7b25 Minor: suppress debug output from key install 2025-03-15 00:59:36 -07:00
billz
c3a210907a Throw exception for invalid key structure, update messages 2025-03-15 00:58:53 -07:00
billz
3e0f1f16c1 Revise installRepositoryKeys() w/ array of key data 2025-03-14 11:37:47 -07:00
billz
c2be25271b Update w/ signed packages from manifest 2025-03-14 11:36:17 -07:00
billz
bb131a7f53 Revise key handling with parameters for url, keyring, repo + list 2025-03-14 11:35:30 -07:00
billz
c753865305 Added handling for GPG keys to enable third-party apt repos 2025-03-14 03:36:43 -07:00
billz
0bac7deccc Define helperScriptPath, create installRepositoryKeys() 2025-03-14 03:35:32 -07:00
Bill Zimmerman
9ada9f9e68
Merge pull request #1777 from RaspAP/maint/php-warnings
Fix PHP warning in getProviderValue()
2025-03-08 10:09:35 +00:00
billz
02b9e20ce3 Use ?? to avoid undefined key warnings 2025-03-02 00:24:57 -08:00
billz
3c8cf996b5 Set permissions on parent directory 2025-03-02 00:10:43 -08:00
Bill Zimmerman
313e2eb06f
Update issue_form.yml 2025-03-01 08:18:54 +01:00
Bill Zimmerman
41fcbba5cc
Update README.md 2025-02-23 14:30:10 +01:00
Bill Zimmerman
ce9916a792
Update README.md 2025-02-23 14:21:58 +01:00
Bill Zimmerman
3f830458b1
Update issue_form.yml 2025-02-23 09:32:02 +01:00
billz
a8e24b7629 Update release version 2025-02-21 09:07:12 -08:00
billz
80f5cb5f46 Set plugins submodule to track master 2025-02-21 05:25:59 -08:00
billz
d45aa752c6 Set plugins submodule to track master 2025-02-21 05:16:45 -08:00
billz
2a1e39a880 Update release version 2025-02-21 05:03:10 -08:00
Bill Zimmerman
8f402ab303
Merge pull request #1763 from RaspAP/maint/plugin-installer
PluginInstaller improvements + fixes
2025-02-21 09:16:29 +01:00
Bill Zimmerman
644d2fe6cb
Update BACKERS.md 2025-02-21 08:02:56 +01:00
billz
b3fe781c19 Prepend this->rootPath if not absolute path 2025-02-19 06:03:07 -08:00
billz
69510a4e92 Set owner after copying dependencies 2025-02-19 06:00:32 -08:00
billz
163d727ee1 Fix: re-add copyPluginFiles() 2025-02-19 01:47:20 -08:00
billz
20fdd9024d Added safeOutputValue() 2025-02-19 01:25:07 -08:00
billz
23e0a6601a Fix: replace dirname var w/ destination param 2025-02-19 01:06:57 -08:00
billz
47d0305bec Revise installPlugin() to use plugins-available, fix copyJavaScriptFiles 2025-02-19 01:05:51 -08:00
billz
f28c5533bc Update pluginConfirm btn actions 2025-02-19 01:02:56 -08:00
billz
b89fe7c6b8 Set install_path, revise parameters used w/ installPlugin() 2025-02-19 01:02:04 -08:00
Bill Zimmerman
71ed223cf5
Merge pull request #1764 from juliovegap/master
Bug #1762
2025-02-19 08:47:52 +01:00
billz
335491124f Add install_path to ajax handler, various fixes + improvements 2025-02-18 09:00:49 -08:00
billz
e45c00b83c Create getRepository(), extend to installed & available plugins 2025-02-18 08:57:04 -08:00
billz
aa5fcef2d4 Add additional field to template, change source -> doc link 2025-02-18 08:55:52 -08:00
billz
01ddf6f4ea Update plugin details w/ javascript, default to none 2025-02-18 01:16:16 -08:00
juliovegap
bbac9a8802
Bug #1762
New versions of 'iw phy#0 info' provides frequencies output with decimals #1762. IW Parser patched to handle this new output format
2025-02-17 20:20:13 +01:00
billz
4374e78bbb Add JavaScript handling to installPlugin(), fix bug w/ manifest parsing 2025-02-17 02:35:42 -08:00
billz
6e0cf0b085 Update w/ javascript support 2025-02-17 02:34:20 -08:00
Bill Zimmerman
867a46bee9
Merge pull request #1757 from RaspAP/maint/version-motd
Fetch RaspAP version and set MOTD
2025-02-13 08:07:02 +01:00
Bill Zimmerman
4a89cb3b07
Update README.md 2025-02-13 07:59:38 +01:00
Bill Zimmerman
dc86f15b59
Update README.md 2025-02-12 23:45:26 +01:00
Bill Zimmerman
91c535ddbb
Update README.md 2025-02-12 23:41:33 +01:00
Bill Zimmerman
f576b02858
Update README.md 2025-02-12 23:20:49 +01:00
billz
eb27ba2c66 Fetch RaspAP version and set MOTD 2025-02-12 08:30:33 -08:00
billz
362a08c00f Update release version 2025-02-09 00:54:27 -08:00
Bill Zimmerman
43ae90bd34
Merge pull request #1754 from RaspAP/maint/release-naming
Update naming to reflect clarity in arch, OS + purpose
2025-02-09 09:51:25 +01:00
billz
c34cbeca8d Exec raspi-config noint do_wifi_country 2025-02-09 00:46:27 -08:00
billz
33f66fdae5 Update naming to reflect clarity in arch, OS + purpose 2025-02-08 10:52:26 -08:00
Bill Zimmerman
cc9720e8ef
Merge pull request #1752 from RaspAP/maint/installer-option
Adds -k --check option to install loader
2025-02-08 13:02:34 +01:00
Bill Zimmerman
74b1b10e31
Merge pull request #1753 from RaspAP/maint/release-check-flag
Specify --check 0 to skip connectivity check
2025-02-08 13:02:01 +01:00
billz
41ce72775e Set default repo at top of _main() 2025-02-08 01:41:43 -08:00
billz
9876ab1e3e Specify --check 0 to skip connectivity check 2025-02-08 01:22:52 -08:00
billz
bd0b226f61 Adds -k --check option to install loader 2025-02-08 01:18:47 -08:00
billz
3417115a84 Update release version 2025-02-08 00:11:43 -08:00
billz
7a880d563f Add _create_plugin_scripts to _update_raspap() 2025-02-07 23:48:30 -08:00
Bill Zimmerman
32e191e55d
Merge pull request #1717 from RaspAP/feat/plugin-manager
Plugin Manager UI
2025-02-08 08:31:07 +01:00
Bill Zimmerman
58501a74a7
Merge pull request #1750 from neo773/bs-redeisgn
feat: Dashboard redesign
2025-02-07 10:29:37 +01:00
neo773
80c1a04797
feat: dashboard redesign 2025-02-07 13:41:23 +05:30
billz
af3abe66f4 Initial commit 2025-02-06 02:44:37 -08:00
billz
8a8be213f7 Display plugins in monitor mode, suppress details dialog 2025-02-04 23:45:06 -08:00
billz
0efbe2b326 Define RASPI_PLUGINS_ENABLED, add condition to template 2025-02-04 23:44:04 -08:00
Bill Zimmerman
624e43f954
Merge pull request #1747 from rjdjohnston/fix/increase-curl-timeout
fix: Increase curl max-time to 15 seconds for connectivity check
2025-02-04 08:12:26 +01:00
RJ Johnston
55b6c81eca fix: Increase curl max-time to 15 seconds for connectivity check 2025-02-03 18:31:59 -05:00
Bill Zimmerman
aa49dcf32c
Merge pull request #1740 from RaspAP/feat/install-loader-check
Adds _check_internet() to install loader
2025-02-03 11:52:27 +01:00
Bill Zimmerman
10d5c4a8a6
Merge pull request #1745 from RaspAP/fix/armbian-dependencies
Install ARM 64- or 32-bit isoquery from Debian pkg (Armbian)
2025-02-03 11:51:15 +01:00
billz
2f205fe9e4 Install ARM 64- or-32 bit isoquery from Debian pkg (Armbian) 2025-02-03 01:33:32 -08:00
billz
6b7b8ef8d0 Refine API response condition 2025-02-03 00:20:03 -08:00
billz
a9804b2f9c Update _get_release() w/ API rate limit and undefined release var check 2025-02-02 03:08:35 -08:00
Bill Zimmerman
484d40f455
Update issue_form.yml 2025-02-01 19:31:47 +01:00
billz
18114df2bd Define component 2025-01-30 00:39:16 -08:00
billz
e61dac332c Add _check_internet() function. Resolves #1729 2025-01-30 00:08:59 -08:00
Bill Zimmerman
b9842b5462
Merge pull request #1739 from RaspAP/fix/debug-log
Fix quoting for dnsmasq_info()
2025-01-30 08:23:56 +01:00
billz
739057c7ac Fix quoting for dnsmasq_info(). Resolves #1738 2025-01-29 23:19:28 -08:00
Bill Zimmerman
d689024c1f
Merge pull request #1690 from Dhanus3133/ci/build-images
ci: build 32 bit and 64 bit images
2025-01-28 14:34:17 +01:00
Dhanus
c1975a78a1 chore: Use pi-gen fork 2025-01-28 15:29:42 +05:30
billz
5fbafeb455 Re-add plugins submodule 2025-01-27 04:49:31 -08:00
billz
7def2d6da1 Remove plugins/ to allow submodule usage 2025-01-27 04:43:01 -08:00
billz
538e37ceb7 Add --recurse-submodules to git clone operation 2025-01-27 04:29:07 -08:00
Bill Zimmerman
5d01fa59b1
Fix for DOM text reinterpreted as HTML
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-01-26 11:50:33 +01:00
billz
0b0f4bc06d Update plugin installed check 2025-01-26 02:45:47 -08:00
Bill Zimmerman
a619e7a25b
Merge branch 'master' into feat/plugin-manager 2025-01-26 10:33:49 +01:00
billz
a8bd85cc80 Create textarea.plugin-log class 2025-01-26 01:32:18 -08:00
billz
e423b7f4d3 Display diagnostic error message on plugin install fail 2025-01-26 01:31:40 -08:00
billz
47c509277c Add checks for empty vars, improve error propagation 2025-01-26 01:25:12 -08:00
billz
a6fdb63dd2 Catch HTTP 442 unprocessable content response 2025-01-26 01:23:42 -08:00
billz
75bb3e4a34 Resolve conflict: update .mo from master + ignore 2025-01-25 09:48:58 -08:00
billz
36c3e5036f Ignore compiled .mo files 2025-01-25 09:42:17 -08:00
billz
a9021c89f4 Refactor getUserPlugins(), define pluginsManifest 2025-01-25 02:49:48 -08:00
billz
77f183817a Replace try/catch callback w/ simplified JSON parsing 2025-01-25 02:48:52 -08:00
billz
97bc8174b4 Add include/session.php 2025-01-25 02:47:46 -08:00
billz
83ed9b5c19 Remove RASPI_PLUGINS_URL (obsolete) 2025-01-25 02:47:03 -08:00
billz
0871601b31 Initial commit 2025-01-25 02:46:34 -08:00
billz
eb25142e03 Update release version 2025-01-22 23:52:05 -08:00
billz
42b6c8fce3 Fix: correctly evaluate session status 2025-01-22 07:38:36 -08:00
Bill Zimmerman
7a9e5169f6
Merge pull request #1735 from RaspAP/feat/session-manager
Feature: Session manager & dedicated login
2025-01-22 15:11:30 +01:00
billz
2ca7448bd3 Define RASPI_BRAND_TITLE, replace static title 2025-01-22 00:46:00 -08:00
billz
cd3fde71e3 Revise login background gradient 2025-01-22 00:35:52 -08:00
billz
ec0dd304ee Add csrf token check, destroy session on timeout 2025-01-22 00:11:22 -08:00
billz
50ed5f9f4b Update en_US locale w/ new msgs, compile .mo 2025-01-21 05:36:13 -08:00
billz
259fac717f Update static label with RASPI_BRAND_TEXT 2025-01-21 05:35:34 -08:00
billz
0fd65fdbc2 Minor: mode change 2025-01-21 05:23:42 -08:00
billz
2b92d028f6 Minor: define status + redirectUrl vars 2025-01-21 05:23:19 -08:00
billz
9c4f8be363 Fix: include session 2025-01-21 05:22:22 -08:00
billz
21b9feb0ef Refactor auth method: http basic > user login 2025-01-21 05:14:18 -08:00
billz
bc7d4ef1c1 Include session in ajax handlers 2025-01-21 04:59:57 -08:00
billz
bc23dfc130 Remove timeout_duration from response 2025-01-21 04:55:56 -08:00
billz
9042ee8c01 Pass redirectUrl as action to login 2025-01-21 00:38:03 -08:00
billz
effcb5e48e Add global styles for login page 2025-01-21 00:35:27 -08:00
billz
111c9581a3 Resolve hash_equals() error w/ expired csrf_token, add login redirect 2025-01-21 00:33:24 -08:00
billz
6cb0be96b4 Set data-bs-backdrop + data-bs-keyboard attributes 2025-01-21 00:31:49 -08:00
billz
6dbdf89760 Add DisplayLogin() page handler 2025-01-21 00:30:40 -08:00
billz
792ce0c956 Include login.php, buffer page output 2025-01-21 00:29:59 -08:00
billz
3f883a70de Initial commit 2025-01-21 00:29:14 -08:00
billz
187041b030 Add checkSession(), showSessionExpiredModal() 2025-01-20 00:58:18 -08:00
billz
c51b520b8d Decouple session from csrf handler, set initial lastActivity 2025-01-20 00:55:21 -08:00
billz
866d8eb5b0 Update w/ sessionTimeoutModal, lastActivity 2025-01-20 00:53:26 -08:00
billz
3cf22a9cbb Define RASPI_SESSION_TIMEOUT 2025-01-20 00:52:28 -08:00
billz
6f1c34f28d Initial commit 2025-01-20 00:51:00 -08:00
Bill Zimmerman
41a138026b
Merge pull request #1733 from RaspAP/fix/dhcp-ignore
Set dhcp-ignore=tag:!known from option switch
2025-01-19 09:55:57 +01:00
billz
0c31b5ba71 Set dhcp-ignore=tag:!known from option switch 2025-01-19 00:23:06 -08:00
billz
2bab11b951 Update callbackTimeout interval, error msg 2025-01-19 00:18:56 -08:00
billz
b52bd84ea4 Add msgs to en_US locale + compile .mo 2025-01-05 02:15:16 -08:00
billz
ff7e674b2e Use callbackTimeout w/ getUserPlugins(), handle err if >2000ms 2025-01-05 02:14:16 -08:00
billz
3e91f50966 Create callbackTimeout() 2025-01-05 02:12:45 -08:00
billz
7dcc177424 Initial commit 2025-01-04 09:33:01 -08:00
Bill Zimmerman
94b502a336
Merge pull request #1724 from RaspAP/fix/adblock-update-ui
Revise bootstrap 5.3 adblock update btn
2025-01-03 00:49:38 -08:00
billz
bfd5859ce1 Revise bootstrap 5.3 adblock update btn 2025-01-03 00:38:45 -08:00
Bill Zimmerman
13cdfbd8cb
Merge pull request #1723 from RaspAP/fix/wg-endpoint-seg
Extends wg peer validation to dynamic addresses
2025-01-01 01:22:46 -08:00
billz
531970d9c6 Extend wg peer endpoint validation w/ subdomain + hostname 2025-01-01 00:41:56 -08:00
billz
5902a8d6a0 Update template + wg-keygen js handler 2025-01-01 00:37:25 -08:00
billz
9bb2075b77 Add strings to en_US locale, update template 2024-12-26 09:41:52 -08:00
billz
3c61954971 Minor: update label 2024-12-26 09:17:33 -08:00
billz
361a2f7531 Use text() instead of replaceWith() 2024-12-26 09:17:12 -08:00
billz
ad36695224 Set/read plugin-installed flag 2024-12-26 08:04:16 -08:00
billz
92ba7df9c6 Create plugin install event handler 2024-12-25 19:03:41 -08:00
billz
ae7b03857e Update install-plugin-progress modal btn 2024-12-25 19:00:41 -08:00
billz
2c0ace4500 Fix for getUserPlugins, add installed btn 2024-12-25 18:59:49 -08:00
billz
759e5dcf5d Add _create_plugin_scripts() 2024-12-25 18:57:59 -08:00
billz
bf0d9f88e2 Implement installPlugin() and related methods 2024-12-25 13:08:32 -08:00
billz
bf74ff7057 Update w/ config, plugin actions 2024-12-25 12:27:47 -08:00
billz
4328f54270 Add visudo -cf, plugin_helper to sudoers 2024-12-24 22:48:21 -08:00
billz
efd67fb698 Add plugin install progress modal 2024-12-24 22:46:17 -08:00
billz
44c99dacdf Initial commit 2024-12-24 22:45:18 -08:00
Bill Zimmerman
048d4ab3e6
Merge pull request #1720 from GuillaumeRossolini/patch-1
Update README.md
2024-12-23 14:53:46 -08:00
Guillaume Rossolini
801ca5a788
Update README.md
Replaced dead link for SSL certificates
2024-12-23 18:00:00 +01:00
billz
e5987a6b59 Update w/ link to plugin_uri 2024-12-22 09:27:40 -08:00
billz
2cb66660c5 Create modal install-user-plugin dialog, update template text 2024-12-21 22:42:34 -08:00
billz
c3968ba42e Encapsulate plugin related functions in PluginInstaller class 2024-12-21 22:40:55 -08:00
billz
edb86d7781 Populate #install-user-plugin modal dialog 2024-12-21 22:39:17 -08:00
billz
6785cc1104 Move getPluginManifest() to PluginInstaller class 2024-12-21 10:40:41 -08:00
billz
ee38614334 Initial commit 2024-12-21 10:39:46 -08:00
billz
9b087f88a7 Call PluginInstaller::getInstance(), set install option 2024-12-20 18:53:35 -08:00
billz
247b35b254 Initial commit 2024-12-20 18:52:09 -08:00
billz
89c4f16e45 Get plugin submodules, fetch manifest details + format output 2024-12-20 13:42:53 -08:00
billz
117370efcf Add plugins tab, render system/plugins template 2024-12-20 13:34:58 -08:00
billz
66e35c564c Initial commit 2024-12-20 13:34:05 -08:00
billz
36b0285158 Define RASPI_PLUGINS_URL 2024-12-20 13:33:46 -08:00
billz
07ec56227d Minor: mode change 2024-12-20 11:45:54 -08:00
Dhanus
8ae468376f fix: enable all flags and install missing packages 2024-11-02 22:19:01 +05:30
Dhanus
90794c8828 fix: disable all options 2024-11-01 18:08:34 +05:30
Dhanus
28d1395bef chore: fail-fast false for 32 bit image build to finish 2024-11-01 12:17:55 +05:30
Dhanus
8048fcf5c4 chore: add apt-get update 2024-10-31 19:00:59 +05:30
Dhanus
710a8dab4e chore: add curl installation 2024-10-31 18:28:26 +05:30
Dhanus
274b42d225 chore: add custom stage 2024-10-31 18:20:25 +05:30
Dhanus
69d58f4713 chore: revert to previous commit due to extracted image size > 2GB
This reverts commit a1b0ade0a5.
2024-10-31 18:00:49 +05:30
Dhanus
d90ccf4277 fix: upload img instead of zip 2024-10-31 17:27:45 +05:30
Dhanus
a1b0ade0a5 chore: remove system update 2024-10-31 17:15:18 +05:30
Dhanus
f1a5b9163b fix: asset_name file type 2024-10-31 17:12:17 +05:30
Dhanus
cb4fc2ac54 ci: Add build for 32/64 bit images 2024-10-31 17:10:50 +05:30
109 changed files with 10653 additions and 930 deletions

View file

@ -34,15 +34,15 @@ body:
required: true
- label: I observed this bug on a clean install of a [supported OS](https://docs.raspap.com/#compatible-operating-systems).
required: true
- label: I have followed the [project prerequisites](https://docs.raspap.com/#quick-start).
- label: I have followed the [project prerequisites](https://docs.raspap.com/quick_start/#quick-install).
required: true
- label: I have searched this repository for existing issues.
required: true
- label: I checked the [FAQ](https://docs.raspap.com/faq/) and [official documentation](https://docs.raspap.com/).
required: true
- label: I am using an [external wireless adapter](https://docs.raspap.com/issues/#external-hardware).
required: true
- label: I have generated a [RaspAP debug log](https://docs.raspap.com/ap-basics/#debug-log) and performed a [self-diagnosis](https://docs.raspap.com/ap-basics/#diagnosing-problems).
required: false
- label: I have generated a [RaspAP debug log](https://docs.raspap.com/troubleshooting/#debug-log) and performed a [self-diagnosis](https://docs.raspap.com/troubleshooting/#diagnosing-problems).
required: true
- type: dropdown
@ -52,18 +52,19 @@ body:
options:
- Raspberry Pi OS (64-bit) Lite Bookworm
- Raspberry Pi OS (32-bit) Lite Bookworm
- Raspberry Pi OS (64-bit) Desktop Bookwom
- Raspberry Pi OS (64-bit) Lite Bullseye
- Raspberry Pi OS (32-bit) Lite Bullseye
- Armbian 23.05 (Suni)
- Debian Bookworm
- Ubuntu Server 23.04 (Lunar)
- Debian Bookworm
validations:
required: true
- type: dropdown
id: install
attributes:
label: Quick install or Manual setup?
label: Installation method
options:
- Pre-built image
- Quick install
- Manual setup
validations:
@ -89,6 +90,7 @@ body:
- Raspberry Pi 3 Model B
- Raspberry Pi Zero 2 W
- Raspberry Pi Zero W
- Raspberry Pi Compute Module
- Orange Pi family
- Other
validations:
@ -98,11 +100,8 @@ body:
attributes:
label: RaspAP version
options:
- 3.1.3 (Latest)
- 3.1.2
- 3.1.1
- 3.1.0
- Other
- Latest
- Other (specify below)
validations:
required: true
- type: dropdown
@ -115,7 +114,6 @@ body:
- Not sure
validations:
required: true
- type: input
id: contact
attributes:

80
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,80 @@
name: Build and publish RaspAP images
permissions:
contents: write
on:
release:
types: [published]
jobs:
build-raspap-image:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- arch: "32-bit"
pi_gen_version: "master"
- arch: "64-bit"
pi_gen_version: "arm64"
fail-fast: false
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Add RaspAP Stage
run: |
mkdir -p stage-raspap/package-raspap &&
{
cat > stage-raspap/package-raspap/00-run-chroot.sh <<-EOF
#!/bin/bash
apt-get update -y && apt-get install -y curl dhcpcd5 iptables procps
curl -sL https://install.raspap.com | bash -s -- --yes --openvpn 1 --restapi 1 --adblock 1 --wireguard 1 --tcp-bbr 1 --check 0
# Set Wi-Fi country to prevent RF kill
raspi-config nonint do_wifi_country "US"
# Fetch RaspAP version and set MOTD
RASPAP_VERSION=\$(curl -sL https://install.raspap.com | bash -s -- --version)
echo "\$RASPAP_VERSION" | tee /etc/motd
EOF
} &&
chmod +x stage-raspap/package-raspap/00-run-chroot.sh &&
{
cat > stage-raspap/prerun.sh <<-EOF
#!/bin/bash -e
if [ ! -d "\${ROOTFS_DIR}" ]; then
copy_previous
fi
EOF
} &&
chmod +x stage-raspap/package-raspap/00-run-chroot.sh &&
{
cat > stage-raspap/prerun.sh <<-EOF
#!/bin/bash -e
if [ ! -d "\${ROOTFS_DIR}" ]; then
copy_previous
fi
EOF
} &&
chmod +x stage-raspap/prerun.sh
- name: Build RaspAP Image
id: build
uses: usimd/pi-gen-action@v1
with:
image-name: "raspap-bookworm-${{ matrix.arch == '32-bit' && 'armhf' || 'arm64' }}-lite-${{ github.event.inputs.tag || github.ref_name }}"
enable-ssh: 1
stage-list: stage0 stage1 stage2 ./stage-raspap
verbose-output: true
pi-gen-version: ${{ matrix.pi_gen_version }}
pi-gen-repository: RaspAP/pi-gen
- name: Upload Artifact
uses: svenstaro/upload-release-action@v2
with:
asset_name: "raspap-bookworm-${{ matrix.arch == '32-bit' && 'armhf' || 'arm64' }}-lite-${{ github.event.inputs.tag || github.ref_name }}.img.zip"
file: ${{ steps.build.outputs.image-path }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.event.inputs.tag || github.ref }}
overwrite: true

2
.gitignore vendored
View file

@ -3,7 +3,7 @@ node_modules
yarn-error.log
*.swp
includes/config.php
plugins/
rootCA.pem
vendor
.env
locale/**/*.mo

4
.gitmodules vendored Normal file
View file

@ -0,0 +1,4 @@
[submodule "plugins"]
path = plugins
url = https://github.com/RaspAP/plugins
branch = master

View file

@ -1,4 +1,4 @@
<img width="465" alt="Insiders logo" src="https://user-images.githubusercontent.com/229399/115766971-e19e1900-a3a8-11eb-8c6f-379deb4313d2.png">
<img width="465" alt="Insiders logo" src="https://i.imgur.com/62TMUy5.png">
Development of RaspAP is made possible thanks to a sponsorware release model. This means that new features are first exclusively released to sponsors as part of **Insiders**. Read on to learn how sponsorship works, and how easy it is to get access to Insiders.
@ -20,7 +20,6 @@ The following features are currently available exclusively to sponsors. A tangib
✅ [WPA3-Personal AP security](https://docs.raspap.com/ap-basics/#wpa3-personal)
✅ [802.11w Protected Management Frames](https://docs.raspap.com/ap-basics/#80211w)
✅ [Printable Wi-Fi signs](https://docs.raspap.com/ap-basics/#printable-signs)
✅ [Drag & drop dashboard widgets](https://docs.raspap.com/ap-basics/#drag-drop-widgets)
✅ [MAC address cloning](https://docs.raspap.com/net-devices/#changing-the-mac-address)
✅ [Network diagnostics](https://docs.raspap.com/net-devices/#diagnostics)
✅ [WireGuard VPN kill switch](https://docs.raspap.com/wireguard/#kill-switch)

View file

@ -1,14 +1,13 @@
![](https://i.imgur.com/xeKD93p.png)
[![Release 3.2.4](https://img.shields.io/badge/release-v3.2.4-green)](https://github.com/raspap/raspap-webgui/releases) [![Awesome](https://awesome.re/badge.svg)](https://github.com/thibmaek/awesome-raspberry-pi) [![Join Insiders](https://img.shields.io/static/v1?label=Insiders&message=%E2%9D%A4&logo=GitHub&color=ff69b4)](https://github.com/sponsors/RaspAP) [![Build Status](https://app.travis-ci.com/RaspAP/raspap-webgui.svg?branch=master)](https://app.travis-ci.com/RaspAP/raspap-webgui) [![Crowdin](https://badges.crowdin.net/raspap/localized.svg)](https://crowdin.com/project/raspap) [![Twitter URL](https://img.shields.io/twitter/url?label=%40RaspAP&logoColor=%23d8224c&url=https%3A%2F%2Ftwitter.com%2Frasp_ap)](https://twitter.com/rasp_ap) [![Reddit](https://img.shields.io/badge/%2Fr%2FRaspAP-e05d44?style=flat&logo=Reddit&logoColor=white&labelColor=e05d44&color=b14835)](https://reddit.com/r/RaspAP) [![Discord](https://img.shields.io/discord/642436993451819018?color=7289DA&label=Discord&logo=discord&style=flat)](https://discord.gg/KVAsaAR)
![RaspAP Custom OS images](https://github.com/user-attachments/assets/e871adf1-123c-450b-94eb-80a185c242cc)
[![Release 3.3.2](https://img.shields.io/badge/release-v3.3.2-green)](https://github.com/raspap/raspap-webgui/releases) [![Awesome](https://awesome.re/badge.svg)](https://github.com/thibmaek/awesome-raspberry-pi) [![Join Insiders](https://img.shields.io/static/v1?label=Insiders&message=%E2%9D%A4&logo=GitHub&color=ff69b4)](https://github.com/sponsors/RaspAP) [![Build Status](https://app.travis-ci.com/RaspAP/raspap-webgui.svg?branch=master)](https://app.travis-ci.com/RaspAP/raspap-webgui) [![Crowdin](https://badges.crowdin.net/raspap/localized.svg)](https://crowdin.com/project/raspap) [![Twitter URL](https://img.shields.io/twitter/url?label=%40RaspAP&logoColor=%23d8224c&url=https%3A%2F%2Ftwitter.com%2Frasp_ap)](https://twitter.com/rasp_ap) [![Reddit](https://img.shields.io/badge/%2Fr%2FRaspAP-e05d44?style=flat&logo=Reddit&logoColor=white&labelColor=e05d44&color=b14835)](https://reddit.com/r/RaspAP) [![Discord](https://img.shields.io/discord/642436993451819018?color=7289DA&label=Discord&logo=discord&style=flat)](https://discord.gg/KVAsaAR)
RaspAP is feature-rich wireless router software that _just works_ on many popular [Debian-based devices](#supported-operating-systems), including the Raspberry Pi. Our [custom OS images](#pre-built-image), [Quick installer](#quick-installer) and [Docker container](#docker-support) create a known-good default configuration for all current Raspberry Pis with onboard wireless. A fully responsive, mobile-ready interface gives you control over the relevant services and networking options. Advanced DHCP settings, WireGuard and OpenVPN support, [SSL certificates](https://docs.raspap.com/ssl/), [ad blocking](#ad-blocking), security audits, [captive portal integration](https://docs.raspap.com/captive/), themes and [multilingual options](https://docs.raspap.com/translations/) are included.
RaspAP is feature-rich wireless router software that _just works_ on many popular [Debian-based devices](#supported-operating-systems), including the Raspberry Pi. Our popular [Quick installer](#quick-installer) and [Docker container](#docker-support) create a known-good default configuration for all current Raspberry Pis with onboard wireless. A fully responsive, mobile-ready interface gives you control over the relevant services and networking options. Advanced DHCP settings, WireGuard and OpenVPN support, [SSL certificates](https://docs.raspap.com/ssl-quick/), security audits, [captive portal integration](https://docs.raspap.com/captive/), themes and [multilingual options](https://docs.raspap.com/translations/) are included.
RaspAP has been featured on sites such as [Instructables](http://www.instructables.com/id/Raspberry-Pi-As-Completely-Wireless-Router/), [Adafruit](https://blog.adafruit.com/2016/06/24/raspap-wifi-configuration-portal-piday-raspberrypi-raspberry_pi/), [Raspberry Pi Weekly](https://www.raspberrypi.org/weekly/commander/) and [Awesome Raspberry Pi](https://project-awesome.org/thibmaek/awesome-raspberry-pi) and implemented in countless projects.
RaspAP has been featured by [PC World](https://www.pcwelt.de/article/1789512/raspberry-pi-als-wlan-router.html), [Adafruit](https://blog.adafruit.com/2016/06/24/raspap-wifi-configuration-portal-piday-raspberrypi-raspberry_pi/), [Raspberry Pi Weekly](https://www.raspberrypi.org/weekly/commander/), and [Awesome Raspberry Pi](https://project-awesome.org/thibmaek/awesome-raspberry-pi) and implemented in [countless projects](https://github.com/RaspAP/raspap-awesome#projects).
We hope you enjoy using RaspAP as much as we do creating it. Tell us how you use this with [your own projects](https://github.com/raspap/raspap-awesome).
![](https://github.com/user-attachments/assets/c9bb0386-0b10-48cc-8b42-0a587a84fc37)
![dashboard](https://github.com/user-attachments/assets/f7cf5c32-4d95-4ac8-8a30-6d892d7ac6ed)
<img width="32.5%" alt="Wifi Client" src="https://github.com/user-attachments/assets/95696ddc-da84-4339-97cc-f2a173054664">
<img width="32.5%" alt="Hotspot" src="https://github.com/user-attachments/assets/c1c4de15-3ff2-4d3c-a7af-339c24896749">
<img width="32.5%" alt="Adblock" src="https://github.com/user-attachments/assets/ab925687-8407-4bec-a952-9dc6a2675f49">
@ -18,15 +17,13 @@ We hope you enjoy using RaspAP as much as we do creating it. Tell us how you use
## Contents
- [Prerequisites](#prerequisites)
- [Quick installer](#quick-installer)
- [Quick start](#quick-start)
- [Join Insiders](#join-insiders)
- [WireGuard support](#wireguard-support)
- [OpenVPN support](#openvpn-support)
- [VPN Provider support](#vpn-provider-support)
- [Ad Blocking](#ad-blocking)
- [Bridged AP](#bridged-ap)
- [Simultaneous AP and Wifi client](#simultaneous-ap-and-wifi-client)
- [Manual installation](#manual-installation)
- [802.11ac 5GHz support](#80211ac-5ghz-support)
- [Supported operating systems](#supported-operating-systems)
@ -38,30 +35,43 @@ We hope you enjoy using RaspAP as much as we do creating it. Tell us how you use
- [Reporting issues](#reporting-issues)
- [License](#license)
## Prerequisites
Start with a clean install of the [latest release of Raspberry Pi OS Lite](https://www.raspberrypi.com/software/operating-systems/). Both the 32- and 64-bit Lite versions are supported. The Raspberry Pi OS desktop distro is [unsupported](https://docs.raspap.com/faq/#distros).
## Quick start
RaspAP gives you two different ways to get up and running quickly. The simplest and recommended approach is to use a custom Raspberry Pi OS image with RaspAP preinstalled. This option eliminates guesswork and gives you a base upon which to build. Alternatively, you may execute the Quick installer on an existing [compatible OS](https://docs.raspap.com/#compatible-operating-systems).
### Pre-built image
Custom Raspberry Pi OS Lite images with the latest RaspAP are available for [direct download](https://github.com/RaspAP/raspap-webgui/releases/latest). This includes both 32- and 64-bit builds for ARM architectures.
| Operating system | Debian version | Kernel version | RaspAP version | Size |
| ---------------------| ---------------|-----------------|----------------|-------|
| Raspberry Pi OS (64-bit) Lite | 12 (bookworm) | 6.6 | Latest | 777 MB|
| Raspberry Pi OS (32-bit) Lite | 12 (bookworm) | 6.6 | Latest | 805 MB|
These images are automatically generated with each release of RaspAP. You may choose between an `arm64` or `armhf` (32-bit) based build. Refer to [this resource](https://www.raspberrypi.com/software/operating-systems/) to ensure compatibility with your hardware.
After downloading your desired image from the [latest release page](https://github.com/RaspAP/raspap-webgui/releases/latest), use a utility such as the Raspberry Pi Imager or [balenaEtcher](https://www.balena.io/etcher) to flash the OS image onto a microSD card. Insert the card into your device and boot it up. The latest RaspAP release version with the most popular optional components will be active and ready for you to configure.
### Quick installer
Alternatively, start with a clean install of a [latest release of Raspberry Pi OS](https://www.raspberrypi.org/software/operating-systems/). Both the 32- and 64-bit release versions are supported, as well as the latest 64-bit Desktop distribution.
Update RPi OS to its latest version, including the kernel and firmware, followed by a reboot:
1. Update Raspbian, including the kernel and firmware, followed by a reboot:
```
sudo apt-get update
sudo apt-get full-upgrade
sudo reboot
```
2. Set the "WLAN country" option in `raspi-config`'s **Localisation Options**: `sudo raspi-config`
Set the WiFi country in raspi-config's **Localisation Options**: `sudo raspi-config`.
3. If you have a device without an onboard wireless chipset, the [**Edimax Wireless 802.11b/g/n nano USB adapter**](https://www.edimax.com/edimax/merchandise/merchandise_detail/data/edimax/global/wireless_adapters_n150/ew-7811un) is an excellent option it's small, cheap and has good driver support.
With the prerequisites done, you can proceed with either the Quick installer or Manual installation steps below.
## Quick installer
Install RaspAP from your device's shell prompt:
```sh
curl -sL https://install.raspap.com | bash
```
The [installer](https://docs.raspap.com/quick/) will complete the steps in the manual installation (below) for you.
After the reboot at the end of the installation the wireless network will be
configured as an access point as follows:
The Quick installer will respond to several [command line arguments](https://docs.raspap.com/quick/), or switches, to customize your installation in a variety of ways, or install one of RaspAP's optional helper tools.
### Initial settings
After completing either of these setup options, the wireless AP network will be configured as follows:
* IP address: 10.3.141.1
* Username: admin
* Password: secret
@ -69,7 +79,7 @@ configured as an access point as follows:
* SSID: `raspi-webgui`
* Password: ChangeMe
**Note:** As the name suggests, the Quick Installer is a great way to quickly setup a new AP. However, it does not automagically detect the unique configuration of your system. Best results are obtained by connecting to ethernet (`eth0`) or as a WiFi client, also known as managed mode, with `wlan0`. For the latter, refer to [this FAQ](https://docs.raspap.com/faq/#headless). Special instructions for the Pi Zero W are [available here](https://docs.raspap.com/ap-sta/).
It's _strongly recommended_ that your first post-install action is to change the default admin [authentication](https://docs.raspap.com/authentication/) settings. Thereafter, your AP's [basic settings](https://docs.raspap.com/ap-basics/) and many [advanced options](https://docs.raspap.com/ap-basics#advanced-options) are now ready to be modified by RaspAP.
Please [read this](https://docs.raspap.com/issues/) before reporting an issue.
@ -118,11 +128,6 @@ By default RaspAP configures a routed AP for your clients to connect to. A bridg
More information on Bridged AP mode is provided [in our documentation](https://docs.raspap.com/bridged/).
## Simultaneous AP and Wifi client
RaspAP lets you create an AP with a Wifi client configuration, often called [AP-STA mode](https://docs.raspap.com/ap-sta/). With your system configured in managed mode, enable the AP from the **Advanced** tab of **Configure hotspot** by sliding the **Wifi client AP mode** toggle. Save settings and start the hotspot. The managed mode AP is functional without restart.
**Note:** This option is disabled until you configure your system as a wireless client. For a device operating in [managed mode](https://docs.raspap.com/faq/#headless) without an `eth0` connection, this configuration must be enabled [_before_ a reboot](https://docs.raspap.com/ap-sta/).
## Manual installation
Detailed manual setup instructions are provided [on our documentation site](https://docs.raspap.com/manual/).
@ -139,11 +144,10 @@ RaspAP was originally made for Raspbian, but now also installs on the following
| Raspberry Pi OS | (64-bit) Desktop Bookworm | ARM | Official |
| Raspberry Pi OS | (64-bit) Lite Bullseye | ARM | Official |
| Raspberry Pi OS | (32-bit) Lite Bullseye | ARM | Official |
| Armbian | 23.11 (Jammy) | [ARM](https://docs.armbian.com/#supported-socs) | Official |
| Armbian | 23.11 (Jammy) | [ARM](https://docs.armbian.com/#supported-socs) | Beta |
| Debian | Bookworm | ARM / x86_64 | Beta |
| Ubuntu | Server 23.04 (Lunar) | ARM / x86_64 | Beta |
<img src="https://github.com/RaspAP/raspap-webgui/assets/229399/6fe62f2d-631a-46c9-8ceb-83ebf0ade6a9" style="width:640px;" />
<img src="https://i.imgur.com/XiAJNKb.png" style="width:480px;" />
You are also encouraged to use RaspAP's community-led [Docker container](#docker-support). Please note that "supported" is not a guarantee. If you are able to improve support for your preferred distro, we encourage you to [actively contribute](#how-to-contribute) to the project.

View file

@ -1,8 +1,8 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
if (isset($_POST['blocklist_id'])) {

View file

@ -1,8 +1,8 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
$interface = filter_input(INPUT_GET, 'inet', FILTER_SANITIZE_SPECIAL_CHARS);

View file

@ -1,8 +1,8 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
if (filter_input(INPUT_GET, 'tu') == 'h') {

View file

@ -1,8 +1,8 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
require_once '../../includes/functions.php';

View file

@ -1,36 +1,29 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
require_once '../../includes/session.php';
require_once '../../includes/authenticate.php';
require_once '../../includes/functions.php';
if (isset($_POST['csrf_token'])) {
if (csrfValidateRequest() && !CSRFValidate()) {
handleInvalidCSRFToken();
}
$return = 0;
$path = "../../config";
$configs = array(
array("src" => $path .'/hostapd.conf', "tmp" => "/tmp/hostapddata", "dest" => RASPI_HOSTAPD_CONFIG),
array("src" => $path .'/dhcpcd.conf', "tmp" => "/tmp/dhcpddata", "dest" => RASPI_DHCPCD_CONFIG),
array("src" => $path .'/090_wlan0.conf', "tmp" => "/tmp/dnsmasqdata", "dest" => RASPI_DNSMASQ_PREFIX.'wlan0.conf'),
array("src" => $path .'/090_raspap.conf', "tmp" => "/tmp/dnsmasqdata", "dest" => RASPI_DNSMASQ_PREFIX.'raspap.conf'),
);
$return = 0;
$path = "../../config";
$configs = array(
array("src" => $path .'/hostapd.conf', "tmp" => "/tmp/hostapddata", "dest" => RASPI_HOSTAPD_CONFIG),
array("src" => $path .'/dhcpcd.conf', "tmp" => "/tmp/dhcpddata", "dest" => RASPI_DHCPCD_CONFIG),
array("src" => $path .'/090_wlan0.conf', "tmp" => "/tmp/dnsmasqdata", "dest" => RASPI_DNSMASQ_PREFIX.'wlan0.conf'),
array("src" => $path .'/090_raspap.conf', "tmp" => "/tmp/dnsmasqdata", "dest" => RASPI_DNSMASQ_PREFIX.'raspap.conf'),
);
foreach ($configs as $config) {
try {
$tmp = file_get_contents($config["src"]);
file_put_contents($config["tmp"], $tmp);
system("sudo cp ".$config["tmp"]. " ".$config["dest"]);
} catch (Exception $e) {
$return = $e->getCode();
}
foreach ($configs as $config) {
try {
$tmp = file_get_contents($config["src"]);
file_put_contents($config["tmp"], $tmp);
system("sudo cp ".$config["tmp"]. " ".$config["dest"]);
} catch (Exception $e) {
$return = $e->getCode();
}
$jsonData = ['return'=>$return];
echo json_encode($jsonData);
} else {
handleInvalidCSRFToken();
}
$jsonData = ['return'=>$return];
echo json_encode($jsonData);

View file

@ -1,8 +1,8 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
exec("ls /sys/class/net | grep -v lo", $interfaces);

View file

@ -1,8 +1,8 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
exec('cat '. RASPI_HOSTAPD_CONFIG, $hostapdconfig);
@ -17,3 +17,4 @@ foreach ($hostapdconfig as $hostapdconfigline) {
};
$channel = intval($arrConfig['channel']);
echo json_encode($channel);

View file

@ -1,9 +1,8 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../src/RaspAP/Parsers/IwParser.php';
require_once '../../includes/authenticate.php';
if (isset($_POST['interface'])) {

View file

@ -1,10 +1,9 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/functions.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
if (isset($_POST['interface'])) {

View file

@ -1,9 +1,10 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
require_once '../../includes/functions.php';
$interface = $_POST['iface'];

View file

@ -1,8 +1,8 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
require_once '../../includes/locale.php';

View file

@ -1,8 +1,8 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
// fetch wg client.conf

View file

@ -1,8 +1,8 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
$entity = escapeshellcmd($_POST['entity']);

View file

@ -1,8 +1,8 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
require_once '../../includes/defaults.php';
require_once '../../includes/functions.php';

View file

@ -1,8 +1,8 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
require_once '../../includes/functions.php';

View file

@ -1,8 +1,8 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
require_once '../../includes/functions.php';

View file

@ -0,0 +1,26 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
$pluginInstaller = \RaspAP\Plugins\PluginInstaller::getInstance();
$plugin_uri = $_POST['plugin_uri'] ?? null;
$plugin_version = $_POST['plugin_version'] ?? null;
$install_path = $_POST['install_path'] ?? null;
if (isset($plugin_uri, $plugin_version, $install_path)) {
try {
$return = $pluginInstaller->installPlugin($plugin_uri, $plugin_version, $install_path);
echo json_encode($return);
} catch (Exception $e) {
http_response_code(422); // unprocessable content
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(400); // Bad Request
echo json_encode(['error' => 'Plugin URI, version, and install path are required']);
exit;
}

View file

@ -0,0 +1,31 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
$lastActivity = $_SESSION['lastActivity'] ?? time();
$sessionLifetime = time() - $lastActivity;
$status = $sessionLifetime >= RASPI_SESSION_TIMEOUT ? 'session_expired' : 'active';
if ($status === 'session_expired') {
session_unset(); // unset all session variables
session_destroy(); // destroy the session
}
// send response
header('Content-Type: application/json');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Expires: Thu, 01 Jan 1970 00:00:00 GMT');
header('Pragma: no-cache');
$response = [
'status' => $status,
'last_activity' => $lastActivity,
'session_lifetime' => $sessionLifetime
];
echo json_encode($response);
exit();

View file

@ -1,8 +1,8 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
$action = escapeshellcmd($_POST['a']);

View file

@ -1,26 +1,22 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../includes/authenticate.php';
require_once '../../includes/defaults.php';
require_once '../../includes/functions.php';
if (isset($_POST['csrf_token'])) {
if (csrfValidateRequest() && !CSRFValidate()) {
handleInvalidCSRFToken();
}
$uri = RASPI_API_ENDPOINT;
preg_match('/(\d+(\.\d+)+)/', RASPI_VERSION, $matches);
$thisRelease = $matches[0];
$uri = RASPI_API_ENDPOINT;
preg_match('/(\d+(\.\d+)+)/', RASPI_VERSION, $matches);
$thisRelease = $matches[0];
$json = shell_exec("wget --timeout=5 --tries=1 $uri -qO -");
$data = json_decode($json, true);
$tagName = $data['tag_name'];
$updateAvailable = checkReleaseVersion($thisRelease, $tagName);
$json = shell_exec("wget --timeout=5 --tries=1 $uri -qO -");
$data = json_decode($json, true);
$tagName = $data['tag_name'];
$updateAvailable = checkReleaseVersion($thisRelease, $tagName);
$response['tag'] = $tagName;
$response['update'] = $updateAvailable;
echo json_encode($response);
$response['tag'] = $tagName;
$response['update'] = $updateAvailable;
echo json_encode($response);
} else {
handleInvalidCSRFToken();
}

View file

@ -1,24 +1,18 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
if (isset($_POST['csrf_token'])) {
if (csrfValidateRequest() && !CSRFValidate()) {
handleInvalidCSRFToken();
}
$root = getenv("DOCUMENT_ROOT");
exec('sudo '.RASPI_CONFIG.'/system/debuglog.sh -i '.$root, $return);
$root = getenv("DOCUMENT_ROOT");
exec('sudo '.RASPI_CONFIG.'/system/debuglog.sh -i '.$root, $return);
$logOutput = implode(PHP_EOL, $return);
$tempDir = sys_get_temp_dir();
$filePath = $tempDir . DIRECTORY_SEPARATOR . RASPI_DEBUG_LOG;
$handle = fopen($filePath, "w");
fwrite($handle, $logOutput);
fclose($handle);
echo json_encode($filePath);
$logOutput = implode(PHP_EOL, $return);
$tempDir = sys_get_temp_dir();
$filePath = $tempDir . DIRECTORY_SEPARATOR . RASPI_DEBUG_LOG;
$handle = fopen($filePath, "w");
fwrite($handle, $logOutput);
fclose($handle);
echo json_encode($filePath);
} else {
handleInvalidCSRFToken();
}

View file

@ -1,8 +1,8 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
$tempDir = sys_get_temp_dir();

View file

@ -1,8 +1,8 @@
<?php
require '../../includes/csrf.php';
require_once '../../includes/autoload.php';
require_once '../../includes/CSRF.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
if (isset($_POST['csrf_token'])) {
@ -11,7 +11,7 @@ if (isset($_POST['csrf_token'])) {
}
// set installer path + options
$path = getenv("DOCUMENT_ROOT");
$opts = " --update --yes --path $path";
$opts = " --update --yes --check 0 --path $path";
$installer = "sudo /etc/raspap/system/raspbian.sh";
$execUpdate = $installer.$opts;

View file

@ -1,7 +1,8 @@
<?php
require_once '../../includes/autoload.php';
require_once '../../includes/session.php';
require_once '../../includes/config.php';
require_once '../../src/RaspAP/Auth/HTTPAuth.php';
require_once '../../includes/authenticate.php';
$logFile = '/tmp/raspap_install.log';

View file

@ -9,7 +9,9 @@ License: GNU General Public License v3.0
:root {
--raspap-content-main: #495057;
--raspap-text-muted: #858796;
--raspap-text-light: #999999;
--raspap-brand-color: #2b8080;
--raspap-offwhite: #faf9f6;
}
a {
@ -167,12 +169,7 @@ th {
}
canvas#divDBChartBandwidthhourly {
height: 350px!important;
}
.chart-container {
height: 150px;
width: 200px;
height: 509px!important;
}
.dbChart {
@ -189,7 +186,7 @@ canvas#divDBChartBandwidthhourly {
}
.check-progress {
color: #999;
color: var(--raspap-text-light);
}
.fa-check {
@ -306,16 +303,16 @@ button > i.fas {
/* Font Awesome 5 brands */
.fa-reddit {
color: #ff4500;
color: #ff4500;
}
.fa-twitter {
color: #55acee
color: #55acee
}
.fa-discord {
color: #7289da
color: #7289da
}
.fa-github {
color: #151b23
color: #151b23
}
@keyframes heart {
@ -332,3 +329,335 @@ button > i.fas {
animation: heart 1000ms infinite;
}
#modal-admin-login .modal-content {
background: radial-gradient(circle at 120% -20%, #032626, #052c2c, #073232, #0a3838, #0d3f3f, #114545, #144c4c);
align-items: center;
}
#modal-admin-login .modal-body {
min-width: 330px;
}
.login-brand {
color: var(--raspap-theme-color);
filter: brightness(150%);
}
.admin-login {
color: var(--raspap-offwhite);
font-size: 1.2em
}
.btn-admin-login {
color: var(--raspap-offwhite);
background-color: var(--raspap-theme-color);
}
.btn-admin-login:hover {
color: var(--raspap-offwhite);
background-color: #236969;
}
.no-right-radius {
border-top-right-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.btn-passwd-append {
border: 1px solid #ced4da;
}
#passwd-toggle:active,
#passwd-toggle:hover,
#passwd-toggle:focus {
border: 1px solid #ced4da;
}
textarea.plugin-log {
width: 100%;
height: 150px;
resize: none;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
padding: 0.5rem;
background-color: #f8f9fa;
font-family: monospace;
font-size: 0.9rem;
}
.card-wrapper {
margin: 1rem;
}
.dashboard-container {
display: flex;
position: relative;
width: 100%;
min-height: 400px;
padding: 2rem;
}
.connections-left,
.connections-right {
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.connection-item {
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5rem;
z-index: 5;
color: var(--raspap-text-light);
}
.connection-right {
align-items: center;
margin-left: 10rem;
}
.connections-left i {
height: 40px;
display: flex;
align-items: center;
justify-content: left;
}
.connections-left i:first-child {
margin-top: 0;
}
.connections-left i:last-child {
margin-bottom: 0;
margin-left: 0.5rem;
}
.center-device {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
position: relative;
z-index: 1;
}
.center-device-top {
margin-bottom: 30px;
}
.client-group {
display: flex;
align-items: center;
flex-direction: row-reverse;
gap: 0.5rem;
}
.client-count {
text-align: right;
}
.clients-status {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-end;
padding-right: 1rem;
}
.dashed-lines,
.solid-lines {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain;
padding: 1rem;
left: 112px;
}
.dashed-lines-right,
.solid-lines-right {
left: -80px;
}
.solid-lines, .solid-lines-right {
z-index: 3;
}
.dashed-lines, .dashed-lines-right {
z-index 0;
}
.device-status {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
margin: 0.8rem 0;
}
.wifi-bands {
display: flex;
gap: 0.5rem;
}
.band {
padding: 0.25rem 1rem;
border: 2px solid var(--raspap-text-light);
border-radius: 4px;
background: transparent;
font-weight: 600;
color: var(--raspap-text-light);
}
.band.active {
border-color: var(--raspap-theme-color);
color: var(--raspap-theme-color);
}
.device-label {
font-size: 1.3rem;
text-align: center;
color: var(--raspap-theme-color);
margin-top: 1rem;
}
.status-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.3rem;
color: var(--raspap-text-light);
}
.bottom {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.3rem;
width: 100%;
}
.status-item .fa-stack {
width: 1.5em!important;
}
.connection-item>i {
color: var(--raspap-text-light);
}
.connection-item .fa-stack {
min-width: 2.5em;
}
.connections-left>.connection-item>span {
color: var(--raspap-text-light);
margin-right: 0.5rem;
}
.inactive {
color: var(--raspap-text-light)!important;
}
a.inactive:hover,
a.inactive:focus {
color: var(--raspap-text-light) !important;
}
@media (max-width: 1200px) {
.connection-item a > span:not(.fa-stack) {
display: none!important;
}
}
@media (max-width: 991px) {
.connections-right,
.connections-left {
display: none!important;
}
.dashboard-container {
width: auto;
padding: 0;
}
.device-status {
gap: 0.5rem;
}
.clients-mobile {
display: flex!important;
flex-direction: row!important;
}
}
.connection-item.active > span {
color: var(--raspap-theme-color)!important;
}
.connection-item.active > i {
color: var(--raspap-theme-color)!important;
}
.status-item.active > span {
color: var(--raspap-theme-color)!important;
}
.status-item.active > i {
color: var(--raspap-theme-color)!important;
}
.clients-mobile {
display: none;
flex-direction: column;
gap: 1rem;
margin-top: 2rem;
}
.client-type {
position: relative;
display: inline-flex;
align-items: center;
gap: 1rem;
}
.client-type i {
font-size: 1.5rem;
color: var(--raspap-theme-color);
width: 45px;
height: 45px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid var(--raspap-theme-color);
}
.client-type i.badge-icon {
font-size: 0.7rem;
background: var(--raspap-theme-color);
color: var(--raspap-offwhite);
width: 20px;
height: 20px;
border: none;
}
.client-count {
position: absolute;
top: -5px;
right: -5px;
background: var(--raspap-theme-color);
color: var(--raspap-offwhite);
border-radius: 50%;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
}
.device-illustration {
min-width: 220px;
max-width: 250px;
}

54
app/img/dashed.svg Normal file
View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 227 596" style="enable-background:new 0 0 227 596;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#999999;stroke-width:3;}
.st1{fill:none;stroke:#999999;stroke-width:3;stroke-dasharray:6.0204,3.0102;}
.st2{fill:none;stroke:#999999;stroke-width:3;stroke-dasharray:5.7963,2.8981;}
</style>
<g id="dashed">
<g id="Line_1">
<g>
<line class="st0" x1="112.8" y1="0" x2="112.8" y2="3"/>
<line class="st1" x1="112.8" y1="6" x2="112.8" y2="591.5"/>
<line class="st0" x1="112.8" y1="593" x2="112.8" y2="596"/>
</g>
</g>
<g id="Line_2">
<g>
<line class="st0" x1="113.2" y1="0.8" x2="110.2" y2="0.8"/>
<line class="st2" x1="107.3" y1="0.8" x2="4.4" y2="0.8"/>
<line class="st0" x1="3" y1="0.8" x2="0" y2="0.8"/>
</g>
</g>
<g id="Line_3">
<g>
<line class="st0" x1="113.2" y1="198.9" x2="110.2" y2="198.9"/>
<line class="st2" x1="107.3" y1="198.9" x2="4.4" y2="198.9"/>
<line class="st0" x1="3" y1="198.9" x2="0" y2="198.9"/>
</g>
</g>
<g id="Line_4">
<g>
<line class="st0" x1="113.2" y1="397.1" x2="110.2" y2="397.1"/>
<line class="st2" x1="107.3" y1="397.1" x2="4.4" y2="397.1"/>
<line class="st0" x1="3" y1="397.1" x2="0" y2="397.1"/>
</g>
</g>
<g id="Line_5">
<g>
<line class="st0" x1="113.2" y1="595.2" x2="110.2" y2="595.2"/>
<line class="st2" x1="107.3" y1="595.2" x2="4.4" y2="595.2"/>
<line class="st0" x1="3" y1="595.2" x2="0" y2="595.2"/>
</g>
</g>
<g id="Line_6">
<g>
<line class="st0" x1="226.2" y1="297.8" x2="223.2" y2="297.8"/>
<line class="st2" x1="220.3" y1="297.8" x2="117.4" y2="297.8"/>
<line class="st0" x1="116" y1="297.8" x2="113" y2="297.8"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

1083
app/img/devices/compute.php Normal file

File diff suppressed because it is too large Load diff

4450
app/img/devices/default.php Normal file

File diff suppressed because it is too large Load diff

1561
app/img/devices/zero.php Normal file

File diff suppressed because it is too large Load diff

0
app/img/raspAP-logo.php Normal file → Executable file
View file

40
app/img/right-dashed.svg Normal file
View file

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 314 594" style="enable-background:new 0 0 314 594;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#999999;stroke-width:3;}
.st1{fill:none;stroke:#999999;stroke-width:3;stroke-dasharray:6.04,3.02;}
.st2{fill:none;stroke:#999999;stroke-width:3;stroke-dasharray:5.7963,2.8981;}
</style>
<g id="dashed">
<g id="horizontal">
<g>
<line class="st0" x1="113.2" y1="144" x2="113.2" y2="147"/>
<line class="st1" x1="113.2" y1="150" x2="113.3" y2="447.5"/>
<line class="st0" x1="113.3" y1="449" x2="113.3" y2="452"/>
</g>
</g>
<g id="top">
<g>
<line class="st0" x1="114" y1="144.8" x2="117" y2="144.8"/>
<line class="st2" x1="119.9" y1="144.8" x2="222.8" y2="144.8"/>
<line class="st0" x1="224.2" y1="144.8" x2="227.2" y2="144.8"/>
</g>
</g>
<g id="out">
<g>
<line class="st0" x1="0" y1="297.8" x2="3" y2="297.8"/>
<line class="st2" x1="5.9" y1="297.8" x2="108.8" y2="297.8"/>
<line class="st0" x1="110.2" y1="297.8" x2="113.2" y2="297.8"/>
</g>
</g>
<g id="bottom">
<g>
<line class="st0" x1="113" y1="450.8" x2="116" y2="450.8"/>
<line class="st2" x1="118.9" y1="450.8" x2="221.8" y2="450.8"/>
<line class="st0" x1="223.2" y1="450.8" x2="226.2" y2="450.8"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

46
app/img/right-solid.php Normal file
View file

@ -0,0 +1,46 @@
<?php
header("Content-Type: image/svg+xml");
$showDevice1 = isset($_GET['device-1']);
$showOut = isset($_GET['out']);
$showDevice2 = isset($_GET['device-2']);
?>
<svg width="313" height="594" viewBox="0 0 313 594" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Frame 1">
<g id="right connection frame">
<g id="solid">
<?php if ($showDevice2): ?>
<line id="joint-device-2" y1="-0.75" x2="154" y2="-0.75"
transform="matrix(4.37114e-08 1 1 -4.37114e-08 114 297)"
stroke="#008281" stroke-width="4"/>
<?php endif; ?>
<?php if ($showDevice1): ?>
<line id="joint-device-1" style="display: inline;"
y1="-0.75" x2="154" y2="-0.75"
transform="matrix(4.37114e-08 1 1 -4.37114e-08 114 144)"
stroke="#008281" stroke-width="4"/>
<line id="device-1" style="display: inline;"
y1="-0.75" x2="113.231" y2="-0.75"
transform="matrix(1 8.74228e-08 8.74228e-08 -1 114 144)"
stroke="#008281" stroke-width="4"/>
<?php endif; ?>
<?php if ($showOut): ?>
<line id="out" style="display: inline;"
y1="-0.75" x2="113.231" y2="-0.75"
transform="matrix(1 8.74228e-08 8.74228e-08 -1 -0.000305176 297)"
stroke="#008281" stroke-width="4"/>
<?php endif; ?>
<?php if ($showDevice2): ?>
<line id="device-2" style="display: inline;"
y1="-0.75" x2="113.231" y2="-0.75"
transform="matrix(1 8.74228e-08 8.74228e-08 -1 113 450)"
stroke="#008281" stroke-width="4"/>
<?php endif; ?>
</g>
</g>
</g>
</svg>

65
app/img/solid.php Normal file
View file

@ -0,0 +1,65 @@
<?php
header("Content-Type: image/svg+xml");
require_once '../../includes/functions.php';
$color = getColorOpt();
$showJoint = isset($_GET['joint']);
$showDevice1 = isset($_GET['device-1']);
$showOut = isset($_GET['out']);
$showDevice2 = isset($_GET['device-2']);
$showDevice3 = isset($_GET['device-3']);
$showDevice4 = isset($_GET['device-4']);
?>
<svg xmlns="http://www.w3.org/2000/svg" width="227" height="596" viewBox="0 0 227 596" fill="none">
<?php
// Device positions array (y-coordinates)
$devicePositions = [
'device-1' => 0.75,
'out' => 297.75,
'device-2' => 198.75,
'device-3' => 397.058,
'device-4' => 595.211
];
// Calculate joint line segments
if ($showJoint) {
$activeDevices = array_filter([$showDevice1, $showDevice2, $showDevice3, $showDevice4]);
$activeYs = [];
foreach ($devicePositions as $device => $y) {
if (isset($_GET[$device])) {
$activeYs[] = $y;
}
}
// Add top/bottom if first/last device is connected
if ($showDevice1) array_unshift($activeYs, 0);
if ($showDevice4) $activeYs[] = 596;
// Draw segments between consecutive points
for ($i = 1; $i < count($activeYs); $i++) {
$y1 = $activeYs[$i-1];
$y2 = $activeYs[$i];
echo "<line x1='112.75' y1='$y1' x2='112.75' y2='$y2' stroke='$color' stroke-width='4'/>";
}
}
?>
<?php if ($showDevice1): ?>
<line x1="113.231" y1="0.75" x2="7.69496e-06" y2="0.75001" stroke="<?php echo $color; ?>" stroke-width="6" id="device-1"/>
<?php endif; ?>
<?php if ($showOut): ?>
<line x1="226.231" y1="297.75" x2="113" y2="297.75" stroke="<?php echo $color; ?>" stroke-width="4" id="out"/>
<?php endif; ?>
<?php if ($showDevice2): ?>
<line x1="113.231" y1="198.75" x2="7.69496e-06" y2="198.75" stroke="<?php echo $color; ?>" stroke-width="4" id="device-2"/>
<?php endif; ?>
<?php if ($showDevice3): ?>
<line x1="113.231" y1="397.058" x2="7.69496e-06" y2="397.058" stroke="<?php echo $color; ?>" stroke-width="4" id="device-3"/>
<?php endif; ?>
<?php if ($showDevice4): ?>
<line x1="113.231" y1="595.211" x2="7.69496e-06" y2="595.211" stroke="<?php echo $color; ?>" stroke-width="4" id="device-4"/>
<?php endif; ?>
</svg>

29
app/img/uri-qr-code.php Executable file
View file

@ -0,0 +1,29 @@
<?php
if (!isset($_GET['uri']) || !filter_var($_GET['uri'], FILTER_VALIDATE_URL)) {
header("HTTP/1.1 400 Bad Request");
exit("Invalid or missing URI parameter");
}
$uri = $_GET['uri'];
$command = "qrencode -t svg -m 0 -o - " . escapeshellarg($uri);
$svg = shell_exec($command);
if ($svg === null) {
error_log("QR generation failed for URI: $uri");
header("HTTP/1.1 500 Internal Server Error");
exit("Failed to generate QR code");
}
$etag = hash('sha256', $uri);
$content_length = strlen($svg);
$last_modified = gmdate("D, d M Y H:i:s") . " GMT";
header("Content-Type: image/svg+xml");
header("Content-Length: $content_length");
header("Last-Modified: $last_modified");
header("ETag: \"$etag\"");
header("X-QR-Code-Content: " . htmlspecialchars($uri, ENT_QUOTES, 'UTF-8'));
echo $svg;

3
app/img/wg-qr-code.php Normal file → Executable file
View file

@ -13,6 +13,7 @@ if (!isset($_SERVER['HTTP_REFERER'])) {
exec("sudo cat " .RASPI_WIREGUARD_PATH.'client.conf', $return);
$peer_conf = implode(PHP_EOL,$return);
$peer_conf.= PHP_EOL;
$peer_conf_sanitized = str_replace(["\r", "\n"], '', $peer_conf);
$command = "qrencode -t svg -m 0 -o - " . mb_escapeshellarg($peer_conf);
$svg = shell_exec($command);
$etag = hash('sha256', $peer_conf);
@ -23,6 +24,6 @@ header("Content-Type: image/svg+xml");
header("Content-Length: $content_length");
header("Last-Modified: $last_modified");
header("ETag: \"$etag\"");
header("X-QR-Code-Content: $peer_conf");
header("X-QR-Code-Content: $peer_conf_sanitized");
echo shell_exec($command);

8
app/img/wifi-qr-code.php Normal file → Executable file
View file

@ -12,12 +12,12 @@ if (!isset($_SERVER['HTTP_REFERER'])) {
$hostapd = parse_ini_file(RASPI_HOSTAPD_CONFIG, false, INI_SCANNER_RAW);
// assume wpa encryption and get the passphrase
// assume WPA encryption and get the passphrase
$type = "WPA";
$password = isset($hostapd['wpa_psk']) ? $hostapd['wpa_psk'] : $hostapd['wpa_passphrase'];
// use wep if configured
$wep_default_key = intval($hostapd['wep_default_key']);
// use WEP if configured
$wep_default_key = intval($hostapd['wep_default_key'] ?? 0);
$wep_key = 'wep_key' . $wep_default_key;
if (array_key_exists($wep_key, $hostapd)) {
$type = "WEP";
@ -30,7 +30,7 @@ if (empty($password)) {
}
$ssid = $hostapd['ssid'];
$hidden = intval($hostapd['ignore_broadcast_ssid']) != 0 ? "H:true" : "";
$hidden = intval($hostapd['ignore_broadcast_ssid'] ?? 0) !== 0 ? "H:true" : "";
$ssid = qr_encode($ssid);
$password = qr_encode($password);

View file

@ -332,47 +332,42 @@ $('#performupdateModal').on('shown.bs.modal', function (e) {
});
function fetchUpdateResponse() {
const xhr = new XMLHttpRequest();
const complete = 6;
const error = 7;
let phpFile = 'ajax/system/sys_read_logfile.php';
$.ajax({
url: phpFile,
type: 'GET',
success: function(response) {
let endPolling = false;
success: function(response) {
for (let i = 1; i <= 6; i++) {
let divId = '#updateStep' + i;
if (response.includes(i.toString())) {
$(divId).removeClass('invisible');
}
if (response.includes(complete)) {
var successMsg = $('#successMsg').data('message');
$('#updateMsg').after('<span class="small">' + successMsg + '</span>');
$('#updateMsg').addClass('fa-check');
$('#updateMsg').removeClass('invisible');
$('#updateStep6').removeClass('invisible');
$('#updateSync2').removeClass("fa-spin");
$('#updateOk').removeAttr('disabled');
endPolling = true;
break;
} else if (response.includes(error)) {
var errorMsg = $('#errorMsg').data('message');
$('#updateMsg').after('<span class="small">' + errorMsg + '</span>');
$('#updateMsg').addClass('fa-times');
$('#updateMsg').removeClass('invisible');
$('#updateSync2').removeClass("fa-spin");
$('#updateOk').removeAttr('disabled');
endPolling = true;
break;
}
}
if (!endPolling) {
// check if the update is complete or if there's an error
if (response.includes(complete)) {
var successMsg = $('#successMsg').data('message');
$('#updateMsg').after('<span class="small">' + successMsg + '</span>');
$('#updateMsg').addClass('fa-check');
$('#updateMsg').removeClass('invisible');
$('#updateStep6').removeClass('invisible');
$('#updateSync2').removeClass("fa-spin");
$('#updateOk').removeAttr('disabled');
} else if (response.includes(error)) {
var errorMsg = $('#errorMsg').data('message');
$('#updateMsg').after('<span class="small">' + errorMsg + '</span>');
$('#updateMsg').addClass('fa-times');
$('#updateMsg').removeClass('invisible');
$('#updateSync2').removeClass("fa-spin");
$('#updateOk').removeAttr('disabled');
} else {
setTimeout(fetchUpdateResponse, 500);
}
},
error: function(xhr, status, error) {
console.error(error);
console.error("AJAX Error:", error);
}
});
}
@ -468,6 +463,139 @@ $('#js-sys-reboot, #js-sys-shutdown').on('click', function (e) {
});
});
$('#install-user-plugin').on('shown.bs.modal', function (e) {
var button = $(e.relatedTarget);
$(this).data('button', button);
var manifestData = button.data('plugin-manifest');
var installed = button.data('plugin-installed') || false;
var repoPublic = button.data('repo-public') || false;
var installPath = manifestData.install_path;
if (!installed && repoPublic && installPath === 'plugins-available') {
insidersHTML = 'Available with <i class="fas fa-heart heart me-1"></i><a href="https://docs.raspap.com/insiders" target="_blank" rel="noopener">Insiders</a>';
$('#plugin-additional').html(insidersHTML);
} else {
$('#plugin-additional').empty();
}
if (manifestData) {
$('#plugin-docs').html(manifestData.plugin_docs
? `<a href="${manifestData.plugin_docs}" target="_blank">${manifestData.plugin_docs}</a>`
: 'Unknown');
$('#plugin-icon').attr('class', `${manifestData.icon || 'fas fa-plug'} link-secondary h5 me-2`);
$('#plugin-name').text(manifestData.name || 'Unknown');
$('#plugin-version').text(manifestData.version || 'Unknown');
$('#plugin-description').text(manifestData.description || 'No description provided');
$('#plugin-author').html(manifestData.author
? manifestData.author + (manifestData.author_uri
? ` (<a href="${manifestData.author_uri}" target="_blank">profile</a>)` : '') : 'Unknown');
$('#plugin-license').text(manifestData.license || 'Unknown');
$('#plugin-locale').text(manifestData.default_locale || 'Unknown');
$('#plugin-configuration').html(formatProperty(manifestData.configuration || 'None'));
$('#plugin-packages').html(formatProperty(manifestData.keys || 'None'));
$('#plugin-dependencies').html(formatProperty(manifestData.dependencies || 'None'));
$('#plugin-javascript').html(formatProperty(manifestData.javascript || 'None'));
$('#plugin-sudoers').html(formatProperty(manifestData.sudoers || 'None'));
$('#plugin-user-name').html((manifestData.user_nonprivileged && manifestData.user_nonprivileged.name) || 'None');
}
if (installed) {
$('#js-install-plugin-confirm').html('OK');
} else if (!installed && repoPublic && installPath == 'plugins-available') {
$('#js-install-plugin-confirm').html('Get Insiders');
} else {
$('#js-install-plugin-confirm').html('Install now');
}
});
$('#js-install-plugin-confirm').on('click', function (e) {
var button = $('#install-user-plugin').data('button');
var manifestData = button.data('plugin-manifest');
var installPath = manifestData.install_path;
var pluginUri = manifestData.plugin_uri;
var pluginVersion = manifestData.version;
var pluginConfirm = $('#js-install-plugin-confirm').text();
var progressText = $('#js-install-plugin-confirm').attr('data-message');
var successHtml = $('#plugin-install-message').attr('data-message');
var successText = $('<div>').text(successHtml).text();
var csrfToken = $('meta[name=csrf_token]').attr('content');
if (pluginConfirm === 'Install now') {
$("#install-user-plugin").modal('hide');
$("#install-plugin-progress").modal('show');
$.post(
'ajax/plugins/do_plugin_install.php',
{
'plugin_uri': pluginUri,
'plugin_version': pluginVersion,
'install_path': installPath,
'csrf_token': csrfToken
},
function (data) {
setTimeout(function () {
response = JSON.parse(data);
if (response === true) {
$('#plugin-install-message').contents().first().text(successText);
$('#plugin-install-message')
.find('i')
.removeClass('fas fa-cog fa-spin link-secondary')
.addClass('fas fa-check');
$('#js-install-plugin-ok').removeAttr("disabled");
} else {
const errorMessage = jsonData.error || 'An unknown error occurred.';
var errorLog = '<textarea class="plugin-log text-secondary" readonly>' + errorMessage + '</textarea>';
$('#plugin-install-message')
.contents()
.first()
.replaceWith('An error occurred installing the plugin:');
$('#plugin-install-message').append(errorLog);
$('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary');
$('#js-install-plugin-ok').removeAttr("disabled");
}
}, 200);
}
).fail(function (xhr) {
const jsonData = JSON.parse(xhr.responseText);
const errorMessage = jsonData.error || 'An unknown error occurred.';
$('#plugin-install-message')
.contents()
.first()
.replaceWith('An error occurred installing the plugin:');
var errorLog = '<textarea class="plugin-log text-secondary" readonly>' + errorMessage + '</textarea>';
$('#plugin-install-message').append(errorLog);
$('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary');
$('#js-install-plugin-ok').removeAttr("disabled");
});
} else if (pluginConfirm === 'Get Insiders') {
window.open('https://docs.raspap.com/insiders/', '_blank');
return;
} else if (pluginConfirm === 'OK') {
$("#install-user-plugin").modal('hide');
}
});
$('#js-install-plugin-ok').on('click', function (e) {
$("#install-plugin-progress").modal('hide');
window.location.reload();
});
function formatProperty(prop) {
if (Array.isArray(prop)) {
if (typeof prop[0] === 'object') {
return prop.map(item => {
return Object.entries(item)
.map(([key, value]) => `${key}: ${value}`)
.join('<br/>');
}).join('<br/><br/>');
}
return prop.map(line => `${line}<br/>`).join('');
}
if (typeof prop === 'object') {
return Object.entries(prop)
.map(([key, value]) => `${key}: ${value}`)
.join('<br/>');
}
return prop || 'None';
}
$(document).ready(function(){
$("#PanelManual").hide();
$('.ip_address').mask('0ZZ.0ZZ.0ZZ.0ZZ', {
@ -507,7 +635,6 @@ $('#wg-upload,#wg-manual').on('click', function (e) {
}
});
// Add the following code if you want the name of the file appear on select
$(".custom-file-input").on("change", function() {
var fileName = $(this).val().split("\\").pop();
$(this).siblings(".custom-file-label").addClass("selected").html(fileName);
@ -623,18 +750,18 @@ function clearBlocklistStatus() {
$('#cbxblocklist-status').removeClass('check-updated').addClass('check-hidden');
}
// Handler for the wireguard generate key button
// Handler for the WireGuard generate key button
$('.wg-keygen').click(function(){
var entity_pub = $(this).parent('div').prev('input[type="text"]');
var entity_priv = $(this).parent('div').next('input[type="hidden"]');
var parentGroup = $(this).closest('.input-group');
var entity_pub = parentGroup.find('input[type="text"]');
var updated = entity_pub.attr('name')+"-pubkey-status";
var csrfToken = $('meta[name=csrf_token]').attr('content');
var csrfToken = $('meta[name="csrf_token"]').attr('content');
$.post('ajax/networking/get_wgkey.php',{'entity':entity_pub.attr('name'), 'csrf_token': csrfToken},function(data){
var jsonData = JSON.parse(data);
entity_pub.val(jsonData.pubkey);
$('#' + updated).removeClass('check-hidden').addClass('check-updated').delay(500).animate({ opacity: 1 }, 700);
})
})
});
});
// Handler for wireguard client.conf download
$('.wg-client-dl').click(function(){
@ -671,6 +798,44 @@ window.addEventListener('load', function() {
});
}, false);
let sessionCheckInterval = setInterval(checkSession, 5000);
function checkSession() {
// skip session check if on login page
if (window.location.pathname === '/login') {
return;
}
var csrfToken = $('meta[name=csrf_token]').attr('content');
$.post('ajax/session/do_check_session.php',{'csrf_token': csrfToken},function (data) {
if (data.status === 'session_expired') {
clearInterval(sessionCheckInterval);
showSessionExpiredModal();
}
}).fail(function (jqXHR, status, err) {
console.error("Error checking session status:", status, err);
});
}
function showSessionExpiredModal() {
$('#sessionTimeoutModal').modal('show');
}
$(document).on("click", "#js-session-expired-login", function(e) {
const loginModal = $('#modal-admin-login');
const redirectUrl = window.location.pathname;
window.location.href = `/login?action=${encodeURIComponent(redirectUrl)}`;
});
// show modal login on page load
$(document).ready(function () {
const params = new URLSearchParams(window.location.search);
const redirectUrl = $('#redirect-url').val() || params.get('action') || '/';
$('#modal-admin-login').modal('show');
$('#redirect-url').val(redirectUrl);
$('#username').focus();
$('#username').addClass("focusedInput");
});
// DHCP or Static IP option group
$('#chkstatic').on('change', function() {
if (this.checked) {
@ -825,42 +990,9 @@ function getCookie(cname) {
// Define themes
var themes = {
"default": "custom.php",
"hackernews" : "hackernews.css",
"lightsout" : "lightsout.php",
"material-light" : "material-light.php",
"material-dark" : "material-dark.php",
"hackernews" : "hackernews.css"
}
// Toggles the sidebar navigation.
// Overrides the default SB Admin 2 behavior
$("#sidebarToggleTopbar").on('click', function(e) {
$("body").toggleClass("sidebar-toggled");
$(".sidebar").toggleClass("toggled d-none");
});
// Overrides SB Admin 2
$("#sidebarToggle, #sidebarToggleTop").on('click', function(e) {
var toggled = $(".sidebar").hasClass("toggled");
// Persist state in cookie
setCookie('sidebarToggled',toggled, 90);
});
$(function() {
if ($(window).width() < 768) {
$('.sidebar').addClass('toggled');
setCookie('sidebarToggled',false, 90);
}
});
$(window).on("load resize",function(e) {
if ($(window).width() > 768) {
$('.sidebar').removeClass('d-none d-md-block');
if (getCookie('sidebarToggled') == 'false') {
$('.sidebar').removeClass('toggled');
}
}
});
// Adds active class to current nav-item
$(window).bind("load", function() {
var url = window.location;
@ -869,6 +1001,27 @@ $(window).bind("load", function() {
}).parent().addClass('active');
});
// Sets focus on a specified tab
document.addEventListener("DOMContentLoaded", function () {
const params = new URLSearchParams(window.location.search);
const targetTab = params.get("tab");
if (targetTab) {
let tabElement = document.querySelector(`[data-bs-toggle="tab"][href="#${targetTab}"]`);
if (tabElement) {
let tab = new bootstrap.Tab(tabElement);
tab.show();
}
}
});
function disableValidation(form) {
form.removeAttribute("novalidate");
form.classList.remove("needs-validation");
form.querySelectorAll("[required]").forEach(function (field) {
field.removeAttribute("required");
});
}
$(document).ready(function() {
const $htmlElement = $('html');
const $modeswitch = $('#night-mode');

View file

@ -1,6 +1,7 @@
<?php
define('RASPI_BRAND_TEXT', 'RaspAP');
define('RASPI_BRAND_TITLE', RASPI_BRAND_TEXT.' Admin Panel');
define('RASPI_CONFIG', '/etc/raspap');
define('RASPI_CONFIG_NETWORK', RASPI_CONFIG.'/networking/defaults.json');
define('RASPI_CONFIG_PROVIDERS', 'config/vpn-providers.json');
@ -11,6 +12,7 @@ define('RASPI_CACHE_PATH', sys_get_temp_dir() . '/raspap');
define('RASPI_ERROR_LOG', sys_get_temp_dir() . '/raspap_error.log');
define('RASPI_DEBUG_LOG', 'raspap_debug.log');
define('RASPI_LOG_SIZE_LIMIT', 64);
define('RASPI_SESSION_TIMEOUT', 1440);
// Constants for configuration file paths.
// These are typical for default RPi installs. Modify if needed.
@ -61,6 +63,7 @@ define('RASPI_VNSTAT_ENABLED', true);
define('RASPI_SYSTEM_ENABLED', true);
define('RASPI_MONITOR_ENABLED', false);
define('RASPI_RESTAPI_ENABLED', false);
define('RASPI_PLUGINS_ENABLED', true);
// Locale settings
define('LOCALE_ROOT', 'locale');

View file

@ -4,7 +4,7 @@ ctrl_interface_group=0
beacon_int=100
auth_algs=1
wpa_key_mgmt=WPA-PSK
ssid=raspi-webgui
ssid=RaspAP
channel=1
hw_mode=g
wpa_passphrase=ChangeMe

66
includes/CSRF.php Normal file
View file

@ -0,0 +1,66 @@
<?php
namespace RaspAP\Tokens;
class CSRF
{
protected static ?CSRFTokenizer $instance = null;
/*
* Get the CSRFTokenizer instance (singleton)
*
* @return CSRFTokenizer
*/
public static function instance(): CSRFTokenizer
{
if (self::$instance === null) {
self::$instance = new CSRFTokenizer();
}
return self::$instance;
}
public static function token(): string
{
return self::instance()->getToken();
}
public static function verify(): bool
{
$token = $_POST['csrf_token'];
return self::instance()->csrfValidateRequest() &&
self::instance()->CSRFValidate($_POST['csrf_token'] ?? '');
}
public static function metaTag(): string
{
return self::instance()->CSRFMetaTag();
}
public static function hiddenField(): string
{
return self::instance()->CSRFTokenFieldTag();
}
public static function handleInvalidToken(): void
{
self::instance()->handleInvalidCSRFToken();
}
/**
* Validates a CSRF Request
*
* @return bool
*/
public static function validateRequest(): bool
{
return self::instance()->csrfValidateRequest();
}
}
if (\RaspAP\Tokens\CSRF::validateRequest()) {
if (!\RaspAP\Tokens\CSRF::verify()) {
error_log("CSRF verification failed. Token: " . ($_POST['csrf_token'] ?? 'not provided'));
\RaspAP\Tokens\CSRF::handleInvalidToken();
}
}

View file

@ -40,7 +40,6 @@ function DisplayAdBlockConfig()
file_put_contents("/tmp/dnsmasq_custom", $_POST['adblock-custom-hosts'].PHP_EOL);
system("sudo cp /tmp/dnsmasq_custom " .RASPI_ADBLOCK_LISTPATH .'custom.txt', $return);
$config.= 'addn-hosts=' .RASPI_ADBLOCK_LISTPATH .'custom.txt'.PHP_EOL;
$custom_enabled = true;
}
if (empty($errors)) {
@ -64,6 +63,13 @@ function DisplayAdBlockConfig()
}
}
$custom_list = RASPI_ADBLOCK_LISTPATH . 'custom.txt';
$custom_enabled = false;
if (file_exists($custom_list) && filesize($custom_list) > 0) {
$custom_enabled = true;
}
exec('cat '. RASPI_ADBLOCK_CONFIG, $return);
$arrConf = ParseConfig($return);
if (sizeof($arrConf) > 0) {

View file

@ -35,6 +35,8 @@ function DisplayAuthConfig($username)
} else {
$status->addMessage('Old password does not match', 'danger');
}
} elseif (isset($_POST['logout'])) {
$auth->logout();
}
echo renderTemplate(

View file

@ -1,16 +1,10 @@
<?php
if (RASPI_AUTH_ENABLED) {
$user = $_SERVER['PHP_AUTH_USER'] ?? '';
$pass = $_SERVER['PHP_AUTH_PW'] ?? '';
$auth = new \RaspAP\Auth\HTTPAuth;
if (!$auth->isLogged()) {
if ($auth->login($user, $pass)) {
$config = $auth->getAuthConfig();
} else {
$auth->authenticate();
}
$auth->authenticate();
}
}

View file

@ -9,31 +9,13 @@
*/
spl_autoload_register(function ($class) {
// project-specific namespace prefix
$prefix = '';
// base directory where all class files are stored
$base_dir = __DIR__ . '/../src/';
// base directory for the namespace prefix
$base_dir = 'src/';
// convert the fully qualified class name into a file path
$file = $base_dir . str_replace('\\', '/', $class) . '.php';
// normalize the base directory with a trailing separator
$base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';
// does the class use the namespace prefix?
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
// no, move to the next registered autoloader
return;
}
// get the relative class name
$relative_class = substr($class, $len);
// replace the namespace prefix with the base directory, replace namespace
// separators with directory separators in the relative class name, append
// with .php
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
// if the file exists, require it
// require the file if it exists
if (file_exists($file)) {
require $file;
}

View file

@ -1,8 +0,0 @@
<?php
require_once 'functions.php';
require_once 'session.php';
if (csrfValidateRequest() && !CSRFValidate()) {
handleInvalidCSRFToken();
}

View file

@ -5,226 +5,170 @@ require_once 'includes/wifi_functions.php';
require_once 'includes/functions.php';
/**
* Show dashboard page.
* Displays the dashboard
*/
function DisplayDashboard(&$extraFooterScripts)
function DisplayDashboard(&$extraFooterScripts): void
{
getWifiInterface();
// instantiate RaspAP objects
$system = new \RaspAP\System\Sysinfo;
$dashboard = new \RaspAP\UI\Dashboard;
$status = new \RaspAP\Messages\StatusMessage;
// Need this check interface name for proper shell execution.
if (!preg_match('/^([a-zA-Z0-9]+)$/', $_SESSION['wifi_client_interface'])) {
$status->addMessage(_('Interface name invalid.'), 'danger');
$status->showMessages();
return;
}
$pluginManager = \RaspAP\Plugins\PluginManager::getInstance();
if (!function_exists('exec')) {
$status->addMessage(_('Required exec function is disabled. Check if exec is not added to php disable_functions.'), 'danger');
$status->showMessages();
return;
}
exec('ip a show '.$_SESSION['ap_interface'], $stdoutIp);
$stdoutIpAllLinesGlued = implode(" ", $stdoutIp);
$stdoutIpWRepeatedSpaces = preg_replace('/\s\s+/', ' ', $stdoutIpAllLinesGlued);
// set AP and client interface session vars
getWifiInterface();
preg_match('/link\/ether ([0-9a-f:]+)/i', $stdoutIpWRepeatedSpaces, $matchesMacAddr) || $matchesMacAddr[1] = _('No MAC Address Found');
$macAddr = $matchesMacAddr[1];
$ipv4Addrs = '';
$ipv4Netmasks = '';
if (!preg_match_all('/inet (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/([0-3][0-9])/i', $stdoutIpWRepeatedSpaces, $matchesIpv4AddrAndSubnet, PREG_SET_ORDER)) {
$ipv4Addrs = _('No IPv4 Address Found');
} else {
foreach ($matchesIpv4AddrAndSubnet as $inet) {
$address = $inet[1];
$suffix = (int) $inet[2];
$netmask = long2ip(-1 << (32 - $suffix));
$ipv4Addrs .= " $address";
$ipv4Netmasks .= " $netmask";
}
$ipv4Addrs = trim($ipv4Addrs);
$ipv4Netmasks = trim($ipv4Netmasks);
}
$ipv4Netmasks = empty($ipv4Netmasks) ? "-" : $ipv4Netmasks;
$ipv6Addrs = '';
if (!preg_match_all('/inet6 ([a-f0-9:]+)/i', $stdoutIpWRepeatedSpaces, $matchesIpv6Addr)) {
$ipv6Addrs = _('No IPv6 Address Found');
} else {
if (isset($matchesIpv6Addr[1])) {
$ipv6Addrs = implode(' ', $matchesIpv6Addr[1]);
}
}
preg_match('/state (UP|DOWN)/i', $stdoutIpWRepeatedSpaces, $matchesState) || $matchesState[1] = 'unknown';
$interfaceState = $matchesState[1];
// Because of table layout used in the ip output we get the interface statistics directly from
// the system. One advantage of this is that it could work when interface is disable.
exec('cat /sys/class/net/'.$_SESSION['ap_interface'].'/statistics/rx_packets ', $stdoutCatRxPackets);
$strRxPackets = _('No data');
if (ctype_digit($stdoutCatRxPackets[0])) {
$strRxPackets = $stdoutCatRxPackets[0];
}
exec('cat /sys/class/net/'.$_SESSION['ap_interface'].'/statistics/tx_packets ', $stdoutCatTxPackets);
$strTxPackets = _('No data');
if (ctype_digit($stdoutCatTxPackets[0])) {
$strTxPackets = $stdoutCatTxPackets[0];
}
exec('cat /sys/class/net/'.$_SESSION['ap_interface'].'/statistics/rx_bytes ', $stdoutCatRxBytes);
$strRxBytes = _('No data');
if (ctype_digit($stdoutCatRxBytes[0])) {
$strRxBytes = $stdoutCatRxBytes[0];
$strRxBytes .= getHumanReadableDatasize($strRxBytes);
}
exec('cat /sys/class/net/'.$_SESSION['ap_interface'].'/statistics/tx_bytes ', $stdoutCatTxBytes);
$strTxBytes = _('No data');
if (ctype_digit($stdoutCatTxBytes[0])) {
$strTxBytes = $stdoutCatTxBytes[0];
$strTxBytes .= getHumanReadableDatasize($strTxBytes);
}
exec ('vnstat --dbiflist', $stdoutVnStatDB);
if (!preg_match('/'.$_SESSION['ap_interface'].'/', $stdoutVnStatDB[0])) {
exec('sudo vnstat --add --iface '.$_SESSION['ap_interface'], $return);
}
define('SSIDMAXLEN', 32);
// Warning iw comes with: "Do NOT screenscrape this tool, we don't consider its output stable."
exec('iw dev ' .$_SESSION['wifi_client_interface']. ' link ', $stdoutIw);
$stdoutIwAllLinesGlued = implode('+', $stdoutIw); // Break lines with character illegal in SSID and MAC addr
$stdoutIwWRepSpaces = preg_replace('/\s\s+/', ' ', $stdoutIwAllLinesGlued);
preg_match('/Connected to (([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2}))/', $stdoutIwWRepSpaces, $matchesBSSID) || $matchesBSSID[1] = '';
$connectedBSSID = $matchesBSSID[1];
$connectedBSSID = empty($connectedBSSID) ? "-" : $connectedBSSID;
$wlanHasLink = false;
if ($interfaceState === 'UP') {
$wlanHasLink = true;
}
if (!preg_match('/SSID: ([^+]{1,'.SSIDMAXLEN.'})/', $stdoutIwWRepSpaces, $matchesSSID)) {
$wlanHasLink = false;
$matchesSSID[1] = 'None';
}
$connectedSSID = str_replace('\x20', '', $matchesSSID[1]);
preg_match('/freq: (\d+)/i', $stdoutIwWRepSpaces, $matchesFrequency) || $matchesFrequency[1] = '';
$frequency = $matchesFrequency[1].' MHz';
preg_match('/signal: (-?[0-9]+ dBm)/i', $stdoutIwWRepSpaces, $matchesSignal) || $matchesSignal[1] = '';
$signalLevel = $matchesSignal[1];
$signalLevel = empty($signalLevel) ? "-" : $signalLevel;
preg_match('/tx bitrate: ([0-9\.]+ [KMGT]?Bit\/s)/', $stdoutIwWRepSpaces, $matchesBitrate) || $matchesBitrate[1] = '';
$bitrate = $matchesBitrate[1];
$bitrate = empty($bitrate) ? "-" : $bitrate;
// txpower is now displayed on iw dev(..) info command, not on link command.
exec('iw dev '.$_SESSION['wifi_client_interface'].' info ', $stdoutIwInfo);
$stdoutIwInfoAllLinesGlued = implode(' ', $stdoutIwInfo);
$stdoutIpInfoWRepSpaces = preg_replace('/\s\s+/', ' ', $stdoutIwInfoAllLinesGlued);
preg_match('/txpower ([0-9\.]+ dBm)/i', $stdoutIpInfoWRepSpaces, $matchesTxPower) || $matchesTxPower[1] = '';
$txPower = $matchesTxPower[1];
// iw does not have the "Link Quality". This is a is an aggregate value,
// and depends on the driver and hardware.
// Display link quality as signal quality for now.
$strLinkQuality = 0;
if ($signalLevel > -100 && $wlanHasLink) {
if ($signalLevel >= 0) {
$strLinkQuality = 100;
} else {
$strLinkQuality = 100 + intval($signalLevel);
}
}
$wlan0up = false;
$classMsgDevicestatus = 'warning';
if ($interfaceState === 'UP') {
$wlan0up = true;
$classMsgDevicestatus = 'success';
}
if (!RASPI_MONITOR_ENABLED) {
if (isset($_POST['ifdown_wlan0'])) {
// Pressed stop button
if ($interfaceState === 'UP') {
$status->addMessage(sprintf(_('Interface is going %s.'), _('down')), 'warning');
exec('sudo ip link set '.$_SESSION['ap_interface'].' down');
$wlan0up = false;
$status->addMessage(sprintf(_('Interface is now %s.'), _('down')), 'success');
} elseif ($interfaceState === 'unknown') {
$status->addMessage(_('Interface state unknown.'), 'danger');
} else {
$status->addMessage(sprintf(_('Interface already %s.'), _('down')), 'warning');
}
} elseif (isset($_POST['ifup_wlan0'])) {
// Pressed start button
if ($interfaceState === 'DOWN') {
$status->addMessage(sprintf(_('Interface is going %s.'), _('up')), 'warning');
exec('sudo ip link set ' .$_SESSION['ap_interface']. ' up');
exec('sudo ip -s a f label ' .$_SESSION['ap_interface']);
$wlan0up = true;
$status->addMessage(sprintf(_('Interface is now %s.'), _('up')), 'success');
} elseif ($interfaceState === 'unknown') {
$status->addMessage(_('Interface state unknown.'), 'danger');
} else {
$status->addMessage(sprintf(_('Interface already %s.'), _('up')), 'warning');
}
} else {
$status->addMessage(sprintf(_('Interface is %s.'), strtolower($interfaceState)), $classMsgDevicestatus);
}
}
// brought in from template
$arrHostapdConf = parse_ini_file(RASPI_CONFIG.'/hostapd.ini');
$bridgedEnable = $arrHostapdConf['BridgedEnable'];
$interface = $_SESSION['ap_interface'] ?? 'wlan0';
$clientInterface = $_SESSION['wifi_client_interface'];
$apInterface = $_SESSION['ap_interface'];
$MACPattern = '"([[:xdigit:]]{2}:){5}[[:xdigit:]]{2}"';
$hostname = $system->hostname();
$revision = $system->rpiRevision();
$deviceImage = $dashboard->getDeviceImage($revision);
$hostapd = $system->hostapdStatus();
$adblock = $system->adBlockStatus();
$vpn = $system->getActiveVpnInterface();
$frequency = $dashboard->getFrequencyBand($interface);
$details = $dashboard->getInterfaceDetails($interface);
$wireless = $dashboard->getWirelessDetails($interface);
$connectionType = $dashboard->getConnectionType();
$connectionIcon = $dashboard->getConnectionIcon($connectionType);
$state = strtolower($details['state']);
$wirelessClients = $dashboard->getWirelessClients();
$ethernetClients = $dashboard->getEthernetClients();
$totalClients = $wirelessClients + $ethernetClients;
$plugins = $pluginManager->getInstalledPlugins();
$bridgedEnable = getBridgedState();
if (getBridgedState()) {
$moreLink = "hostapd_conf";
exec('iw dev ' . $apInterface . ' station dump | grep -oE ' . $MACPattern, $clients);
} else {
$moreLink = "dhcpd_conf";
exec('cat ' . RASPI_DNSMASQ_LEASES . '| grep -E $(iw dev ' . $apInterface . ' station dump | grep -oE ' . $MACPattern . ' | paste -sd "|")', $clients);
// handle page actions
if (!empty($_POST)) {
$status = $dashboard->handlePageAction($state, $_POST, $status, $interface);
// refresh interface details + state
$details = $dashboard->getInterfaceDetails($interface);
$state = strtolower($details['state']);
}
$ipv4Address = $details['ipv4'];
$ipv4Netmask = $details['ipv4_netmask'];
$macAddress = $details['mac'];
$ssid = $wireless['ssid'];
$ethernetActive = ($connectionType === 'ethernet') ? "active" : "inactive";
$wirelessActive = ($connectionType === 'wireless') ? "active" : "inactive";
$tetheringActive = ($connectionType === 'tethering') ? "active" : "inactive";
$cellularActive = ($connectionType === 'cellular') ? "active" : "inactive";
$bridgedStatus = ($bridgedEnable == 1) ? "active" : "";
$hostapdStatus = ($hostapd[0] == 1) ? "active" : "";
$adblockStatus = ($adblock == true) ? "active" : "";
$wirelessClientActive = ($wirelessClients > 0) ? "active" : "inactive";
$wirelessClientLabel = sprintf(
_('%d WLAN %s'),
$wirelessClients,
$dashboard->formatClientLabel($wirelessClients)
);
$ethernetClientActive = ($ethernetClients > 0) ? "active" : "inactive";
$ethernetClientLabel = sprintf(
_('%d LAN %s'),
$ethernetClients,
$dashboard->formatClientLabel($ethernetClients)
);
$totalClientsActive = ($totalClients > 0) ? "active": "inactive";
$freq5active = $freq24active = "";
$varName = "freq" . str_replace('.', '', $frequency) . "active";
$$varName = "active";
$vpnStatus = $vpn ? "active" : "inactive";
$vpnManaged = $vpn ? $dashboard->getVpnManaged($vpn) : null;
$firewallManaged = $firewallStatus = "";
$firewallInstalled = array_filter($plugins, fn($p) => str_ends_with($p, 'Firewall')) ? true : false;
if (!$firewallInstalled) {
$firewallUnavailable = '<i class="fas fa-slash fa-stack-1x"></i>';
} else {
$firewallManaged = '<a href="/plugin__Firewall">';
$firewallStatus = ($dashboard->firewallEnabled() == true) ? "active" : "";
}
$ifaceStatus = $wlan0up ? "up" : "down";
echo renderTemplate(
"dashboard", compact(
"clients",
"moreLink",
"apInterface",
"revision",
"deviceImage",
"interface",
"clientInterface",
"ifaceStatus",
"bridgedEnable",
"status",
"ipv4Addrs",
"ipv4Netmasks",
"ipv6Addrs",
"macAddr",
"strRxPackets",
"strRxBytes",
"strTxPackets",
"strTxBytes",
"connectedSSID",
"connectedBSSID",
"bitrate",
"signalLevel",
"txPower",
"state",
"bridgedStatus",
"hostapdStatus",
"adblockStatus",
"vpnStatus",
"vpnManaged",
"firewallUnavailable",
"firewallStatus",
"firewallManaged",
"ipv4Address",
"ipv4Netmask",
"macAddress",
"ssid",
"frequency",
"strLinkQuality",
"wlan0up"
"freq5active",
"freq24active",
"wirelessClients",
"wirelessClientLabel",
"wirelessClientActive",
"ethernetClients",
"ethernetClientLabel",
"ethernetClientActive",
"totalClients",
"totalClientsActive",
"connectionType",
"connectionIcon",
"ethernetActive",
"wirelessActive",
"tetheringActive",
"cellularActive",
"status"
)
);
$extraFooterScripts[] = array('src'=>'app/js/dashboardchart.js', 'defer'=>false);
$extraFooterScripts[] = array('src'=>'app/js/linkquality.js', 'defer'=>false);
}
/**
* Renders a URL for an svg solid line representing the associated
* connection type
*
* @param string $connectionType
* @return string
*/
function renderConnection(string $connectionType): string
{
$deviceMap = [
'ethernet' => 'device-1',
'wireless' => 'device-2',
'tethering' => 'device-3',
'cellular' => 'device-4'
];
$device = $deviceMap[$connectionType] ?? 'device-unknown';
// return generated URL for solid.php
return sprintf('app/img/solid.php?joint&%s&out', $device);
}
/**
* Renders a URL for an svg solid line representing associated
* client connection(s)
*
* @param int $wirelessClients
* @param int $ethernetClients
* @return string
*/
function renderClientConnections(int $wirelessClients, int $ethernetClients): string
{
$devices = [];
if ($wirelessClients > 0) {
$devices[] = 'device-1&out';
}
if ($ethernetClients > 0) {
$devices[] = 'device-2&out';
}
return empty($devices) ? '' : sprintf(
'<img src="app/img/right-solid.php?%s" class="solid-lines solid-lines-right" alt="Client connections">',
implode('&', $devices)
);
}

View file

@ -6,7 +6,8 @@ if (!defined('RASPI_CONFIG')) {
$defaults = [
'RASPI_BRAND_TEXT' => 'RaspAP',
'RASPI_VERSION' => '3.2.4',
'RASPI_BRAND_TITLE' => RASPI_BRAND_TEXT.' Admin Panel',
'RASPI_VERSION' => '3.3.2',
'RASPI_CONFIG_NETWORK' => RASPI_CONFIG.'/networking/defaults.json',
'RASPI_CONFIG_PROVIDERS' => 'config/vpn-providers.json',
'RASPI_CONFIG_API' => RASPI_CONFIG.'/api',
@ -16,6 +17,7 @@ $defaults = [
'RASPI_ERROR_LOG' => sys_get_temp_dir() . '/raspap_error.log',
'RASPI_DEBUG_LOG' => 'raspap_debug.log',
'RASPI_LOG_SIZE_LIMIT' => 64,
'RASPI_SESSION_TIMEOUT' => 1440,
// Constants for configuration file paths.
// These are typical for default RPi installs. Modify if needed.
@ -63,6 +65,7 @@ $defaults = [
'RASPI_SYSTEM_ENABLED' => true,
'RASPI_MONITOR_ENABLED' => false,
'RASPI_RESTAPI_ENABLED' => false,
'RASPI_PLUGINS_ENABLED' => true,
// Locale settings
'LOCALE_ROOT' => 'locale',

View file

@ -268,6 +268,9 @@ function updateDnsmasqConfig($iface,$status)
}
$config .= PHP_EOL;
}
if ($_POST['dhcp-ignore'] == "1") {
$config .= 'dhcp-ignore=tag:!known'.PHP_EOL;
}
file_put_contents("/tmp/dnsmasqdata", $config);
$msg = file_exists(RASPI_DNSMASQ_PREFIX.$iface.'.conf') ? 'updated' : 'added';
system('sudo cp /tmp/dnsmasqdata '.RASPI_DNSMASQ_PREFIX.$iface.'.conf', $result);

View file

@ -1,6 +0,0 @@
<?php
require_once 'src/RaspAP/Exceptions/ExceptionHandler.php';
$handler = new RaspAP\Exceptions\ExceptionHandler;
?>

24
includes/footer.php Normal file → Executable file
View file

@ -1,10 +1,28 @@
<div class="d-flex align-items-center justify-content-between small">
<?php $_SESSION['lastActivity'] = time(); ?>
<div class="d-flex align-items-center justify-content-between small">
<div class="text-muted">
<span class="pe-2"><a href="/about">v<?php echo RASPI_VERSION; ?></a></span> |
<span class="ps-2">Created by the <a href="https://github.com/RaspAP" target="_blank" rel="noopener">RaspAP Team</a></span>
<span class="ps-2"><?php echo sprintf(_('Created by the <a href="%s" target="_blank" rel="noopener">%s</a>'), 'https://github.com/RaspAP', _('RaspAP Team')); ?></span>
</div>
<div class="text-muted">
<i class="fas fa-heart heart"></i> <a href="https://docs.raspap.com/insiders" target="_blank" rel="noopener">Get Insiders</a>
<i class="fas fa-heart heart"></i> <a href="https://docs.raspap.com/insiders" target="_blank" rel="noopener"><?php echo _("Get Insiders"); ?></a>
</div>
</div>
<div class="modal fade" data-bs-backdrop="static" data-bs-keyboard="false" id="sessionTimeoutModal" tabindex="-1" aria-labelledby="sessionTimeoutLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title" id="sessionTimeoutLabel"><i class="fa fa-clock me-2"></i><?php echo _("Session Expired"); ?></div>
</div>
<div class="modal-body">
<?php echo _("Your session has expired. Please login to continue.") ?>
</div>
<div class="modal-footer">
<button type="button" id="js-session-expired-login" class="btn btn-outline btn-primary"><?php echo _("Login"); ?></button>
</div>
</div>
</div>
</div>

View file

@ -1,3 +1,4 @@
<?php require_once 'session.php'; ?>
<?php
/* Functions for Networking */
@ -176,15 +177,17 @@ function getDefaultNetOpts($svc,$key)
* @param string $key
* @return object $json
*/
function getProviderValue($id,$key)
function getProviderValue($id, $key)
{
$obj = json_decode(file_get_contents(RASPI_CONFIG_PROVIDERS), true);
if ($obj === null) {
if (!isset($obj['providers']) || !is_array($obj['providers'])) {
return false;
} else {
$id--;
return $obj['providers'][$id][$key];
}
$id--;
if (!isset($obj['providers'][$id]) || !is_array($obj['providers'][$id])) {
return false;
}
return $obj['providers'][$id][$key] ?? false;
}
/* Functions to write ini files */
@ -303,79 +306,6 @@ function filter_comments($var)
return $var[0] != '#';
}
/**
* Saves a CSRF token in the session
*/
function ensureCSRFSessionToken()
{
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
}
/**
* Add CSRF Token to form
*/
function CSRFTokenFieldTag()
{
$token = htmlspecialchars($_SESSION['csrf_token']);
return '<input type="hidden" name="csrf_token" value="' . $token . '">';
}
/**
* Retuns a CSRF meta tag (for use with xhr, for example)
*/
function CSRFMetaTag()
{
$token = htmlspecialchars($_SESSION['csrf_token']);
return '<meta name="csrf_token" content="' . $token . '">';
}
/**
* Validate CSRF Token
*/
function CSRFValidate()
{
if(isset($_POST['csrf_token'])) {
$post_token = $_POST['csrf_token'];
$header_token = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? null;
if (empty($post_token) && is_null($header_token)) {
return false;
}
$request_token = $post_token;
if (empty($post_token)) {
$request_token = $header_token;
}
if (hash_equals($_SESSION['csrf_token'], $request_token)) {
return true;
} else {
error_log('CSRF violation');
return false;
}
}
}
/**
* Should the request be CSRF-validated?
*/
function csrfValidateRequest()
{
$request_method = strtolower($_SERVER['REQUEST_METHOD']);
return in_array($request_method, [ "post", "put", "patch", "delete" ]);
}
/**
* Handle invalid CSRF
*/
function handleInvalidCSRFToken()
{
header('HTTP/1.1 500 Internal Server Error');
header('Content-Type: text/plain');
echo 'Invalid CSRF token';
exit;
}
/**
* Test whether array is associative
*/
@ -625,6 +555,13 @@ function mb_escapeshellarg($arg)
}
}
function safeOutputValue($def, $arr)
{
if (array_key_exists($def, $arr)) {
echo htmlspecialchars($arr[$def], ENT_QUOTES);
}
}
function dnsServers()
{
$data = json_decode(file_get_contents("./config/dns-servers.json"));
@ -700,7 +637,6 @@ function formatDateAgo($datetime, $full = false)
function initializeApp()
{
$_SESSION["theme_url"] = getThemeOpt();
$_SESSION["toggleState"] = getSidebarState();
$_SESSION["bridgedEnabled"] = getBridgedState();
$_SESSION["providerID"] = getProviderID();
}
@ -726,22 +662,17 @@ function getColorOpt()
return $color;
}
function getSidebarState()
{
if(isset($_COOKIE['sidebarToggled'])) {
if ($_COOKIE['sidebarToggled'] == 'true' ) {
return "toggled";
}
}
}
// Returns bridged AP mode status
function getBridgedState()
{
$arrHostapdConf = parse_ini_file(RASPI_CONFIG.'/hostapd.ini');
// defaults to false
$hostapdIni = RASPI_CONFIG . '/hostapd.ini';
if (!file_exists($hostapdIni)) {
return 0;
} else {
$arrHostapdConf = parse_ini_file($hostapdIni);
}
return $arrHostapdConf['BridgedEnable'];
}
}
// Returns VPN provider ID, if defined
function getProviderID()
@ -1036,3 +967,25 @@ function renderStatus($hostapd_led, $hostapd_status, $memused_led, $memused, $cp
<?php
}
/**
* Executes a callback with a timeout
*
* @param callable $callback function to execute
* @param int $interval timeout in milliseconds
* @return mixed result of the callback
* @throws \Exception if the execution exceeds the timeout or an error occurs
*/
function callbackTimeout(callable $callback, int $interval)
{
$startTime = microtime(true); // use high-resolution timer
$result = $callback();
$elapsed = (microtime(true) - $startTime) * 1000;
if ($elapsed > $interval) {
throw new \Exception('Operation timed out');
}
return $result;
}

View file

@ -19,7 +19,7 @@ function DisplayHostAPDConfig()
'a' => '802.11a - 5 GHz',
'b' => '802.11b - 2.4 GHz',
'g' => '802.11g - 2.4 GHz',
'n' => '802.11n - 2.4 GHz',
'n' => '802.11n - 2.4/5 GHz',
'ac' => '802.11ac - 5 GHz'
];
$languageCode = strtok($_SESSION['locale'], '_');
@ -46,7 +46,12 @@ function DisplayHostAPDConfig()
SaveHostAPDConfig($arrSecurity, $arrEncType, $arr80211Standard, $interfaces, $reg_domain, $status);
}
}
$arrHostapdConf = parse_ini_file(RASPI_CONFIG.'/hostapd.ini');
$arrHostapdConf = [];
$hostapdIni = RASPI_CONFIG . '/hostapd.ini';
if (file_exists($hostapdIni)) {
$arrHostapdConf = parse_ini_file($hostapdIni);
}
if (!RASPI_MONITOR_ENABLED) {
if (isset($_POST['StartHotspot']) || isset($_POST['RestartHotspot'])) {
@ -136,6 +141,9 @@ function DisplayHostAPDConfig()
}
}
$arrConfig['ignore_broadcast_ssid'] ??= 0;
$arrConfig['max_num_sta'] ??= 0;
$arrConfig['wep_default_key'] ??= 0;
exec('sudo /bin/chmod o+r '.RASPI_HOSTAPD_LOG);
$logdata = getLogLimited(RASPI_HOSTAPD_LOG);
@ -281,18 +289,13 @@ function SaveHostAPDConfig($wpa_array, $enc_types, $modes, $interfaces, $reg_dom
$good_input = false;
}
if (isset($_POST['hiddenSSID'])) {
if (!is_int((int)$_POST['hiddenSSID'])) {
$status->addMessage('Parameter hiddenSSID not a number.', 'danger');
$good_input = false;
} elseif ((int)$_POST['hiddenSSID'] < 0 || (int)$_POST['hiddenSSID'] >= 3) {
$status->addMessage('Parameter hiddenSSID contains invalid configuratie value.', 'danger');
$good_input = false;
} else {
$ignore_broadcast_ssid = $_POST['hiddenSSID'];
}
} else {
$ignore_broadcast_ssid = '0';
$ignore_broadcast_ssid = $_POST['hiddenSSID'] ?? '0';
if (!ctype_digit($ignore_broadcast_ssid)) {
$status->addMessage('Parameter hiddenSSID not a number.', 'danger');
$good_input = false;
} elseif ((int)$ignore_broadcast_ssid < 0 || (int)$ignore_broadcast_ssid >= 3) {
$status->addMessage('Parameter hiddenSSID contains an invalid configuration value.', 'danger');
$good_input = false;
}
if (! in_array($_POST['interface'], $interfaces)) {
@ -364,14 +367,17 @@ function SaveHostAPDConfig($wpa_array, $enc_types, $modes, $interfaces, $reg_dom
// Set dhcp values from system config, fallback to default if undefined
$jsonData = json_decode(getNetConfig($ap_iface), true);
$ip_address = ($jsonData['StaticIP'] == '') ? getDefaultNetValue('dhcp',$ap_iface,'static ip_address') : $jsonData['StaticIP'];
$domain_name_server = ($jsonData['StaticDNS'] =='') ? getDefaultNetValue('dhcp',$ap_iface,'static domain_name_server') : $jsonData['StaticDNS'];
$routers = ($jsonData['StaticRouters'] == '') ? getDefaultNetValue('dhcp',$ap_iface,'static routers') : $jsonData['StaticRouters'];
$netmask = ($jsonData['SubnetMask'] == '' || $jsonData['SubnetMask'] == '0.0.0.0') ? getDefaultNetValue('dhcp',$ap_iface,'subnetmask') : $jsonData['SubnetMask'];
$ip_address = empty($jsonData['StaticIP'])
? getDefaultNetValue('dhcp', $ap_iface, 'static ip_address') : $jsonData['StaticIP'];
$domain_name_server = empty($jsonData['StaticDNS'])
? getDefaultNetValue('dhcp', $ap_iface, 'static domain_name_server') : $jsonData['StaticDNS'];
$routers = empty($jsonData['StaticRouters'])
? getDefaultNetValue('dhcp', $ap_iface, 'static routers') : $jsonData['StaticRouters'];
$netmask = (empty($jsonData['SubnetMask']) || $jsonData['SubnetMask'] === '0.0.0.0')
? getDefaultNetValue('dhcp', $ap_iface, 'subnetmask') : $jsonData['SubnetMask'];
if (isset($ip_address) && !preg_match('/.*\/\d+/', $ip_address)) {
$ip_address.='/'.mask2cidr($netmask);
}
if ($bridgedEnable == 1) {
$config = array_keys(getDefaultNetOpts('dhcp','options'));
$config[] = PHP_EOL.'# RaspAP br0 configuration';
@ -392,7 +398,9 @@ function SaveHostAPDConfig($wpa_array, $enc_types, $modes, $interfaces, $reg_dom
$config[] = 'static ip_address='.$ip_address;
$config[] = 'static routers='.$routers;
$config[] = 'static domain_name_server='.$domain_name_server;
if (! is_null($jsonData['Metric'])) { $config[] = 'metric '.$jsonData['Metric']; }
if (!empty($jsonData['Metric'])) {
$config[] = 'metric ' . $jsonData['Metric'];
}
}
$dhcp_cfg = file_get_contents(RASPI_DHCPCD_CONFIG);

View file

@ -11,7 +11,7 @@
*
* Refer to: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
*/
if (empty($_SESSION['locale']) && strlen($_SERVER['HTTP_ACCEPT_LANGUAGE']) >= 2) {
if (empty($_SESSION['locale']) && !empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) && strlen($_SERVER['HTTP_ACCEPT_LANGUAGE']) >= 2) {
$lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
switch ($lang) {
case "de":
@ -90,9 +90,10 @@ if (empty($_SESSION['locale']) && strlen($_SERVER['HTTP_ACCEPT_LANGUAGE']) >= 2)
// Use: 'sudo raspi-configure' and select 'Localisation Options'
// activate the locale setting
putenv("LANG=" . $_SESSION['locale']);
setlocale(LC_ALL, $_SESSION['locale']);
if (!empty($_SESSION['locale'])) {
putenv("LANG=" . $_SESSION['locale']);
setlocale(LC_ALL, $_SESSION['locale']);
}
bindtextdomain(LOCALE_DOMAIN, LOCALE_ROOT);
bind_textdomain_codeset(LOCALE_DOMAIN, 'UTF-8');

40
includes/login.php Executable file
View file

@ -0,0 +1,40 @@
<?php
require_once 'includes/config.php';
require_once 'includes/functions.php';
/**
* Handler for administrative user login
*/
function DisplayLogin()
{
// initialize auth object
$auth = new \RaspAP\Auth\HTTPAuth;
$status = null;
$redirectUrl = null;
// handle page action
if (RASPI_AUTH_ENABLED) {
if (isset($_POST['login-auth'])) {
// authenticate user
$username = $_POST['username'];
$password = $_POST['password'];
$redirectUrl = $_POST['redirect-url'];
if ($auth->login($username, $password)) {
$config = $auth->getAuthConfig();
header('Location: ' . $redirectUrl);
die();
} else {
$status = "Login failed";
}
}
}
echo renderTemplate(
"login", compact(
"status",
"redirectUrl"
)
);
}

View file

@ -13,7 +13,7 @@
<!-- Auth user -->
<li class="nav-item mt-1">
<a class="nav-link" href="auth_conf">
<span class="mr-2 small nav-user"><?php echo htmlspecialchars($_SESSION['user_id'], ENT_QUOTES); ?></span>
<span class="mr-2 small nav-user"><?php echo htmlspecialchars($_SESSION['user_id'] ?? '', ENT_QUOTES); ?></span>
<i class="fas fa-user-circle text-muted mt-2 fa-3x"></i>
</a>
</li>

View file

@ -70,6 +70,9 @@ function handleCorePageAction(string $page, array &$extraFooterScripts): void
case "/about":
DisplayAbout();
break;
case "/login":
DisplayLogin();
break;
default:
DisplayDashboard($extraFooterScripts);
}

0
includes/restapi.php Normal file → Executable file
View file

View file

@ -3,3 +3,8 @@
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
if (!isset($_SESSION['lastActivity'])) {
$_SESSION['lastActivity'] = time();
}

View file

@ -50,6 +50,6 @@ if ($hostapd[0] ==1) {
$hostapd_led = "service-status-up";
} else {
$hostapd_status = "down";
$hostapd_led = "service-status-down";
$hostapd_led = "service-status-warn";
}

View file

@ -9,6 +9,8 @@ require_once 'config.php';
function DisplaySystem(&$extraFooterScripts)
{
$status = new \RaspAP\Messages\StatusMessage;
$dashboard = new \RaspAP\UI\Dashboard;
$pluginInstaller = \RaspAP\Plugins\PluginInstaller::getInstance();
if (isset($_POST['SaveLanguage'])) {
if (isset($_POST['locale'])) {
@ -85,53 +87,23 @@ function DisplaySystem(&$extraFooterScripts)
$kernel = $system->kernelVersion();
$systime = $system->systime();
$revision = $system->rpiRevision();
// mem used
$deviceImage = $dashboard->getDeviceImage($revision);
// memory use
$memused = $system->usedMemory();
$memused_status = "primary";
if ($memused > 90) {
$memused_status = "danger";
$memused_led = "service-status-down";
} elseif ($memused > 75) {
$memused_status = "warning";
$memused_led = "service-status-warn";
} elseif ($memused > 0) {
$memused_status = "success";
$memused_led = "service-status-up";
}
$memStatus = getMemStatus($memused);
$memused_status = $memStatus['status'];
$memused_led = $memStatus['led'];
// cpu load
$cpuload = $system->systemLoadPercentage();
if ($cpuload > 90) {
$cpuload_status = "danger";
} elseif ($cpuload > 75) {
$cpuload_status = "warning";
} elseif ($cpuload >= 0) {
$cpuload_status = "success";
}
$cpuload_status = getCPULoadStatus($cpuload);
// cpu temp
$cputemp = $system->systemTemperature();
if ($cputemp > 70) {
$cputemp_status = "danger";
$cputemp_led = "service-status-down";
} elseif ($cputemp > 50) {
$cputemp_status = "warning";
$cputemp_led = "service-status-warn";
} else {
$cputemp_status = "success";
$cputemp_led = "service-status-up";
}
// hostapd status
$hostapd = $system->hostapdStatus();
if ($hostapd[0] == 1) {
$hostapd_status = "active";
$hostapd_led = "service-status-up";
} else {
$hostapd_status = "inactive";
$hostapd_led = "service-status-down";
}
$cpuStatus = getCPUTempStatus($cputemp);
$cputemp_status = $cpuStatus['status'];
$cputemp_led = $cpuStatus['led'];
// theme options
$themes = [
@ -147,6 +119,9 @@ function DisplaySystem(&$extraFooterScripts)
$extraFooterScripts[] = array('src'=>'app/js/huebee.js', 'defer'=>false);
$logLimit = isset($_SESSION['log_limit']) ? $_SESSION['log_limit'] : RASPI_LOG_SIZE_LIMIT;
$plugins = $pluginInstaller->getUserPlugins();
$pluginsTable = $pluginInstaller->getHTMLPluginsTable($plugins);
echo renderTemplate("system", compact(
"arrLocales",
"status",
@ -156,6 +131,7 @@ function DisplaySystem(&$extraFooterScripts)
"uptime",
"systime",
"revision",
"deviceImage",
"cores",
"os",
"kernel",
@ -167,11 +143,62 @@ function DisplaySystem(&$extraFooterScripts)
"cputemp",
"cputemp_status",
"cputemp_led",
"hostapd",
"hostapd_status",
"hostapd_led",
"themes",
"selectedTheme",
"logLimit"
"logLimit",
"pluginsTable"
));
}
function getMemStatus($memused): array
{
$memused_status = "primary";
$memused_led = "";
if ($memused > 90) {
$memused_status = "danger";
$memused_led = "service-status-down";
} elseif ($memused > 75) {
$memused_status = "warning";
$memused_led = "service-status-warn";
} elseif ($memused > 0) {
$memused_status = "success";
$memused_led = "service-status-up";
}
return [
'status' => $memused_status,
'led' => $memused_led
];
}
function getCPULoadStatus($cpuload): string
{
if ($cpuload > 90) {
$status = "danger";
} elseif ($cpuload > 75) {
$status = "warning";
} elseif ($cpuload >= 0) {
$status = "success";
}
return $status;
}
function getCPUTempStatus($cputemp): array
{
if ($cputemp > 70) {
$cputemp_status = "danger";
$cputemp_led = "service-status-down";
} elseif ($cputemp > 50) {
$cputemp_status = "warning";
$cputemp_led = "service-status-warn";
} else {
$cputemp_status = "success";
$cputemp_led = "service-status-up";
}
return [
'status' => $cputemp_status,
'led' => $cputemp_led
];
}

View file

@ -160,19 +160,20 @@ function sortNetworksByRSSI(&$networks)
*/
function getWifiInterface()
{
$arrHostapdConf = parse_ini_file(RASPI_CONFIG.'/hostapd.ini');
$iface = $_SESSION['ap_interface'] = isset($arrHostapdConf['WifiInterface']) ? $arrHostapdConf['WifiInterface'] : RASPI_WIFI_AP_INTERFACE;
// check for 2nd wifi interface -> wifi client on different interface
exec("iw dev | awk '$1==\"Interface\" && $2!=\"$iface\" {print $2}'",$iface2);
$client_iface = $_SESSION['wifi_client_interface'] = (empty($iface2) ? $iface : trim($iface2[0]));
$hostapdIni = RASPI_CONFIG . '/hostapd.ini';
$arrHostapdConf = file_exists($hostapdIni) ? parse_ini_file($hostapdIni) : [];
// specifically for rpi0W in AP-STA mode, the above check ends up with the interfaces
// crossed over (wifi_client_interface vs 'ap_interface'), because the second interface (uap0) is
// created by raspap and used as the access point.
if ($client_iface == "uap0" && ($arrHostapdConf['WifiAPEnable'] ?? 0)){
$_SESSION['wifi_client_interface'] = $iface;
$_SESSION['ap_interface'] = $client_iface;
}
$iface = $_SESSION['ap_interface'] = $arrHostapdConf['WifiInterface'] ?? RASPI_WIFI_AP_INTERFACE;
// check for 2nd wifi interface -> wifi client on different interface
exec("iw dev | awk '$1==\"Interface\" && $2!=\"$iface\" {print $2}'", $iface2);
$client_iface = $_SESSION['wifi_client_interface'] = empty($iface2) ? $iface : trim($iface2[0]);
// handle special case for RPi Zero W in AP-STA mode
if ($client_iface === "uap0" && ($arrHostapdConf['WifiAPEnable'] ?? 0)) {
$_SESSION['wifi_client_interface'] = $iface;
$_SESSION['ap_interface'] = $client_iface;
}
}
/*

View file

@ -10,10 +10,10 @@ function DisplayWireGuardConfig()
$status = new \RaspAP\Messages\StatusMessage;
$parseFlag = true;
if (!RASPI_MONITOR_ENABLED) {
$optRules = $_POST['wgRules'];
$optConf = $_POST['wgCnfOpt'];
$optSrvEnable = $_POST['wgSrvEnable'];
$optLogEnable = $_POST['wgLogEnable'];
$optRules = isset($_POST['wgRules']) ? $_POST['wgRules'] : null;
$optConf = isset($_POST['wgCnfOpt']) ? $_POST['wgCnfOpt'] : null;
$optSrvEnable = isset($_POST['wgSrvEnable']) ? $_POST['wgSrvEnable'] : null;
$optLogEnable = isset($_POST['wgLogEnable']) ? $_POST['wgLogEnable'] : null;
if (isset($_POST['savewgsettings']) && $optConf == 'manual' && $optSrvEnable == 1 ) {
SaveWireGuardConfig($status);
} elseif (isset($_POST['savewgsettings']) && $optConf == 'upload' && is_uploaded_file($_FILES["wgFile"]["tmp_name"])) {
@ -69,6 +69,14 @@ function DisplayWireGuardConfig()
$wg_state = ($wgstatus[0] == 'active' ? true : false );
$public_ip = get_public_ip();
// retrieve wg log
$wg_log = "";
if (file_exists('/tmp/wireguard.log')) {
exec('sudo chmod o+r /tmp/wireguard.log');
$wg_log = file_get_contents('/tmp/wireguard.log');
}
$peer_id = $peer_id ?? "1";
echo renderTemplate(
"wireguard", compact(
"status",
@ -89,7 +97,8 @@ function DisplayWireGuardConfig()
"wg_peerpubkey",
"wg_pendpoint",
"wg_pallowedips",
"wg_pkeepalive"
"wg_pkeepalive",
"wg_log"
)
);
}
@ -207,7 +216,10 @@ function SaveWireGuardConfig($status)
}
if (isset($_POST['wg_pendpoint']) && strlen(trim($_POST['wg_pendpoint']) >0 )) {
$wg_pendpoint_seg = substr($_POST['wg_pendpoint'],0,strpos($_POST['wg_pendpoint'],':'));
if (!filter_var($wg_pendpoint_seg,FILTER_VALIDATE_IP)) {
$host_port = explode(':', $wg_pendpoint_seg);
$hostname = $host_port[0];
if (!filter_var($hostname, FILTER_VALIDATE_IP) &&
!filter_var($hostname, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
$status->addMessage('Invalid value for endpoint address', 'danger');
$good_input = false;
}

View file

@ -8,13 +8,13 @@
* Recommended distribution is Raspberry Pi OS (64-bit) Lite. Specific instructions to install the supported software are
* in the README and original post by @SirLagz. For a quick run through, the packages required for the WebGUI are:
* lighttpd (version 1.4.69 installed via apt)
* php-cgi (version 8.2.26 installed via apt)
* php-cgi (version 8.2.28 installed via apt)
* along with their supporting packages, php8.2 will also need to be enabled.
*
* @author Lawrence Yau <sirlagz@gmail.com>
* @author Bill Zimmerman <billzimmerman@gmail.com>
* @license GNU General Public License, version 3 (GPL-3.0)
* @version 3.2.4
* @version 3.3.2
* @link https://github.com/RaspAP/raspap-webgui/
* @link https://raspap.com/
* @see http://sirlagz.net/2013/02/08/raspap-webgui/
@ -23,18 +23,19 @@
* as you leave these references intact in the header comments of your source files.
*/
require 'includes/csrf.php';
ensureCSRFSessionToken();
require_once 'includes/exceptions.php';
require_once 'includes/config.php';
require_once 'includes/autoload.php';
$handler = new RaspAP\Exceptions\ExceptionHandler;
require_once 'includes/CSRF.php';
require_once 'includes/session.php';
require_once 'includes/defaults.php';
require_once 'includes/locale.php';
require_once 'includes/functions.php';
// Default page actions
require_once 'includes/dashboard.php';
require_once 'includes/login.php';
require_once 'includes/authenticate.php';
require_once 'includes/admin.php';
require_once 'includes/dhcp.php';
@ -57,13 +58,13 @@ initializeApp();
<html lang="en" <?php setTheme();?>>
<head>
<meta charset="utf-8">
<?php echo CSRFMetaTag() ?>
<?php echo \RaspAP\Tokens\CSRF::metaTag(); ?>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="">
<meta name="author" content="">
<title><?php echo _("RaspAP WiFi Configuration Portal"); ?></title>
<title><?php echo RASPI_BRAND_TITLE; ?></title>
<!-- Bootstrap Core CSS -->
<link href="dist/bootstrap/css/bootstrap.min.css" rel="stylesheet">
@ -96,6 +97,7 @@ initializeApp();
<body class="sb-nav-fixed">
<!-- Navbar -->
<?php ob_start(); ?>
<?php require_once 'includes/navbar.php'; ?>
<!-- End of Navbar -->
<div id="layoutSidenav">
@ -123,6 +125,7 @@ initializeApp();
</footer>
</div>
</div>
<?php ob_end_flush(); ?>
<!-- jQuery -->
<script src="dist/jquery/jquery.min.js"></script>

View file

@ -51,6 +51,7 @@ function _install_raspap() {
_download_latest_files
_change_file_ownership
_create_hostapd_scripts
_create_plugin_scripts
_create_lighttpd_scripts
_install_lighttpd_configs
_default_configuration
@ -74,6 +75,7 @@ function _update_raspap() {
_download_latest_files
_change_file_ownership
_patch_system_files
_create_plugin_scripts
_install_complete
}
@ -243,7 +245,22 @@ function _install_dependencies() {
if [ ${OS,,} = "armbian" ]; then
ifconfig_package="net-tools"
echo "${ifconfig_package} will be installed from the main deb sources list"
# Manually install isoquery
_install_log "Installing isoquery from the Debian package repository"
isoquery_deb="https://ftp.debian.org/debian/pool/main/i/isoquery"
if [ "$LONG_BIT" = "64" ]; then
isoquery_pkg="isoquery_3.3.4-1+b1_arm64.deb"
else
isoquery_pkg="isoquery_3.3.4-1_armhf.deb"
fi
echo "isoquery ARM ${LONG_BIT}-bit package selected"
wget $isoquery_deb/$isoquery_pkg -q --show-progress --progress=bar:force -P /tmp || _install_status 1 "Failed to download isoquery"
sudo dpkg -x /tmp/$isoquery_pkg /tmp/isoquery/ || _install_status 1 "Failed to extract isoquery"
sudo cp /tmp/isoquery/usr/bin/isoquery /usr/local/bin/ || _install_status 1 "Failed to copy isoquery binary"
sudo chmod +x /usr/local/bin/isoquery || _install_status 1 "Failed to set executable permissions on isoquery"
fi
if [ "$insiders" == 1 ]; then
network_tools="curl dnsutils nmap"
echo "${network_tools} will be installed from the main deb sources list"
@ -298,6 +315,19 @@ function _create_hostapd_scripts() {
_install_status 0
}
# Generate plugin helper scripts
function _create_plugin_scripts() {
_install_log "Creating plugin helper scripts"
sudo mkdir -p $raspap_dir/plugins || _install_status 1 "Unable to create directory '$raspap_dir/plugins'"
# Copy plugin helper script
sudo cp "$webroot_dir/installers/"plugin_helper.sh "$raspap_dir/plugins" || _install_status 1 "Unable to move plugin script"
# Change ownership and permissions of plugin script
sudo chown -c root:root "$raspap_dir/plugins/"*.sh || _install_status 1 "Unable change owner and/or group"
sudo chmod 750 "$raspap_dir/plugins/"*.sh || _install_status 1 "Unable to change file permissions"
_install_status 0
}
# Generate lighttpd service control scripts
function _create_lighttpd_scripts() {
_install_log "Creating lighttpd control scripts"
@ -569,14 +599,16 @@ function _download_latest_files() {
if [ "$repo" == "RaspAP/raspap-insiders" ]; then
if [ -n "$username" ] && [ -n "$acctoken" ]; then
insiders_source_url="https://${username}:${acctoken}@github.com/$repo"
git clone --branch $branch --depth 1 -c advice.detachedHead=false $insiders_source_url $source_dir || clone=false
git clone --branch $branch --depth 1 --recurse-submodules -c advice.detachedHead=false $insiders_source_url $source_dir || clone=false
git -C $source_dir submodule update --remote plugins || clone=false
else
_install_status 3
echo "Insiders please read this: https://docs.raspap.com/insiders/#authentication"
fi
fi
if [ -z "$insiders_source_url" ]; then
git clone --branch $branch --depth 1 -c advice.detachedHead=false $git_source_url $source_dir || clone=false
git clone --branch $branch --depth 1 --recurse-submodules -c advice.detachedHead=false $git_source_url $source_dir || clone=false
git -C $source_dir submodule update --remote plugins || clone=false
fi
if [ "$clone" = false ]; then
_install_status 1 "Unable to download files from GitHub"

View file

@ -217,10 +217,10 @@ function _dnsmasq_info() {
if [ -f "$file" ]; then
contents+="\n$file contents:\n"
contents+="$(cat $file)"
contents="${contents}$\n"
contents+=$'\n'
fi
done
_log_write $contents
_log_write "$contents"
else
_log_write "Not found: ${DNSMASQ_D_DIR}"
fi

171
installers/plugin_helper.sh Executable file
View file

@ -0,0 +1,171 @@
#!/bin/bash
#
# PluginInstaller helper for RaspAP
# @author billz
# license: GNU General Public License v3.0
# Exit on error
set -o errexit
readonly raspap_user="www-data"
[ $# -lt 1 ] && { echo "Usage: $0 <action> [parameters...]"; exit 1; }
action="$1" # action to perform
shift 1
case "$action" in
"sudoers")
[ $# -ne 1 ] && { echo "Usage: $0 sudoers <file>"; exit 1; }
file="$1"
plugin_name=$(basename "$file")
dest="/etc/sudoers.d/${plugin_name}"
mv "$file" "$dest" || { echo "Error: Failed to move $file to $dest."; exit 1; }
chown root:root "$dest" || { echo "Error: Failed to set ownership for $dest."; exit 1; }
chmod 0440 "$dest" || { echo "Error: Failed to set permissions for $dest."; exit 1; }
echo "OK"
;;
"packages")
[ $# -lt 1 ] && { echo "Usage: $0 packages <apt_packages...>"; exit 1; }
echo "Installing APT packages..."
for package in "$@"; do
echo "Installing package: $package"
apt-get install -y "$package" || { echo "Error: Failed to install $package."; exit 1; }
done
echo "OK"
;;
"user")
[ $# -lt 2 ] && { echo "Usage: $0 user <username> <password>."; exit 1; }
username=$1
password=$2
if id "$username" &>/dev/null; then # user already exists
echo "OK"
exit 0
fi
# create the user without shell access
useradd -r -s /bin/false "$username"
# set password non-interactively
echo "$username:$password" | chpasswd
echo "OK"
;;
"config")
[ $# -lt 2 ] && { echo "Usage: $0 config <source> <destination>"; exit 1; }
source=$1
destination=$2
if [ ! -f "$source" ]; then
echo "Source file $source does not exist."
exit 1
fi
mkdir -p "$(dirname "$destination")"
cp "$source" "$destination"
chown -R $raspap_user:$raspap_user "$destination"
echo "OK"
;;
"javascript")
[ $# -lt 2 ] && { echo "Usage: $0 javascript <source> <destination>"; exit 1; }
source=$1
destination=$2
if [ ! -f "$source" ]; then
echo "Source file $source does not exist."
exit 1
fi
if [ ! -d "$destination" ]; then
mkdir -p "$destination"
fi
cp "$source" "$destination"
chown -R $raspap_user:$raspap_user "$destination"
echo "OK"
;;
"plugin")
[ $# -lt 2 ] && { echo "Usage: $0 plugin <source> <destination>"; exit 1; }
source=$1
destination=$2
if [ ! -d "$source" ]; then
echo "Source directory $source does not exist."
exit 1
fi
plugin_dir=$(dirname "$destination")
if [ ! -d "$plugin_dir" ]; then
mkdir -p "$plugin_dir"
fi
cp -R "$source" "$destination"
chown -R $raspap_user:$raspap_user "$plugin_dir"
echo "OK"
;;
"keys")
[ $# -ne 4 ] && { echo "Usage: $0 keys <key_url> <keyring> <repo> <sources>"; exit 1; }
key_url="$1"
keyring="$2"
repo="$3"
list_file="$4"
# add repository GPG key if it doesn't already exist
if [ ! -f "$keyring" ]; then
echo "Downloading GPG key from $key_url..."
curl -fsSL "$key_url" | sudo tee "$keyring" > /dev/null || { echo "Error: Failed to download GPG key."; exit 1; }
else
echo "Repository GPG key already exists at $keyring"
fi
# add repository list if not present
if [ ! -f "$list_file" ]; then
echo "Adding repository $repo to sources list"
curl -fsSL "$repo" | sudo tee "$list_file" > /dev/null || { echo "Error: Failed to add repository to sources list."; exit 1; }
update_required=1
else
echo "Repository already exists in sources list"
fi
# update apt package list if required
if [ "$update_required" == "1" ]; then
sudo apt-get update || { echo "Error: Failed to update apt"; exit 1; }
fi
echo "OK"
;;
*)
echo "Invalid action: $action"
echo "Usage: $0 <action> [parameters...]"
echo "Actions:"
echo " sudoers <file> Install a sudoers file"
echo " packages <packages> Install aptitude package(s)"
echo " user <name> <password> Add user non-interactively"
echo " config <source <destination> Applies a config file"
echo " javascript <source> <destination> Applies a JavaScript file"
echo " plugin <source> <destination> Copies a plugin directory"
echo " keys <key_url> <keyring> <repo> <sources> Installs a GPG key for a third-party repo"
exit 1
;;
esac

View file

@ -78,3 +78,5 @@ www-data ALL=(ALL) NOPASSWD:/bin/rm /etc/wireguard/wg-*.key
www-data ALL=(ALL) NOPASSWD:/usr/sbin/netplan
www-data ALL=(ALL) NOPASSWD:/bin/truncate -s 0 /tmp/*.log,/bin/truncate -s 0 /var/log/dnsmasq.log
www-data ALL=(ALL) NOPASSWD:/usr/bin/vnstat *
www-data ALL=(ALL) NOPASSWD:/usr/sbin/visudo -cf *
www-data ALL=(ALL) NOPASSWD:/etc/raspap/plugins/plugin_helper.sh

View file

@ -54,6 +54,7 @@ OPTIONS:
-p, --path <path> Used with -d, --update, sets the existing install path
-i, --insiders Installs from the Insiders Edition (RaspAP/raspap-insiders)
-m, --minwrite Configures a microSD card for minimum write operation
-k, --check <flag> Sets the connectivity check flag (default is 1=perform check)
-v, --version Outputs release info and exits
-n, --uninstall Loads and executes the uninstaller
-h, --help Outputs usage notes and exits
@ -84,8 +85,13 @@ function _main() {
# set defaults
repo="RaspAP/raspap-webgui" # override with -r, --repo option
repo_common="$repo"
_parse_params "$@"
_setup_colors
if [ "${check}" == 1 ]; then
_check_internet
fi
_log_output
_load_installer
}
@ -103,6 +109,7 @@ function _parse_params() {
minwrite=0
acctoken=""
path=""
check=1
while :; do
case "${1-}" in
@ -173,6 +180,10 @@ function _parse_params() {
path="$2"
shift
;;
-k|--check)
check="$2"
shift
;;
-v|--version)
_version
;;
@ -233,11 +244,25 @@ function _display_welcome() {
# Fetch latest release from GitHub or RaspAP Installer API
function _get_release() {
readonly RASPAP_LATEST=$(curl -s "https://api.github.com/repos/$repo/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")' )
local response
local host="api.github.com"
response=$(curl -s "https://$host/repos/$repo/releases/latest")
if echo "$response" | grep -q 'API rate limit exceeded'; then
_install_status 1 "GitHub API rate limit exceeded. Try again later or use a GitHub token."
return 1
fi
readonly RASPAP_LATEST=$(echo "$response" | grep -Po '"tag_name": "\K.*?(?=")')
if [ -z "$RASPAP_LATEST" ]; then
_install_status 1 "Failed to fetch latest release. Check network connectivity."
return 1
fi
if [ "$insiders" == 1 ]; then
repo="RaspAP/raspap-insiders"
repo_common="RaspAP/raspap-webgui"
readonly RASPAP_INSIDERS_LATEST=$(curl -s "https://api.raspap.com/repos/RaspAP/raspap-insiders/releases/latest/" | grep -Po '"tag_name": "\K.*?(?=")' )
readonly RASPAP_INSIDERS_LATEST=$(curl -s "https://api.raspap.com/repos/RaspAP/raspap-insiders/releases/latest/" | grep -Po '"tag_name": "\K.*?(?=")')
readonly RASPAP_RELEASE="${RASPAP_INSIDERS_LATEST} Insiders"
else
readonly RASPAP_RELEASE="${RASPAP_LATEST}"
@ -271,6 +296,40 @@ function _install_status() {
esac
}
# Checks connectivity to github.com
function _check_internet() {
component="Install"
_install_log "Checking internet connectivity..."
# spinner frames
local spinner='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
local i=0
tput civis # hide cursor
# run check in background
( curl -Is --connect-timeout 3 --max-time 15 https://github.com | head -n 1 | grep "HTTP/2 200" >/dev/null ) &
local pid=$!
# display spinner while curl runs
while kill -0 $pid 2>/dev/null; do
printf "\r%s" "${spinner:i++%${#spinner}:1}"
sleep 0.05
done
printf "\r"
# check exit status of curl
wait $pid || exit_code=$?
tput cnorm # restore cursor
if [[ $exit_code -ne 0 ]]; then
_install_status 1 "No internet connection or unable to reach GitHub"
exit 1
fi
_install_status 0
}
function _update_system_packages() {
_install_log "Updating sources"
sudo apt-get update || _install_status 1 "Unable to update package list"

Binary file not shown.

View file

@ -25,8 +25,8 @@ msgstr "RaspAP Wifi Configuration Portal"
msgid "Toggle navigation"
msgstr "Toggle navigation"
msgid "RaspAP Wifi Portal"
msgstr "RaspAP Wifi Portal"
msgid "RaspAP Admin Panel"
msgstr "RaspAP Admin Panel"
msgid "Dashboard"
msgstr "Dashboard"
@ -244,8 +244,8 @@ msgstr "Frequency"
msgid "Link Quality"
msgstr "Link Quality"
msgid "Information provided by ip and iw and from system"
msgstr "Information provided by ip and iw and from system"
msgid "Information provided by raspap.system"
msgstr "Information provided by raspap.system"
msgid "No MAC Address Found"
msgstr "No MAC Address Found"
@ -283,9 +283,53 @@ msgstr "Connected Devices"
msgid "Client: Ethernet cable"
msgstr "Client: Ethernet cable"
msgid "Current status"
msgstr "Current status"
msgid "Ethernet"
msgstr "Ethernet"
msgid "Repeater"
msgstr "Repeater"
msgid "Tethering"
msgstr "Tethering"
msgid "Cellular"
msgstr "Cellular"
msgid "AP"
msgstr "AP"
msgid "Bridged"
msgstr "Bridged"
msgid "Adblock"
msgstr "Adblock"
msgid "VPN"
msgstr "VPN"
msgid "Firewall"
msgstr "Firewall"
msgid "Netmask"
msgstr "Netmask"
msgid "5G"
msgstr "5G"
msgid "2.4G"
msgstr "2.4G"
msgid "%d WLAN %s"
msgstr "%d WLAN %s"
msgid "client"
msgid_plural "clients"
msgstr[0] "client"
msgstr[1] "clients"
msgid "Client: Smartphone (USB tethering)"
msgstr "Client: Smartphone (USB tethering)"
@ -334,6 +378,16 @@ msgstr "Signal strength"
msgid "No Client device or not yet configured"
msgstr "No Client device or not yet configured"
#: includes/footer.php
msgid "Created by the <a href=\"%s\" target=\"_blank\" rel=\"noopener\">%s</a>"
msgstr "Created by the <a href=\"%s\" target=\"_blank\" rel=\"noopener\">%s</a>"
msgid "RaspAP Team"
msgstr "RaspAP Team"
msgid "Get Insiders"
msgstr "Get Insiders"
#: includes/dhcp.php
msgid "DHCP server settings"
msgstr "DHCP server settings"
@ -920,6 +974,81 @@ msgstr "Changing log limit size to %s KB"
msgid "Information provided by raspap.sysinfo"
msgstr "Information provided by raspap.sysinfo"
msgid "The following user plugins are available to extend RaspAP's functionality."
msgstr "The following user plugins are available to extend RaspAP's functionality."
msgid "Choose <strong>Details</strong> for more information and to install a plugin."
msgstr "Choose <strong>Details</strong> for more information and to install a plugin."
msgid "Network error"
msgstr "Network error"
msgid "Unable to load plugins"
msgstr "Unable to load plugins"
msgid "Reload"
msgstr "Reload"
msgid "and try again"
msgstr "and try again"
msgid "Plugins"
msgstr "Plugins"
msgid "Plugin details"
msgstr "Plugin details"
msgid "Name"
msgstr "Name"
msgid "Version"
msgstr "Version"
msgid "Description"
msgstr "Description"
msgid "Plugin source"
msgstr "Plugin source"
msgid "Author"
msgstr "Author"
msgid "License"
msgstr "License"
msgid "Language locale"
msgstr "Language locale"
msgid "Configuration files"
msgstr "Configuration files"
msgid "Dependencies"
msgstr "Dependencies"
msgid "Permissions"
msgstr "Permissions"
msgid "Non-privileged users"
msgstr "Non-privileged users"
msgid "Install now"
msgstr "Install now"
msgid "Installing plugin"
msgstr "Installing plugin"
msgid "Plugin installation in progress..."
msgstr "Plugin installation in progress..."
msgid "Plugin install completed."
msgstr "Plugin install completed."
msgid "Details"
msgstr "Details"
msgid "Installed"
msgstr "Installed"
#: includes/data_usage.php
msgid "Data usage"
msgstr "Data usage"
@ -1566,3 +1695,23 @@ msgstr "Restarting restapi.service"
msgid "Information provided by restapi.service"
msgstr "Information provided by restapi.service"
#: includes/login.php
msgid "Session Expired"
msgstr "Session Expired"
msgid "Your session has expired. Please login to continue."
msgstr "Your session has expired. Please login to continue."
msgid "Login"
msgstr "Login"
msgid "Administrator login"
msgstr "Administrator login"
msgid "Forgot password"
msgstr "Forgot password"
msgid "Login failed"
msgstr "Login failed"

1
plugins Submodule

@ -0,0 +1 @@
Subproject commit 38331709b6c8198c0dbf1c7c85cb52b8ae3ea79a

View file

@ -15,12 +15,6 @@ namespace RaspAP\Auth;
class HTTPAuth
{
/**
* @var string $realm
*/
public $realm = 'Authentication Required';
/**
* Stored login credentials
* @var array $auth_config
@ -57,15 +51,11 @@ class HTTPAuth
public function authenticate()
{
if (!$this->isLogged()) {
header('HTTP/1.0 401 Unauthorized');
header('WWW-Authenticate: Basic realm="'.$this->realm.'"');
if (function_exists('http_response_code')) {
// http_response_code will respond with proper HTTP version
http_response_code(401);
} else {
header('HTTP/1.0 401 Unauthorized');
$redirectUrl = $_SERVER['REQUEST_URI'];
if (strpos($redirectUrl, '/login') === false) {
header('Location: /login?action=' . urlencode($redirectUrl));
exit();
}
exit('Not authorized'.PHP_EOL);
}
}
@ -84,6 +74,21 @@ class HTTPAuth
return false;
}
/*
* Logs out the administrative user
*/
public function logout(): void
{
session_regenerate_id(true); // generate a new session id
session_unset(); // unset all session variables
session_destroy(); // destroy the session
$redirectUrl = $_SERVER['REQUEST_URI'];
if (strpos($redirectUrl, '/login') === false) {
header('Location: /login?action=' . urlencode($redirectUrl));
exit();
}
}
/*
* Gets the current authentication config
* return array $config

View file

@ -37,7 +37,7 @@ class DotEnv
}
}
} else {
throw new Exception(".env file '{$this->envFile}' not found.");
throw new \Exception(".env file '{$this->envFile}' not found.");
}
}
@ -75,7 +75,7 @@ class DotEnv
file_put_contents("/tmp/.env", $content);
system('sudo mv /tmp/.env '.$this->envFile, $result);
if ($result !== 0) {
throw new Exception("Unable to move .env file: ". $this->envFile);
throw new \Exception("Unable to move .env file: ". $this->envFile);
}
}
@ -83,7 +83,7 @@ class DotEnv
{
exec('sudo touch '. escapeshellarg($this->envFile), $output, $result);
if ($result !== 0) {
throw new Exception("Unable to create .env file: ". $this->envFile);
throw new \Exception("Unable to create .env file: ". $this->envFile);
}
}
}

View file

@ -47,7 +47,7 @@ class IwParser
"(no IR)"
];
$excluded_pattern = implode('|', array_map('preg_quote', $excluded));
$pattern = '/\*\s+(\d+)\s+MHz \[(\d+)\] \(([\d.]+) dBm\)\s(?!' .$excluded_pattern. ')/';
$pattern = '/\*\s+([\d.]+)\s+MHz \[(\d+)\] \(([\d.]+) dBm\)\s(?!' .$excluded_pattern. ')/';
$supportedFrequencies = [];
// Match iw_output containing supported frequencies

View file

@ -0,0 +1,547 @@
<?php
/**
* Plugin Installer class
*
* @description Class to handle installation of user plugins
* @author Bill Zimmerman <billzimmerman@gmail.com>
* @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
*/
declare(strict_types=1);
namespace RaspAP\Plugins;
class PluginInstaller
{
private static $instance = null;
private $pluginName;
private $manifestRaw;
private $tempSudoers;
private $destSudoers;
private $refModules;
private $rootPath;
private $pluginsManifest;
private $repoPublic;
private $helperScriptPath;
public function __construct()
{
$this->pluginPath = 'plugins';
$this->manifestRaw = '/blob/master/manifest.json?raw=true';
$this->tempSudoers = '/tmp/090_';
$this->destSudoers = '/etc/sudoers.d/';
$this->refModules = '/refs/heads/master/.gitmodules';
$this->rootPath = $_SERVER['DOCUMENT_ROOT'];
$this->pluginsManifest = '/plugins/manifest.json';
$this->repoPublic = $this->getRepository();
$this->helperScriptPath = RASPI_CONFIG.'/plugins/plugin_helper.sh';
}
// Returns a single instance of PluginInstaller
public static function getInstance(): PluginInstaller
{
if (self::$instance === null) {
self::$instance = new PluginInstaller();
}
return self::$instance;
}
/**
* Returns availble user plugin details from a manifest.json file
*
* @return array $plugins
*/
public function getUserPlugins()
{
try {
$manifestPath = $this->rootPath . $this->pluginsManifest;
if (!file_exists($manifestPath)) {
throw new \Exception("Manifest file not found at " . $manifestPath);
}
// decode manifest file contents
$manifestContents = file_get_contents($manifestPath);
$manifestData = json_decode($manifestContents, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception("Error parsing manifest.json: " . json_last_error_msg());
}
// fetch installed plugins
$installedPlugins = $this->getPlugins();
$plugins = [];
foreach ($manifestData as $pluginManifest) {
$pluginEntries = [];
foreach ($pluginManifest as $plugin) {
$installed = false;
if (!empty($plugin['namespace'])) {
foreach ($installedPlugins as $installedPlugin) {
if (strpos($installedPlugin['class'], $plugin['namespace']) !== false) {
$installed = true;
break;
}
}
}
$pluginEntries[] = [
'manifest' => $plugin,
'installed' => $installed
];
}
$plugins[] = $pluginEntries;
}
return array_merge(...$plugins);
} catch (\Exception $e) {
error_log("An error occurred: " . $e->getMessage());
throw $e; // re-throw to global ExceptionHandler
}
}
/**
* Returns an array of installed plugins in pluginPath
*
* @param string|null $path; optional path to search for plugins. Defaults to $this->pluginPath.
* @return array $plugins
*/
public function getPlugins(?string $path = null): array
{
$plugins = [];
$pluginPath = $path ?? $this->pluginPath;
if (file_exists($pluginPath)) {
$directories = scandir($pluginPath);
foreach ($directories as $directory) {
if ($directory === '.' || $directory === '..') {
continue;
}
$pluginClass = "RaspAP\\Plugins\\$directory\\$directory";
$pluginFile = "$pluginPath/$directory/$directory.php";
if (file_exists($pluginFile)) {
if ($path === 'plugins-available') {
require_once $pluginFile;
}
if (class_exists($pluginClass)) {
$plugins[] = [
'class' => $pluginClass,
'installPath' => $pluginPath
];
}
}
}
}
return $plugins;
}
/**
* Installs a plugin by either extracting an archive or creating a symlink,
* then performs required actions as defined in the plugin manifest
*
* @param string $pluginUri
* @param string $pluginVersion
* @param string $installPath
* @return boolean
* @throws \Exception
*/
public function installPlugin(string $pluginUri, string $pluginVersion, string $installPath): bool
{
$tempFile = null;
$extractDir = null;
$pluginDir = null;
try {
if ($installPath === 'plugins-available') {
// extract plugin name from URI
$pluginName = basename($pluginUri);
$sourcePath = $this->rootPath . '/plugins-available/' . $pluginName;
$targetPath = $this->rootPath . '/plugins/' . $pluginName;
if (!is_dir($sourcePath)) {
throw new \Exception("Plugin '$pluginName' not found in plugins-available");
}
// ensure target does not already exist
if (file_exists($targetPath)) {
throw new \Exception("Plugin '$pluginName' is already installed.");
}
// create symlink
if (!symlink($sourcePath, $targetPath)) {
throw new \Exception("Failed to symlink '$pluginName' to plugins/");
}
$pluginDir = $targetPath;
} else {
// fetch and extract the plugin archive
$archiveUrl = rtrim($pluginUri, '/') . '/archive/refs/tags/' .$pluginVersion.'.zip';
list($tempFile, $extractDir, $pluginDir) = $this->getPluginArchive($archiveUrl);
}
$manifest = $this->parseManifest($pluginDir);
$this->pluginName = preg_replace('/\s+/', '', $manifest['name']);
$rollbackStack = []; // Store actions to rollback on failure
try {
if (!empty($manifest['sudoers'])) {
$this->addSudoers($manifest['sudoers']);
$rollbackStack[] = 'removeSudoers';
}
if (!empty($manifest['keys'])) {
$this->installRepositoryKeys($manifest['keys']);
$rollbackStack[] = 'uninstallRepositoryKeys';
}
if (!empty($manifest['dependencies'])) {
$this->installDependencies($manifest['dependencies']);
$rollbackStack[] = 'uninstallDependencies';
}
if (!empty($manifest['user_nonprivileged'])) {
$this->createUser($manifest['user_nonprivileged']);
$rollbackStack[] = 'deleteUser';
}
if (!empty($manifest['configuration'])) {
$this->copyConfigFiles($manifest['configuration'], $pluginDir);
$rollbackStack[] = 'removeConfigFiles';
}
if (!empty($manifest['javascript'])) {
$this->copyJavaScriptFiles($manifest['javascript'], $pluginDir);
$rollbackStack[] = 'removeJavaScript';
}
if ($installPath === 'plugins') {
$this->copyPluginFiles($pluginDir, $this->rootPath);
$rollbackStack[] = 'removePluginFiles';
}
return true;
} catch (\Exception $e) {
throw new \Exception('Installation step failed: ' . $e->getMessage());
}
} catch (\Exception $e) {
error_log('Plugin installation failed: ' . $e->getMessage());
throw new \Exception($e->getMessage());
} finally {
if (isset($tempFile) && file_exists($tempFile)) {
unlink($tempFile);
}
if (isset($extractDir) && is_dir($extractDir)) {
$this->deleteDir($extractDir);
}
}
}
/**
* Adds sudoers entries to a temp file and copies to /etc/sudoers.d/
*
* @param array $sudoers
*/
private function addSudoers(array $sudoers): void
{
$tmpSudoers = $this->tempSudoers . $this->pluginName;
$destination = $this->destSudoers;
$content = implode("\n", $sudoers);
if (file_put_contents($tmpSudoers, $content) === false) {
throw new \Exception('Failed to update sudoers file.');
}
$cmd = sprintf('sudo visudo -cf %s', escapeshellarg($tmpSudoers));
$return = shell_exec($cmd);
if (strpos(strtolower($return), 'parsed ok') !== false) {
$cmd = sprintf('sudo %s sudoers %s', escapeshellarg($this->helperScriptPath), escapeshellarg($tmpSudoers));
$return = shell_exec($cmd);
if (strpos(strtolower($return), 'ok') === false) {
throw new \Exception('Plugin helper failed to install sudoers.');
}
} else {
throw new \Exception('Sudoers check failed.');
}
}
/**
* Installs plugin dependencies from the aptitude package repository
*
* @param array $dependencies
*/
private function installDependencies(array $dependencies): void
{
$packages = array_keys($dependencies);
$packageList = implode(' ', $packages);
$cmd = sprintf('sudo %s packages %s', escapeshellarg($this->helperScriptPath), escapeshellarg($packageList));
$return = shell_exec($cmd);
if (strpos(strtolower($return), 'ok') === false) {
throw new \Exception('Plugin helper failed to install depedencies.');
}
}
/**
* Creates a non-priviledged Linux user
*
* @param array $user
*/
private function createUser(array $user): void
{
if (empty($user['name']) || empty($user['pass'])) {
throw new \InvalidArgumentException('User name or password is missing.');
}
$username = escapeshellarg($user['name']);
$password = escapeshellarg($user['pass']);
$cmd = sprintf('sudo %s user %s %s', escapeshellarg($this->helperScriptPath), $username, $password);
$return = shell_exec($cmd);
if (strpos(strtolower($return), 'ok') === false) {
throw new \Exception('Plugin helper failed to create user: ' . $user['name']);
}
}
/**
* Copies plugin configuration files to their destination
*
* @param array $configurations
* @param string $pluginDir
*/
private function copyConfigFiles(array $configurations, string $pluginDir): void
{
foreach ($configurations as $config) {
$source = escapeshellarg($pluginDir . DIRECTORY_SEPARATOR . $config['source']);
$destination = $config['destination'];
if (!str_starts_with($destination, '/')) {
$destination = $this->rootPath . '/' . ltrim($destination, '/');
}
$destination = escapeshellarg($destination);
$cmd = sprintf('sudo %s config %s %s', escapeshellarg($this->helperScriptPath), $source, $destination);
$return = shell_exec($cmd);
if (strpos(strtolower($return), 'ok') === false) {
throw new \Exception("Failed to copy configuration file: $source to $destination");
}
}
}
/**
* Copies plugin JavaScript files to their destination
*
* @param array $javascript
* @param string $pluginDir
*/
private function copyJavaScriptFiles(array $javascript, string $pluginDir): void
{
foreach ($javascript as $js) {
$source = escapeshellarg($pluginDir . DIRECTORY_SEPARATOR . $js);
$destination = escapeshellarg($this->rootPath . DIRECTORY_SEPARATOR . 'app/js/plugins/');
$cmd = sprintf('sudo %s javascript %s %s', escapeshellarg($this->helperScriptPath), $source, $destination);
$return = shell_exec($cmd);
if (strpos(strtolower($return), 'ok') === false) {
throw new \Exception("Failed to copy JavaScript file: $source");
}
}
}
/**
* Copies an extracted plugin directory from /tmp to /plugins
*
* @param string $source
* @param string $destination
*/
private function copyPluginFiles(string $source, string $destination): void
{
$source = escapeshellarg($source);
$destination = escapeshellarg($destination . DIRECTORY_SEPARATOR .$this->pluginPath . DIRECTORY_SEPARATOR . $this->pluginName);
$cmd = sprintf('sudo %s plugin %s %s', escapeshellarg($this->helperScriptPath), $source, $destination);
$return = shell_exec($cmd);
if (strpos(strtolower($return), 'ok') === false) {
throw new \Exception('Failed to copy plugin files to: ' . $destination);
}
}
/**
* Installs repository keys for third-party apt packages
*
* @param array $keys Array containing key_url, keyring, repo, and sources
* @throws Exception on key installation failure
*/
public function installRepositoryKeys(array $keys): void
{
if (!is_array($keys)) {
throw new \Exception("Invalid repository key structure: array expected");
}
foreach ($keys as $keyData) {
if (!isset($keyData['key_url'], $keyData['keyring'], $keyData['repo'], $keyData['sources'])) {
throw new \Exception("Invalid repository key structure: " . json_encode($keyData));
}
$cmd = sprintf(
'sudo %s keys %s %s %s %s',
escapeshellarg($this->helperScriptPath),
escapeshellarg($keyData['key_url']),
escapeshellarg($keyData['keyring']),
escapeshellarg($keyData['repo']),
escapeshellarg($keyData['sources'])
);
$return = shell_exec($cmd);
if (strpos(strtolower($return), 'ok') === false) {
throw new \Exception("Failed to add repository and key");
}
}
}
/**
* Parses and returns a downloaded plugin manifest
*
* @param string $pluginDir
* @return array json
*/
private function parseManifest($pluginDir): array
{
$manifestPath = $pluginDir . DIRECTORY_SEPARATOR . 'manifest.json';
if (!file_exists($manifestPath)) {
throw new \Exception('manifest.json file not found.');
}
$json = file_get_contents($manifestPath);
$manifest = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception('Failed to parse manifest.json: ' . json_last_error_msg());
}
return $manifest;
}
/**
* Retrieves a plugin archive and extracts it to /tmp
*
* @param string $archiveUrl
* @return array
* @throws \Exception
*/
private function getPluginArchive(string $archiveUrl): array
{
$tempFile = '';
$extractDir = '';
try {
$tempFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('plugin_', true) . '.zip';
$extractDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('plugin_', true);
$data = @file_get_contents($archiveUrl); // suppress PHP warnings for better exception handling
if ($data === false) {
$error = error_get_last();
throw new \Exception('Failed to download archive: ' . ($error['message'] ?? 'Unknown error'));
}
file_put_contents($tempFile, $data);
if (!mkdir($extractDir) && !is_dir($extractDir)) {
throw new \Exception('Failed to create temp directory.');
}
$cmd = escapeshellcmd("unzip -o $tempFile -d $extractDir");
$output = shell_exec($cmd);
if ($output === null) {
throw new \Exception('Failed to extract plugin archive.');
}
$extractedDirs = glob($extractDir . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR);
if (empty($extractedDirs)) {
throw new \Exception('No directories found in plugin archive.');
}
$pluginDir = $extractedDirs[0];
return [$tempFile, $extractDir, $pluginDir];
} catch (\Exception $e) {
if (!empty($tempFile) && file_exists($tempFile)) {
unlink($tempFile);
}
if (!empty($extractDir) && is_dir($extractDir)) {
rmdir($extractDir);
}
throw new \Exception('Error occurred during plugin archive retrieval: ' . $e->getMessage());
}
}
private function deleteDir(string $dir): void
{
if (!is_dir($dir)) {
return;
}
$items = array_diff(scandir($dir), ['.', '..']);
foreach ($items as $item) {
$itemPath = $dir . DIRECTORY_SEPARATOR . $item;
is_dir($itemPath) ? $this->deleteDir($itemPath) : unlink($itemPath);
}
rmdir($dir);
}
/**
* Returns a list of available plugins formatted as an HTML table
*
* @param array $plugins
* @return string $html
*/
public function getHTMLPluginsTable(array $plugins): string
{
$html = '<table class="table table-striped table-hover">';
$html .= '<thead><tr>';
$html .= '<th scope="col">Name</th>';
$html .= '<th scope="col">Version</th>';
$html .= '<th scope="col">Description</th>';
$html .= '<th scope="col"></th>';
$html .= '</tr></thead><tbody>';
foreach ($plugins as $plugin) {
$manifestData = $plugin['manifest'] ?? [];
$installed = $plugin['installed'] ?? false;
$manifest = htmlspecialchars(json_encode($manifestData), ENT_QUOTES, 'UTF-8');
if ($installed === true) {
$button = '<button type="button" class="btn btn-outline btn-primary btn-sm text-nowrap"
name="plugin-details" data-bs-toggle="modal" data-bs-target="#install-user-plugin"
data-plugin-manifest="' .$manifest. '" data-plugin-installed="' .$installed. '">' . _("Installed") .'</button>';
} elseif (!RASPI_MONITOR_ENABLED) {
$button = '<button type="button" class="btn btn-outline btn-primary btn-sm text-nowrap"
name="install-plugin" data-bs-toggle="modal" data-bs-target="#install-user-plugin"
data-plugin-manifest="' .$manifest. '" data-repo-public="' .$this->repoPublic. '">' . _("Details") .'</button>';
}
$icon = htmlspecialchars($manifestData['icon'] ?? '');
$pluginDocs = htmlspecialchars($manifestData['plugin_docs'] ?? '');
$nameText = htmlspecialchars($manifestData['name'] ?? 'Unknown Plugin');
$name = '<i class="' .$icon. ' link-secondary me-2"></i><a href="'
.$pluginDocs
.'" target="_blank">'
.$nameText. '</a>';
$version = htmlspecialchars($manifestData['version'] ?? 'N/A');
$description = htmlspecialchars($manifestData['description'] ?? 'No description available');
$html .= '<tr><td>' .$name. '</td>';
$html .= '<td>' .$version. '</td>';
$html .= '<td>' .$description. '</td>';
$html .= '<td>' .$button. '</td></tr>';
}
$html .= '</tbody></table>';
return $html;
}
/**
* Determines remote repository of installed application
*
* @return boolean; true if public repo
*/
public function getRepository(): bool
{
$output = [];
exec('git -C ' . escapeshellarg($this->rootPath) . ' remote -v', $output);
foreach ($output as $line) {
if (preg_match('#github\.com/RaspAP/(raspap-\w+)#', $line, $matches)) {
$repo = $matches[1];
$public = ($repo === 'raspap-webgui');
return $public;
}
}
return false;
}
}

View file

@ -95,48 +95,50 @@ class Sysinfo
/*
* Returns RPi Model and PCB Revision from Pi Revision Code (cpuinfo)
* @see http://www.raspberrypi-spy.co.uk/2012/09/checking-your-raspberry-pi-board-version/
* @see https://github.com/raspberrypi/documentation/blob/develop/documentation/asciidoc/computers/raspberry-pi/revision-codes.adoc
*/
public function rpiRevision()
{
$revisions = array(
'0002' => 'Model B Revision 1.0',
'0003' => 'Model B Revision 1.0 + ECN0001',
'0004' => 'Model B Revision 2.0 (256 MB)',
'0005' => 'Model B Revision 2.0 (256 MB)',
'0006' => 'Model B Revision 2.0 (256 MB)',
'0007' => 'Model A',
'0008' => 'Model A',
'0009' => 'Model A',
'000d' => 'Model B Revision 2.0 (512 MB)',
'000e' => 'Model B Revision 2.0 (512 MB)',
'000f' => 'Model B Revision 2.0 (512 MB)',
'0010' => 'Model B+',
'0013' => 'Model B+',
'0011' => 'Compute Module',
'0012' => 'Model A+',
'a01041' => 'a01041',
'a21041' => 'a21041',
'900092' => 'PiZero 1.2',
'900093' => 'PiZero 1.3',
'9000c1' => 'PiZero W',
'a02082' => 'Pi 3 Model B',
'a22082' => 'Pi 3 Model B',
'a32082' => 'Pi 3 Model B',
'a52082' => 'Pi 3 Model B',
'a020d3' => 'Pi 3 Model B+',
'a220a0' => 'Compute Module 3',
'a020a0' => 'Compute Module 3',
'0002' => 'Raspberry Pi Model B Rev 1.0',
'0003' => 'Raspberry Pi Model B Rev 1.0',
'0004' => 'Raspberry Pi Model B Rev 2.0',
'0005' => 'Raspberry Pi Model B Rev 2.0',
'0006' => 'Raspberry Pi Model B Rev 2.0',
'0007' => 'Raspberry Pi Model A',
'0008' => 'Raspberry Pi Model A',
'0009' => 'Raspberry Pi Model A',
'000d' => 'Raspberry Pi Model B Rev 2.0',
'000e' => 'Raspberry Pi Model B Rev 2.0',
'000f' => 'Raspberry Pi Model B Rev 2.0',
'0010' => 'Raspberry Pi Model B+',
'0013' => 'Raspberry Pi Model B+',
'0011' => 'Compute Module 1',
'0012' => 'Raspberry Pi Model A+',
'a01041' => 'Raspberry Pi 2 Model B',
'a21041' => 'Raspberry Pi 2 Model B',
'900092' => 'Raspberry Pi Zero 1.2',
'900093' => 'Raspberry Pi Zero 1.3',
'9000c1' => 'Raspberry Pi Zero W',
'a02082' => 'Raspberry Pi 3 Model B',
'a22082' => 'Raspberry Pi 3 Model B',
'a32082' => 'Raspberry Pi 3 Model B',
'a52082' => 'Raspberry Pi 3 Model B+',
'9020e0' => 'Raspberry Pi 3 Model A+',
'a02100' => 'Compute Module 3+',
'a03111' => 'Model 4B Revision 1.1 (1 GB)',
'b03111' => 'Model 4B Revision 1.1 (2 GB)',
'c03111' => 'Model 4B Revision 1.1 (4 GB)',
'a03111' => 'Raspberry Pi 4 Model B (1 GB)',
'b03111' => 'Raspberry Pi 4 Model B (2 GB)',
'c03111' => 'Raspberry Pi 4 Model B (4 GB)',
'b03112' => 'Raspberry Pi 4 Model B (2 GB)',
'c03112' => 'Raspberry Pi 4 Model B (4 GB)',
'd03114' => 'Raspberry Pi 4 Model B (8 GB)',
'902120' => 'Raspberry Pi Zero 2 W',
'a03140' => 'Compute Module 4 (1 GB)',
'b03140' => 'Compute Module 4 (2 GB)',
'c03140' => 'Compute Module 4 (4 GB)',
'd03140' => 'Compute Module 4 (8 GB)',
'c04170' => 'Pi 5 (4 GB)',
'd04170' => 'Pi 5 (8 GB)'
'c04170' => 'Raspberry Pi 5 (4 GB)',
'd04170' => 'Raspberry Pi 5 (8 GB)'
);
$cpuinfo_array = '';
@ -155,5 +157,48 @@ class Sysinfo
}
}
}
/**
* Determines if ad blocking is enabled and active
*
* @return bool $status
*/
public function adBlockStatus(): bool
{
exec('cat '. RASPI_ADBLOCK_CONFIG, $return);
$arrConf = ParseConfig($return);
if (sizeof($arrConf) > 0) {
$enabled = true;
}
exec('pidof dnsmasq | wc -l', $dnsmasq);
$dnsmasq_state = ($dnsmasq[0] > 0);
$status = $dnsmasq_state && $enabled ? true : false;
return $status;
}
/**
* Determines if a VPN interface is active
*
* @return string $interface
*/
public function getActiveVpnInterface(): ?string
{
$output = shell_exec('ip a 2>/dev/null');
if (!$output) {
return null;
}
$vpnInterfaces = ['wg0', 'tun0', 'tailscale0'];
// interface must have an 'UP' status and an IP address
foreach ($vpnInterfaces as $interface) {
if (strpos($output, "$interface:") !== false) {
if (preg_match("/\d+: $interface: .*<.*UP.*>/", $output) &&
preg_match("/inet\b.*$interface/", $output)) {
return $interface;
}
}
}
return null;
}
}

View file

@ -0,0 +1,136 @@
<?php
/**
* CSRF tokenizer class
*
* @description CSRF tokenizer class for RaspAP
* @author Bill Zimmerman <billzimmerman@gmail.com>
* @author Martin Glaß <mail@glasz.org>
* @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
*/
declare(strict_types=1);
namespace RaspAP\Tokens;
class CSRFTokenizer {
// Constructor
public function __construct()
{
$this->ensureSession();
// ensure a CSRF token exists in the session
if (empty($_SESSION['csrf_token'])) {
$this->ensureCSRFSessionToken();
header("Location: " .$_SERVER['REQUEST_URI']);
exit;
}
if ($this->csrfValidateRequest()) {
$token = $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
if (!$this->CSRFValidate($token)) {
$this->handleInvalidCSRFToken();
}
}
}
/**
* Saves a CSRF token in the session
*/
public function ensureCSRFSessionToken(): void
{
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
}
/**
* Adds a CSRF Token to form
*/
public function CSRFTokenFieldTag(): string
{
$token = htmlspecialchars($_SESSION['csrf_token']);
return '<input type="hidden" name="csrf_token" value="' . $token . '">';
}
/**
* Returns a CSRF meta tag (for use with xhr, for example)
*/
public function CSRFMetaTag(): string
{
// if session has expired or user has logged out,
// create a new session and token
if (empty($_SESSION['csrf_token'])) {
$this->ensureSession();
$this->ensureCSRFSessionToken();
return $_SESSION['csrf_token'];
} else {
$token = htmlspecialchars($_SESSION['csrf_token']);
return '<meta name="csrf_token" content="' . $token . '">';
}
}
/**
* Validates a CSRF Token
*
* @param string $token
*/
public function CSRFValidate(string $token): bool
{
if (empty($_SESSION['csrf_token']) || !is_string($_SESSION['csrf_token'])) {
error_log('Session expired or CSRF token is missing.');
header('Location: /login');
exit;
}
$post_token = $token ?? null;
$header_token = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? null;
if (empty($post_token) && is_null($header_token)) {
error_log('CSRF token missing in the request');
return false;
}
$request_token = $post_token ?: $header_token;
if (hash_equals($_SESSION['csrf_token'], $request_token)) {
return true;
} else {
error_log('CSRF token mismatch');
return false;
}
}
/**
* Should the request be CSRF-validated?
*/
public function csrfValidateRequest(): bool
{
$request_method = strtolower($_SERVER['REQUEST_METHOD']);
return in_array($request_method, [ "post", "put", "patch", "delete" ]);
}
/**
* Handle invalid CSRF
*/
public function handleInvalidCSRFToken(): string
{
if (function_exists('http_response_code')) {
http_response_code(500);
echo 'Invalid CSRF token';
} else {
header('HTTP/1.1 500 Internal Server Error');
header('Content-Type: text/plain');
echo 'Invalid CSRF token';
}
exit;
}
protected function ensureSession()
{
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
}
}

368
src/RaspAP/UI/Dashboard.php Normal file
View file

@ -0,0 +1,368 @@
<?php
/**
* Dashboard UI class
*
* @description A class for rendering the RaspAP dashboard
* @author Bill Zimmerman <billzimmerman@gmail.com>
* @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
*/
namespace RaspAP\UI;
class Dashboard {
private string $firewallConfig;
public function __construct() {
$this->firewallConfig = RASPI_CONFIG.'/networking/firewall.conf';
}
/*
* Returns the management page for an associated VPN
*
* @param string $interface
* @return string
*/
public function getVpnManaged(?string $interface = null): ?string
{
switch ($interface) {
case 'wg0':
return '/wg_conf';
case 'tun0':
return '/openvpn_conf';
case 'tailscale0':
return '/plugin__Tailscale';
default:
return null;
}
}
/*
* Parses output of iw, extracts frequency (MHz) and classifies
* it as 2.4 or 5 GHz. Returns null if not found
*
* @param string $interface
* @return string frequency
*/
public function getFrequencyBand(string $interface): ?string
{
$output = shell_exec("iw dev " . escapeshellarg($interface) . " info 2>/dev/null");
if (!$output) {
return null;
}
if (preg_match('/channel\s+\d+\s+\((\d+)\s+MHz\)/', $output, $matches)) {
$frequency = (int)$matches[1];
if ($frequency >= 2400 && $frequency < 2500) {
return "2.4";
} elseif ($frequency >= 5000 && $frequency < 6000) {
return "5";
}
}
return null;
}
/*
* Aggregate function that fetches output of ip and calls
* functions to parse output into discreet network properties
*
* @param string $interface
* @return array
*/
public function getInterfaceDetails(string $interface): array
{
$output = shell_exec('ip a show ' . escapeshellarg($interface));
if (!$output) {
return [
'mac' => _('No MAC Address Found'),
'ipv4' => 'None',
'ipv4_netmask' => '-',
'ipv6' => _('No IPv6 Address Found'),
'state' => 'unknown'
];
}
$cleanOutput = preg_replace('/\s\s+/', ' ', implode(' ', explode("\n", $output)));
return [
'mac' => $this->getMacAddress($cleanOutput),
'ipv4' => $this->getIPv4Addresses($cleanOutput),
'ipv4_netmask' => $this->getIPv4Netmasks($cleanOutput),
'ipv6' => $this->getIPv6Addresses($cleanOutput),
'state' => $this->getInterfaceState($cleanOutput),
];
}
private function getMacAddress(string $output): string
{
return preg_match('/link\/ether ([0-9a-f:]+)/i', $output, $matches) ? $matches[1] : _('No MAC Address Found');
}
private function getIPv4Addresses(string $output): string
{
if (!preg_match_all('/inet (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/([0-3][0-9])/i', $output, $matches, PREG_SET_ORDER)) {
return 'None';
}
$addresses = array_column($matches, 1);
return implode(' ', $addresses);
}
private function getIPv4Netmasks(string $output): string
{
if (!preg_match_all('/inet (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/([0-3][0-9])/i', $output, $matches, PREG_SET_ORDER)) {
return '-';
}
$netmasks = array_map(fn($match) => long2ip(-1 << (32 - (int)$match[2])), $matches);
return implode(' ', $netmasks);
}
private function getIPv6Addresses(string $output): string
{
return preg_match_all('/inet6 ([a-f0-9:]+)/i', $output, $matches) && isset($matches[1])
? implode(' ', $matches[1])
: _('No IPv6 Address Found');
}
private function getInterfaceState(string $output): string
{
return preg_match('/state (UP|DOWN)/i', $output, $matches) ? $matches[1] : 'unknown';
}
public function getWirelessDetails(string $interface): array
{
$output = shell_exec('iw dev ' . escapeshellarg($interface) . ' info');
if (!$output) {
return ['bssid' => '-', 'ssid' => '-'];
}
$cleanOutput = preg_replace('/\s\s+/', ' ', trim($output)); // Fix here
return [
'bssid' => $this->getConnectedBSSID($cleanOutput),
'ssid' => $this->getSSID($cleanOutput),
];
}
private function getConnectedBSSID(string $output): string
{
return preg_match('/Connected to (([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2}))/i', $output, $matches)
? $matches[1]
: '-';
}
private function getSSID(string $output): string
{
return preg_match('/ssid ([^\n\s]+)/i', $output, $matches)
? $matches[1]
: '-';
}
/*
* Parses the output of iw to obtain a list of wireless clients
*
* @return integer $clientCount
*/
public function getWirelessClients()
{
exec('iw dev wlan0 station dump', $output, $status);
if ($status !== 0) {
return 0;
}
// enumerate 'station' entries (each represents a wireless client)
$clientCount = 0;
foreach ($output as $line) {
if (strpos($line, 'Station') === 0) {
$clientCount++;
}
}
return $clientCount;
}
/*
* Retrieves ethernet neighbors from ARP cache, parses DHCP leases
* to find matching MAC addresses and returns only clients that
* exist in both sources
*
* @return int $ethernetClients
*/
public function getEthernetClients(): int
{
$ethernetClients = [];
// Get ARP table entries and filter ethernet clients
$arpOutput = shell_exec("ip neigh show");
if ($arpOutput) {
foreach (explode("\n", trim($arpOutput)) as $line) {
/* match both traditional interface names (eth0...n) and predictable names like
* enp3s0 (PCI ethernet)
* eno1 (onboard ethernet)
* ens160, etc.
* ...ignoring STALE entries
*/
if (preg_match('/^(\S+) dev (eth[0-9]+|en\w+) lladdr (\S+) (REACHABLE|DELAY|PROBE)/', $line, $matches)) {
$ethernetClients[$matches[3]] = $matches[1]; // MAC => IP
}
}
}
// compare against active DHCP leases
$leaseFile = RASPI_DNSMASQ_LEASES;
if (file_exists($leaseFile)) {
$leases = file($leaseFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$activeLeases = [];
foreach ($leases as $lease) {
$fields = preg_split('/\s+/', $lease);
if (count($fields) >= 3) {
$activeLeases[$fields[1]] = true; // MAC as key
}
}
// keep only clients that exist in the DHCP lease file
$ethernetClients = array_intersect_key($ethernetClients, $activeLeases);
}
return count($ethernetClients);
}
public function formatClientLabel($clientCount)
{
return ngettext('client', 'clients', $clientCount);
}
/*
* Determines the device's primary connection type by
* parsing the output of ip route; the interface listed
* as the default gateway is used for internet connectivity.
*
* The following interface classifications are matched:
* - ethernet (eth0, enp*, ens*, enx*)
* - wireless (wlan*, wlp*, wlx*)
* - tethered USB (usb*, eth1-9)
* - cellular (ppp*, wwan*, wwp*)
* - fallback
* @return string
*/
public function getConnectionType(): string
{
// get the interface associated with the default route
$interface = trim(shell_exec("ip route show default | awk '{print $5}'"));
if (empty($interface)) {
return 'unknown';
}
// classify interface type
if (preg_match('/^eth0|enp\d+s\d+|ens\d+s\d+|enx[0-9a-f]*/', $interface)) {
return 'ethernet';
}
if (preg_match('/^wlan\d+|wlp\d+s\d+|wlx[0-9a-f]*/', $interface)) {
return 'wireless';
}
if (preg_match('/^usb\d+|^eth[1-9]\d*/', $interface)) {
return 'tethering';
}
if (preg_match('/^ppp\d+|wwan\d+|wwp\d+s\d+/', $interface)) {
return 'cellular';
}
// if none match, return the interface name as a fallback
return "other ($interface)";
}
/**
* Returns a fontawesome icon associated with a connection
* type/class
*
* @param $type
* @return string
*/
public function getConnectionIcon($type): string
{
switch (strtolower($type)) {
case 'ethernet':
return 'fa-ethernet';
case 'wireless':
return 'fa-wifi';
case 'tethering':
return 'fa-mobile-alt';
case 'cellular':
return 'fa-broadcast-tower';
default:
return 'fa-question-circle'; // unknown
}
}
/**
* Retrieves the firewall's current status
*
* @return bool status
*/
public function firewallEnabled(): bool
{
$conf = array();
if (file_exists($this->firewallConfig) ) {
$conf = parse_ini_file($this->firewallConfig);
}
if ($conf["firewall-enable"] == 1) {
return true;
}
return false;
}
/*
* Returns an SVG resource associated with a Pi revision
*
* @param string $deviceName
* @return string
*/
public function getDeviceImage($deviceName): string
{
if (stripos($deviceName, 'zero') !== false) {
return 'zero.php';
}
if (stripos($deviceName, 'compute') !== false) {
return 'compute.php';
}
return 'default.php';
}
/**
* Handles dashboard page actions
*
* @param string $state
* @param array $post
* @param object $status
* @param string $interface
*/
public function handlePageAction(string $state, array $post, $status, string $interface): object
{
if (!RASPI_MONITOR_ENABLED) {
if (isset($post['ifdown_wlan0'])) {
if ($state === 'up') {
$status->addMessage(sprintf(_('Interface %s is going %s'), $interface, _('down')), 'warning');
exec('sudo ip link set ' .escapeshellarg($interface). ' down');
$status->addMessage(sprintf(_('Interface %s is %s'), $interface, _('down')), 'success');
} elseif ($details['state'] === 'unknown') {
$status->addMessage(_('Interface state unknown'), 'danger');
} else {
$status->addMessage(sprintf(_('Interface %s is already %s'), $interface, _('down')), 'warning');
}
} elseif (isset($post['ifup_wlan0'])) {
if ($state === 'down') {
$status->addMessage(sprintf(_('Interface %s is going %s'), $interface, _('up')), 'warning');
exec('sudo ip link set ' .escapeshellarg($interface). ' up');
exec('sudo ip -s a f label ' .escapeshellarg($interface));
usleep(250000);
$status->addMessage(sprintf(_('Interface %s is %s'), $interface, _('up')), 'success');
} elseif ($state === 'unknown') {
$status->addMessage(_('Interface state unknown'), 'danger');
} else {
$status->addMessage(sprintf(_('Interface %s is already %s'), $interface, _('up')), 'warning');
}
}
return $status;
}
}
}

View file

@ -16,7 +16,7 @@ class Sidebar {
public function __construct() {
// Load default sidebar items
$this->addItem(_('Dashboard'), 'fa-solid fa-gauge-high', 'wlan0_info', 10);
$this->addItem(_('Hotspot'), 'far fa-dot-circle', 'hostapd_conf', 20,
$this->addItem(_('Hotspot'), 'fas fa-bullseye', 'hostapd_conf', 20,
fn() => RASPI_HOTSPOT_ENABLED
);
$this->addItem(_('DHCP Server'), 'fas fa-exchange-alt', 'dhcpd_conf', 30,
@ -39,9 +39,6 @@ class Sidebar {
);
$this->addItem(_(getProviderValue($_SESSION["providerID"], "name")), 'fas fa-shield-alt', 'provider_conf', 90,
fn() => RASPI_VPN_PROVIDER_ENABLED
);
$this->addItem(_('Authentication'), 'fas fa-user-lock', 'auth_conf', 100,
fn() => RASPI_CONFAUTH_ENABLED
);
$this->addItem(_('Data usage'), 'fas fa-chart-area', 'data_use', 110,
fn() => RASPI_VNSTAT_ENABLED

View file

@ -69,7 +69,7 @@ require_once 'app/lib/Parsedown.php';
</div>
<div class="modal-body">
<div class="col-md-12 mb-3 mt-1" id="msg-check-update"><?php echo _("Application is being updated..."); ?></div>
<div class="ms-5"><i class="fas fa-check me-2 invisible" id="updateStep1"></i><?php echo _("Configuring update"); ?></div>
<div class="ms-5"><i class="fas fa-check me-2" id="updateStep1"></i><?php echo _("Configuring update"); ?></div>
<div class="ms-5"><i class="fas fa-check me-2 invisible" id="updateStep2"></i><?php echo _("Updating sources"); ?></div>
<div class="ms-5"><i class="fas fa-check me-2 invisible" id="updateStep3"></i><?php echo _("Installing package updates"); ?></div>
<div class="ms-5"><i class="fas fa-check me-2 invisible" id="updateStep4"></i><?php echo _("Downloading latest files"); ?></div>

View file

@ -28,7 +28,7 @@
<div class="card-body">
<?php $status->showMessages(); ?>
<form role="form" action="adblock_conf" enctype="multipart/form-data" method="POST">
<?php echo CSRFTokenFieldTag() ?>
<?php echo \RaspAP\Tokens\CSRF::hiddenField();?>
<!-- Nav tabs -->
<ul class="nav nav-tabs">
<li class="nav-item"><a class="nav-link active" id="blocklisttab" href="#adblocklistsettings" data-bs-toggle="tab"><?php echo _("Blocklist settings"); ?></a></li>

View file

@ -30,10 +30,8 @@
<option disabled="disabled"></option>
<?php echo optionsForSelect(blocklistProviders()) ?>
</select>
<div class="input-group-append">
<button class="btn btn-sm btn-outline-secondary rounded-end" type="button" onclick="updateBlocklist()"><?php echo _("Update now"); ?></button>
<span id="cbxblocklist-status" class="input-group-addon check-hidden ms-2 mt-1"><i class="fas fa-check"></i></span>
</div>
<button class="btn btn-sm btn-outline-secondary rounded-end" type="button" onclick="updateBlocklist()"><?php echo _("Update now"); ?></button>
<span id="cbxblocklist-status" class="input-group-addon check-hidden ms-2 mt-1"><i class="fas fa-check"></i></span>
</div>
</div>
</div>

View file

@ -1,10 +1,17 @@
<?php ob_start() ?>
<?php if (!RASPI_MONITOR_ENABLED) : ?>
<input type="submit" class="btn btn-outline btn-primary" name="UpdateAdminPassword" value="<?php echo _("Save settings"); ?>" />
<input type="submit" class="btn btn-warning" name="logout" value="<?php echo _("Logout") ?>" onclick="disableValidation(this.form)"/>
<?php endif ?>
<?php $buttons = ob_get_clean(); ob_end_clean() ?>
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<div class="row">
<div class="col">
<i class="fas fa-user-lock me-2"></i><?php echo _("Authentication"); ?>
<div class="col">
<i class="fas fa-user-lock me-2"></i><?php echo _("Authentication"); ?>
</div>
</div><!-- /.row -->
</div><!-- /.card-header -->
@ -12,7 +19,7 @@
<?php $status->showMessages(); ?>
<h4><?php echo _("Authentication settings") ;?></h4>
<form role="form" action="auth_conf" method="POST" class="needs-validation" novalidate>
<?php echo CSRFTokenFieldTag() ?>
<?php echo \RaspAP\Tokens\CSRF::hiddenField(); ?>
<div class="row">
<div class="mb-3 col-md-6">
<label for="username"><?php echo _("Username"); ?></label>
@ -58,7 +65,7 @@
</div>
</div>
</div>
<input type="submit" class="btn btn-outline btn-primary" name="UpdateAdminPassword" value="<?php echo _("Save settings"); ?>" />
<?php echo $buttons ?>
</form>
</div><!-- /.card-body -->
<div class="card-footer"></div>

View file

@ -27,7 +27,7 @@
<div class="row" id="wpaConf">
<div class="col">
<form method="POST" action="wpa_conf" name="wpa_conf_form">
<?php echo CSRFTokenFieldTag() ?>
<?php echo \RaspAP\Tokens\CSRF::hiddenField(); ?>
<input type="hidden" name="client_settings" ?>
<div class="js-wifi-stations loading-spinner"></div>
</form>

View file

@ -1,149 +1,56 @@
<?php ob_start() ?>
<?php if (!RASPI_MONITOR_ENABLED) : ?>
<?php if ($state === "down") : ?>
<input type="submit" class="btn btn-success mt-2" value="<?php echo _("Start").' '.$interface ?>" name="ifup_wlan0" />
<?php else : ?>
<input type="submit" class="btn btn-warning mt-2" value="<?php echo _("Stop").' '.$interface ?>" name="ifdown_wlan0" />
<?php endif ?>
<?php endif ?>
<button type="button" onClick="window.location.reload();" class="btn btn-outline btn-primary mt-2"><i class="fas fa-sync-alt"></i> <?php echo _("Refresh") ?></a>
<?php $buttons = ob_get_clean(); ob_end_clean() ?>
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<div class="row">
<div class="col">
<i class="fas fa-tachometer-alt fa-fw me-2"></i><?php echo _("Dashboard"); ?>
</div>
<div class="col">
<button class="btn btn-light btn-icon-split btn-sm service-status float-end">
<span class="icon"><i class="fas fa-circle service-status-<?php echo $ifaceStatus ?>"></i></span>
<span class="text service-status"><?php echo strtolower($apInterface) .' '. _($ifaceStatus) ?></span>
</button>
</div>
<div class="col">
<i class="fas fa-tachometer-alt fa-fw me-2"></i>
<?php echo _("Dashboard"); ?>
</div>
<div class="col">
<button class="btn btn-light btn-icon-split btn-sm service-status float-end">
<span class="icon"><i class="fas fa-circle service-status-<?php echo $state ?>"></i></span>
<span class="text service-status"><?php echo strtolower($interface) .' '. _($state) ?></span>
</button>
</div>
</div><!-- /.row -->
</div><!-- /.card-header -->
<div class="card-body">
<div class="row">
<?php $status->showMessages(); ?>
<form action="wlan0_info" method="POST">
<?php echo \RaspAP\Tokens\CSRF::hiddenField(); ?>
<div class="col-lg-12">
<div class="card mb-3">
<div class="card-body">
<h4 class="card-title"><?php echo _("Hourly traffic amount"); ?></h4>
<div id="divInterface" class="d-none"><?php echo $apInterface; ?></div>
<div class="col-md-12">
<div class="col dbChart">
<canvas id="divDBChartBandwidthhourly"></canvas>
</div>
</div>
</div><!-- /.card-body -->
</div><!-- /.card-->
</div>
<!-- Nav tabs -->
<ul class="nav nav-tabs">
<li class="nav-item"><a class="nav-link active" id="statustab" href="#status" aria-controls="status" data-bs-toggle="tab"><?php echo _("Status"); ?></a></li>
<li class="nav-item"><a class="nav-link" id="datatab" href="#data" data-bs-toggle="tab"><?php echo _("Data usage"); ?></a></li>
</ul>
<div class="col-sm-6 align-items-stretch">
<div class="card h-100">
<div class="card-body wireless">
<h4 class="card-title"><?php echo _("Wireless Client"); ?></h4>
<div class="row ms-1">
<div class="col-sm">
<div class="row mb-1">
<div class="info-item col"><?php echo _("Connected To"); ?></div><div class="info-value col"><?php echo htmlspecialchars($connectedSSID, ENT_QUOTES); ?></div>
</div>
<div class="row mb-1">
<div class="info-item col"><?php echo _("Interface"); ?></div><div class="info-value col"><?php echo htmlspecialchars($clientInterface); ?></div>
</div>
<div class="row mb-1">
<div class="info-item col"><?php echo _("AP Mac Address"); ?></div><div class="info-value col"><?php echo htmlspecialchars($connectedBSSID, ENT_QUOTES); ?></div>
</div>
<div class="row mb-1">
<div class="info-item col"><?php echo _("Bitrate"); ?></div><div class="info-value col"><?php echo htmlspecialchars($bitrate, ENT_QUOTES); ?></div>
</div>
<div class="row mb-1">
<div class="info-item col"><?php echo _("Signal Level"); ?></div><div class="info-value col"><?php echo htmlspecialchars($signalLevel, ENT_QUOTES); ?></div>
</div>
<div class="row mb-1">
<div class="info-item col"><?php echo _("Transmit Power"); ?></div><div class="info-value col"><?php echo htmlspecialchars($txPower, ENT_QUOTES); ?></div>
</div>
<div class="row mb-1">
<div class="info-item col"><?php echo _("Frequency"); ?></div><div class="info-value col"><?php echo htmlspecialchars($frequency, ENT_QUOTES); ?></div>
</div>
</div>
<div class="col-md d-flex">
<script>var linkQ = <?php echo json_encode($strLinkQuality); ?>;</script>
<div class="chart-container">
<canvas id="divChartLinkQ"></canvas>
</div>
</div>
</div><!--row-->
</div><!-- /.card-body -->
</div><!-- /.card -->
</div><!-- /.col-md-6 -->
<div class="col-sm-6">
<div class="card h-100 mb-3">
<div class="card-body">
<h4 class="card-title"><?php echo _("Connected Devices"); ?></h4>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<?php if ($bridgedEnable == 1) : ?>
<th><?php echo _("MAC Address"); ?></th>
<?php else : ?>
<th><?php echo _("Host name"); ?></th>
<th><?php echo _("IP Address"); ?></th>
<th><?php echo _("MAC Address"); ?></th>
<?php endif; ?>
</tr>
</thead>
<tbody>
<?php if ($bridgedEnable == 1) : ?>
<tr>
<td><small class="text-muted"><?php echo _("Bridged AP mode is enabled. For Hostname and IP, see your router's admin page.");?></small></td>
</tr>
<?php endif; ?>
<?php foreach (array_slice($clients,0, 2) as $client) : ?>
<tr>
<?php if ($arrHostapdConf['BridgedEnable'] == 1): ?>
<td><?php echo htmlspecialchars($client, ENT_QUOTES) ?></td>
<?php else : ?>
<?php $props = explode(' ', $client) ?>
<td><?php echo htmlspecialchars($props[3], ENT_QUOTES) ?></td>
<td><?php echo htmlspecialchars($props[2], ENT_QUOTES) ?></td>
<td><?php echo htmlspecialchars($props[1], ENT_QUOTES) ?></td>
<?php endif; ?>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php if (sizeof($clients) >2) : ?>
<div class="col-lg-12 float-end">
<a class="btn btn-outline-info" role="button" href="<?php echo $moreLink ?>"><?php echo _("More");?> <i class="fas fa-chevron-right"></i></a>
</div>
<?php elseif (sizeof($clients) ==0) : ?>
<div class="col-lg-12 mt-3"><?php echo _("No connected devices");?></div>
<?php endif; ?>
</div><!-- /.table-responsive -->
</div><!-- /.card-body -->
</div><!-- /.card -->
</div><!-- /.col-md-6 -->
</div><!-- /.row -->
<div class="col-lg-12 mt-3">
<div class="row">
<form action="wlan0_info" method="POST">
<?php echo CSRFTokenFieldTag() ?>
<?php if (!RASPI_MONITOR_ENABLED) : ?>
<?php if (!$wlan0up) : ?>
<input type="submit" class="btn btn-success" value="<?php echo _("Start").' '.$apInterface ?>" name="ifup_wlan0" />
<?php else : ?>
<input type="submit" class="btn btn-warning" value="<?php echo _("Stop").' '.$apInterface ?>" name="ifdown_wlan0" />
<?php endif ?>
<?php endif ?>
<button type="button" onClick="window.location.reload();" class="btn btn-outline btn-primary"><i class="fas fa-sync-alt"></i> <?php echo _("Refresh") ?></a>
</form>
</div>
</div>
<!-- Tab panes -->
<div class="tab-content">
<?php echo renderTemplate("dashboard/status", $__template_data) ?>
<?php echo renderTemplate("dashboard/data", $__template_data) ?>
</div><!-- /.tab-content -->
<?php echo $buttons ?>
</form>
</div><!-- /.card-body -->
<div class="card-footer"><?php echo _("Information provided by ip and iw and from system"); ?></div>
<div class="card-footer"><?php echo _("Information provided by raspap.sysinfo"); ?></div>
</div><!-- /.card -->
</div><!-- /.col-lg-12 -->
</div><!-- /.row -->
<script type="text/javascript"<?php //echo ' nonce="'.$csp_page_nonce.'"'; ?>>
// js translations:
var t = new Array();
t['send'] = '<?php echo addslashes(_('Send')); ?>';
t['receive'] = '<?php echo addslashes(_('Receive')); ?>';
</script>

View file

@ -0,0 +1,19 @@
<div class="tab-pane" id="data">
<h4 class="card-title mt-3">
<?php echo _("Hourly traffic"); ?>
</h4>
<div class="col-md-12">
<div class="col dbChart">
<canvas id="divDBChartBandwidthhourly"></canvas>
</div>
</div>
</div><!-- /.tab-pane | data tab -->
<script type="text/javascript"<?php //echo ' nonce="'.$csp_page_nonce.'"'; ?>>
// js translations:
var t = new Array();
t['send'] = '<?php echo addslashes(_('Send')); ?>';
t['receive'] = '<?php echo addslashes(_('Receive')); ?>';
</script>

View file

@ -0,0 +1,123 @@
<div class="tab-pane active" id="status">
<h4 class="card-title mt-3">
<?php echo _("Current status"); ?>
</h4>
<div class="dashboard-container row">
<div class="connections-left col-lg-4">
<div class="connection-item">
<a href="/network_conf" class="<?php echo $ethernetActive; ?>"><span><?php echo _("Ethernet"); ?></span></a>
<a href="/network_conf" class="<?php echo $ethernetActive; ?>"><i class="fas fa-ethernet fa-2xl"></i></a>
</div>
<div class="connection-item">
<a href="/network_conf" class="<?php echo $wirelessActive; ?>"><span><?php echo _("Repeater"); ?></span>
<a href="/network_conf" class="<?php echo $wirelessActive; ?>"><i class="fas fa-wifi fa-2xl"></i></a>
</div>
<div class="connection-item">
<a href="/network_conf" class="<?php echo $tetheringActive; ?>"><span><?php echo _("Tethering"); ?></span></a>
<a href="/network_conf" class="<?php echo $tetheringActive; ?>"><i class="fas fa-mobile-alt fa-2xl"></i></a>
</div>
<div class="connection-item">
<a href="/network_conf" class="<?php echo $cellularActive; ?>"><span><?php echo _("Cellular"); ?></span></a>
<a href="/network_conf" class="<?php echo $cellularActive; ?>"><i class="fas fa-broadcast-tower fa-2xl"></i></a>
</div>
<img src="app/img/dashed.svg" class="dashed-lines" alt="">
<img src="<?php echo htmlspecialchars(renderConnection($connectionType)); ?>" class="solid-lines" alt="Network connection">
</div>
<div class="center-device col-12 col-lg-4">
<div class="center-device-top">
<a href="/system_info"><img class="device-illustration" src="app/img/devices/<?php echo $deviceImage; ?>" alt="<?php echo htmlspecialchars($revision, ENT_QUOTES); ?>"></a>
<div class="device-label"><a href="/system_info"><?php echo htmlspecialchars($revision, ENT_QUOTES); ?></a></div>
<div class="mt-1 small"><?php echo _("IP Address"); ?>: <a href="/dhcpd_conf"><?php echo htmlspecialchars($ipv4Address, ENT_QUOTES); ?></a></div>
<div class="small"><?php echo _("Netmask"); ?>: <a href="/dhcpd_conf"><?php echo htmlspecialchars($ipv4Netmask, ENT_QUOTES); ?></a></div>
<div class="small"><?php echo _("MAC Address"); ?>: <a href="/dhcpd_conf"><?php echo htmlspecialchars($macAddress, ENT_QUOTES); ?></a></div>
<div class="small"><?php echo _("SSID"); ?>: <a href="/hostapd_conf"><?php echo htmlspecialchars($ssid, ENT_QUOTES); ?></a></div>
</div>
<div class="bottom">
<div class="device-status">
<a href="/hostapd_conf">
<div class="status-item <?php echo $hostapdStatus; ?>">
<i class="fas fa-bullseye fa-2xl"></i>
<span><?php echo _('AP'); ?></span>
</div>
</a>
<a href="/hostapd_conf?tab=advanced">
<div class="status-item <?php echo $bridgedStatus; ?>">
<i class="fas fa-bridge fa-2xl"></i>
<span><?php echo _('Bridged'); ?></span>
</div>
</a>
<a href="/adblock_conf">
<div class="status-item <?php echo $adblockStatus; ?>">
<i class="far fa-hand-paper fa-2xl"></i>
<span><?php echo _('Adblock'); ?></span>
</div>
</a>
<a href="<?php echo $vpnManaged; ?>">
<div class="status-item <?php echo $vpnStatus; ?>">
<i class="fas fa-shield-alt fa-2xl"></i>
<span><?php echo _('VPN'); ?></span>
</div>
</a>
<?php echo $firewallManaged; ?>
<div class="status-item <?php echo $firewallStatus; ?>">
<span class="fa-stack fa-2xl" style="line-height: 0!important;height: 100%!important;">
<i class="fas fa-fire-flame-curved fa-stack-1x"></i>
<?php echo $firewallUnavailable; ?>
</span>
<span><?php echo _('Firewall'); ?></span>
</div>
</a>
</div>
<div class="wifi-bands">
<a href="/hostapd_conf"><span class="band <?php echo $freq5active; ?>"><?php echo _("5G"); ?></span></a>
<a href="/hostapd_conf"><span class="band <?php echo $freq24active; ?>"><?php echo _("2.4G"); ?></span></a>
</div>
</div>
<div class="clients-mobile">
<div class="client-type">
<a href="/network_conf">
<i class="fas fa-globe"></i>
<div class="client-count">
<i class="fas <?php echo $connectionIcon; ?> badge-icon"></i>
</div>
</a>
</div>
<div class="client-type">
<a href="/dhcpd_conf">
<i class="fas fa-laptop <?php echo $totalClientsActive; ?>"></i>
<span class="client-count"><?php echo $totalClients; ?></span>
</a>
</div>
</div>
</div>
<div class="connections-right col-lg-4">
<div class="d-flex flex-column justify-content-around h-100">
<div class="connection-item connection-right">
<a href="/dhcpd_conf" class="<?php echo $wirelessClientActive; ?>">
<span class="fa-stack">
<i class="fas fa-laptop fa-stack-1x fa-2xl"></i>
<i class="fas fa-wifi fa-stack-1x fa-xs"></i>
</span>
</a>
<a href="/dhcpd_conf"><span class="text-nowrap <?php echo $wirelessClientActive; ?>"><?php echo $wirelessClientLabel; ?></span></a>
</div>
<div class="connection-item connection-right">
<a href="/dhcpd_conf" class="<?php echo $ethernetClientActive; ?>">
<span class="fa-stack">
<i class="fas fa-laptop fa-stack-1x fa-2xl"></i>
<i class="fas fa-ethernet fa-stack-1x fa-xs"></i>
</span>
</a>
<a href="/dhcpd_conf"><span class="text-nowrap <?php echo $ethernetClientActive; ?>"><?php echo $ethernetClientLabel; ?></span></a>
</div>
</div>
<?php echo renderClientConnections($wirelessClients, $ethernetClients); ?>
<img src="app/img/right-dashed.svg" class="dashed-lines dashed-lines-right" alt="">
</div>
</div>
</div><!-- /.tab-pane | status tab -->

View file

@ -30,7 +30,7 @@
<div class="card-body">
<?php $status->showMessages(); ?>
<form method="POST" action="dhcpd_conf" class="js-dhcp-settings-form needs-validation" novalidate>
<?php echo CSRFTokenFieldTag() ?>
<?php echo \RaspAP\Tokens\CSRF::hiddenField(); ?>
<!-- Nav tabs -->
<ul class="nav nav-tabs mb-3">

View file

@ -20,11 +20,17 @@
<tbody>
<?php foreach ($leases as $lease) : ?>
<tr>
<?php foreach (explode(' ', $lease) as $prop) : ?>
<td><?php echo htmlspecialchars($prop, ENT_QUOTES) ?></td>
<?php endforeach ?>
<?php
$props = explode(' ', $lease);
if (!empty($props)) {
$props[0] = date('Y-m-d H:i:s', (int)$props[0]);
}
?>
<?php foreach ($props as $prop) : ?>
<td><?php echo htmlspecialchars($prop, ENT_QUOTES) ?></td>
<?php endforeach ?>
</tr>
<?php endforeach ?>
<?php endforeach ?>
</tbody>
</table>
</div><!-- /.table-responsive -->

View file

@ -36,7 +36,7 @@
<div class="card-header">
<div class="row">
<div class="col">
<i class="far fa-dot-circle me-2"></i><?php echo _("Hotspot"); ?>
<i class="fas fa-bullseye me-2"></i><?php echo _("Hotspot"); ?>
</div>
<div class="col">
<button class="btn btn-light btn-icon-split btn-sm service-status float-end">
@ -50,7 +50,7 @@
<div class="card-body">
<?php $status->showMessages(); ?>
<form role="form" action="hostapd_conf" method="POST" class="needs-validation" novalidate>
<?php echo CSRFTokenFieldTag() ?>
<?php echo \RaspAP\Tokens\CSRF::hiddenField(); ?>
<!-- Nav tabs -->
<ul class="nav nav-tabs">

43
templates/login.php Executable file
View file

@ -0,0 +1,43 @@
<!-- fullscreen modal -->
<div class="modal" id="modal-admin-login" data-bs-backdrop="static" data-bs-keyboard="false" role="dialog" aria-labelledby="ModalLabel" aria-hidden="true">
<div class="modal-dialog modal-fullscreen" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="row h-100 justify-content-center align-items-center">
<div class="col-12">
<!-- branding -->
<div class="text-center mb-3">
<img src="app/img/raspAP-logo.php" class="navbar-logo" alt="RaspAP logo" class="img-fluid" style="max-width: 100px;">
<h2 class="login-brand"><?php echo htmlspecialchars(RASPI_BRAND_TEXT); ?></h2>
<div class="mt-2 admin-login"><?php echo _("Administrator login") ?></div>
<div class="text-center text-danger mt-1 mb-3"><?php echo $status ?></div>
</div>
<div class="text-center mb-4">
<form id="admin-login-form" action="login" method="POST" class="needs-validation" novalidate>
<?php echo \RaspAP\Tokens\CSRF::hiddenField(); ?>
<div class="form-group">
<input type="hidden" name="login-auth">
<input type="hidden" id="redirect-url" name="redirect-url" value="<?php echo htmlspecialchars($redirectUrl, ENT_QUOTES, 'UTF-8'); ?>">
<input type="text" class="form-control" id="username" name="username" placeholder="<?php echo _("Username") ?>" required>
</div>
<div class="mt-2">
<div class="input-group has-validation">
<input type="password" class="form-control rounded-start border-end-0 no-right-radius" id="password" name="password" placeholder="<?php echo _("Password") ?>" required>
<button class="btn bg-white btn-passwd-append border-start-0 js-toggle-password" type="button" id="passwd-toggle" data-bs-target="[name=password]" data-toggle-with="fas fa-eye-slash text-secondary text-opacity-50">
<i class="fas fa-eye text-secondary text-opacity-50"></i>
</button>
</div>
</div>
<button type="submit" class="btn btn-outline btn-admin-login rounded-pill w-75 mt-4"><?php echo _("Login") ?></button>
<div class="small mt-2"><a href="https://docs.raspap.com/authentication/#restoring-defaults" target="_blank"><?php echo _("Forgot password") ?></a></div>
<img src="app/img/uri-qr-code.php?uri=https://docs.raspap.com/authentication/" class="figure-img img-fluid mt-2" alt="RaspAP docs" style="width:75px;">
</form>
</div>
</div><!-- /.col -->
</div><!-- /.row -->
</div><!-- /.modal-body -->
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>

View file

@ -29,7 +29,7 @@
<div class="card-body">
<?php $status->showMessages(); ?>
<form role="form" action="openvpn_conf" enctype="multipart/form-data" method="POST">
<?php echo CSRFTokenFieldTag() ?>
<?php echo \RaspAP\Tokens\CSRF::hiddenField(); ?>
<!-- Nav tabs -->
<ul class="nav nav-tabs">
<li class="nav-item"><a class="nav-link active" id="clienttab" href="#openvpnclient" data-bs-toggle="tab"><?php echo _("Client settings"); ?></a></li>

View file

@ -28,7 +28,7 @@
<div class="card-body">
<?php $status->showMessages(); ?>
<form role="form" action="provider_conf" enctype="multipart/form-data" method="POST">
<?php echo CSRFTokenFieldTag() ?>
<?php echo \RaspAP\Tokens\CSRF::hiddenField(); ?>
<!-- Nav tabs -->
<ul class="nav nav-tabs">
<li class="nav-item"><a class="nav-link active" id="clienttab" href="#providerclient" data-bs-toggle="tab"><?php echo _("Settings"); ?></a></li>

2
templates/restapi.php Normal file → Executable file
View file

@ -28,7 +28,7 @@
<div class="card-body">
<?php $status->showMessages(); ?>
<form role="form" action="restapi_conf" method="POST" class="needs-validation" novalidate>
<?php echo CSRFTokenFieldTag() ?>
<?php echo \RaspAP\Tokens\CSRF::hiddenField(); ?>
<!-- Nav tabs -->
<ul class="nav nav-tabs">
<li class="nav-item"><a class="nav-link active" id="restapisettingstab" href="#restapisettings" data-bs-toggle="tab"><?php echo _("Settings"); ?></a></li>

Some files were not shown because too many files have changed in this diff Show more