diff --git a/includes/dashboard.php b/includes/dashboard.php
index ed7032ba..bc91a8ed 100755
--- a/includes/dashboard.php
+++ b/includes/dashboard.php
@@ -7,187 +7,249 @@ require_once 'includes/functions.php';
/**
* Displays the dashboard
*/
-function DisplayDashboard()
+function DisplayDashboard(): void
{
// instantiate RaspAP objects
$status = new \RaspAP\Messages\StatusMessage;
$system = new \RaspAP\System\Sysinfo;
$pluginManager = \RaspAP\Plugins\PluginManager::getInstance();
+ // set AP and client interface session vars
+ getWifiInterface();
+
+ $interface = $_SESSION['ap_interface'] ?? 'wlan0';
+ $clientInterface = $_SESSION['wifi_client_interface'];
$hostname = $system->hostname();
$revision = $system->rpiRevision();
$hostapd = $system->hostapdStatus();
$adblock = $system->adBlockStatus();
+ $vpn = $system->getActiveVpnInterface();
+ $frequency = getFrequencyBand($interface);
+ $details = getInterfaceDetails($interface);
+ $wireless = getWirelessDetails($interface);
+ $connectedBSSID = getConnectedBSSID($interface);
+ $state = strtolower($details['state']);
+ $plugins = $pluginManager->getInstalledPlugins();
- getWifiInterface();
-
- exec('ip a show '.$_SESSION['ap_interface'], $stdoutIp);
- $stdoutIpAllLinesGlued = implode(" ", $stdoutIp);
- $stdoutIpWRepeatedSpaces = preg_replace('/\s\s+/', ' ', $stdoutIpAllLinesGlued);
-
- 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 = _('None');
- } 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];
-
-
- define('SSIDMAXLEN', 32);
- exec('iw dev ' .$_SESSION['ap_interface']. ' info ', $stdoutIw);
- $stdoutIwAllLinesGlued = implode('+', $stdoutIw);
- $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];
-
- $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);
- }
- }
$arrHostapdConf = parse_ini_file(RASPI_CONFIG.'/hostapd.ini');
$bridgedEnable = $arrHostapdConf['BridgedEnable'];
- $clientInterface = $_SESSION['wifi_client_interface'];
- $apInterface = $_SESSION['ap_interface'];
- $MACPattern = '"([[:xdigit:]]{2}:){5}[[:xdigit:]]{2}"';
- 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 = handlePageAction($state, $_POST, $status, $interface);
+ // refresh interface details + state
+ $details = getInterfaceDetails($interface);
+ $state = strtolower($details['state']);
}
- $ifaceStatus = $wlan0up ? "up" : "down";
- $plugins = $pluginManager->getInstalledPlugins();
+ $ipv4Address = $details['ipv4'];
+ $ipv4Netmask = $details['ipv4_netmask'];
+ $macAddress = $details['mac'];
+ $ssid = $wireless['ssid'];
$bridgedStatus = ($bridgedEnable == 1) ? "active" : "";
$hostapdStatus = ($hostapd[0] == 1) ? "active" : "";
$adblockStatus = ($adblock == true) ? "active" : "";
+ $varName = "freq" . str_replace('.', '', $frequency) . "active";
+ $$varName = "active";
+ $vpnStatus = $vpn ? "active" : "inactive";
+ if ($vpn) {
+ $vpnManaged = getVpnManged($vpn);
+ }
$firewallInstalled = array_filter($plugins, fn($p) => str_ends_with($p, 'Firewall')) ? true : false;
if (!$firewallInstalled) {
- $firewallStack = '';
+ $firewallUnavailable = '';
}
echo renderTemplate(
"dashboard", compact(
"clients",
- "moreLink",
- "apInterface",
+ "interface",
"clientInterface",
- "ifaceStatus",
+ "state",
"bridgedStatus",
"hostapdStatus",
"adblockStatus",
- "firewallStack",
+ "vpnStatus",
+ "vpnManaged",
+ "firewallUnavailable",
"status",
- "ipv4Addrs",
- "ipv4Netmasks",
- "ipv6Addrs",
- "macAddr",
- "connectedSSID",
- "connectedBSSID",
+ "ipv4Address",
+ "ipv4Netmask",
+ "ipv6Address",
+ "macAddress",
+ "ssid",
+ "bssid",
"frequency",
- "revision",
- "wlan0up"
+ "freq5active",
+ "freq24active",
+ "revision"
)
);
}
+/*
+ * Returns the management page for an associated VPN
+ *
+ * @param string $interface
+ * @return string
+ * @todo Determine if VPN provider is active
+ */
+function getVpnManged(?string $interface = null): ?string
+{
+ return match ($interface) {
+ 'wg0' => 'wg_conf',
+ 'tun0' => 'openvpn_conf',
+ 'tailscale0' => 'plugin__Tailscale',
+ default => 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
+ */
+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
+ */
+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' => getMacAddress($cleanOutput),
+ 'ipv4' => getIPv4Addresses($cleanOutput),
+ 'ipv4_netmask' => getIPv4Netmasks($cleanOutput),
+ 'ipv6' => getIPv6Addresses($cleanOutput),
+ 'state' => getInterfaceState($cleanOutput),
+ ];
+}
+
+function getMacAddress(string $output): string {
+ return preg_match('/link\/ether ([0-9a-f:]+)/i', $output, $matches) ? $matches[1] : _('No MAC Address Found');
+}
+
+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);
+}
+
+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);
+}
+
+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');
+}
+
+function getInterfaceState(string $output): string {
+ return preg_match('/state (UP|DOWN)/i', $output, $matches) ? $matches[1] : 'unknown';
+}
+
+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' => getConnectedBSSID($cleanOutput),
+ 'ssid' => getSSID($cleanOutput),
+ ];
+}
+
+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]
+ : '-';
+}
+
+function getSSID(string $output): string {
+ return preg_match('/ssid ([^\n\s]+)/i', $output, $matches)
+ ? $matches[1]
+ : '-';
+}
+
+/**
+ * Handles dashboard page actions
+ *
+ * @param string $state
+ * @param array $post
+ * @param object $status
+ * @param string $interface
+ */
+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;
+ }
+}
+