스캔 캐시를 백그라운드로 갱신
This commit is contained in:
+146
-46
@@ -2652,6 +2652,66 @@ function wifi_scan_cache_path(): string
|
||||
return sys_get_temp_dir() . '/control_wifi_scan.json';
|
||||
}
|
||||
|
||||
function scan_refresh_lock_path(string $type): string
|
||||
{
|
||||
return sys_get_temp_dir() . '/control_' . preg_replace('/[^a-z0-9_]/i', '', $type) . '_scan_refresh.lock';
|
||||
}
|
||||
|
||||
function scan_start_background_refresh(string $type): void
|
||||
{
|
||||
$lockPath = scan_refresh_lock_path($type);
|
||||
$script = dirname(__DIR__) . '/bin/control_scan_refresh.php';
|
||||
if (!is_file($script)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cmd = '/usr/bin/flock -n '
|
||||
. escapeshellarg($lockPath)
|
||||
. ' /usr/bin/php '
|
||||
. escapeshellarg($script)
|
||||
. ' '
|
||||
. escapeshellarg($type)
|
||||
. ' >/dev/null 2>&1 &';
|
||||
exec($cmd);
|
||||
}
|
||||
|
||||
function scan_cached_payload(string $cachePath, int $cacheSeconds): ?array
|
||||
{
|
||||
if (!is_file($cachePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cached = json_decode((string)@file_get_contents($cachePath), true);
|
||||
if (!is_array($cached)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cached['cached'] = true;
|
||||
$cached['stale'] = (time() - filemtime($cachePath)) >= $cacheSeconds;
|
||||
|
||||
return $cached;
|
||||
}
|
||||
|
||||
function scan_write_cache(string $cachePath, array $payload): void
|
||||
{
|
||||
$tmp = $cachePath . '.' . getmypid() . '.tmp';
|
||||
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
if ($json === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (@file_put_contents($tmp, $json) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
@chmod($tmp, 0666);
|
||||
if (!@rename($tmp, $cachePath)) {
|
||||
@unlink($tmp);
|
||||
return;
|
||||
}
|
||||
@chmod($cachePath, 0666);
|
||||
}
|
||||
|
||||
function wifi_channel_from_freq(int $freq): ?int
|
||||
{
|
||||
if ($freq >= 2412 && $freq <= 2472) {
|
||||
@@ -3031,31 +3091,9 @@ function merge_wifi_scan_networks(array $freshNetworks, array $observedNetworks)
|
||||
return $rows;
|
||||
}
|
||||
|
||||
function wifi_scan_data(): array
|
||||
function wifi_scan_refresh_payload(): array
|
||||
{
|
||||
if (!(bool)setting_value('wifi.scan_enabled')) {
|
||||
return [
|
||||
'enabled' => false,
|
||||
'cached' => false,
|
||||
'generated_at' => null,
|
||||
'interfaces' => [],
|
||||
'networks' => wifi_scan_observed_networks((int)setting_value('wifi.scan_max_networks')),
|
||||
'observed_hours' => null,
|
||||
'observed_mode' => 'all',
|
||||
'errors' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$cacheSeconds = (int)setting_value('wifi.scan_cache_seconds');
|
||||
$cachePath = wifi_scan_cache_path();
|
||||
if (is_file($cachePath) && (time() - filemtime($cachePath)) < $cacheSeconds) {
|
||||
$cached = json_decode((string)@file_get_contents($cachePath), true);
|
||||
if (is_array($cached)) {
|
||||
$cached['cached'] = true;
|
||||
return $cached;
|
||||
}
|
||||
}
|
||||
|
||||
$interfaces = wifi_interfaces_from_iw();
|
||||
$networks = [];
|
||||
$errors = [];
|
||||
@@ -3098,11 +3136,54 @@ function wifi_scan_data(): array
|
||||
'errors' => $errors,
|
||||
];
|
||||
|
||||
@file_put_contents($cachePath, json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
scan_write_cache($cachePath, $payload);
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
function wifi_scan_data(): array
|
||||
{
|
||||
$max = (int)setting_value('wifi.scan_max_networks');
|
||||
if (!(bool)setting_value('wifi.scan_enabled')) {
|
||||
return [
|
||||
'enabled' => false,
|
||||
'cached' => false,
|
||||
'stale' => false,
|
||||
'generated_at' => null,
|
||||
'interfaces' => [],
|
||||
'networks' => wifi_scan_observed_networks($max),
|
||||
'observed_hours' => null,
|
||||
'observed_mode' => 'all',
|
||||
'errors' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$cacheSeconds = (int)setting_value('wifi.scan_cache_seconds');
|
||||
$cachePath = wifi_scan_cache_path();
|
||||
$cached = scan_cached_payload($cachePath, $cacheSeconds);
|
||||
if ($cached !== null && empty($cached['stale'])) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
scan_start_background_refresh('wifi');
|
||||
$observedNetworks = wifi_scan_observed_networks($max);
|
||||
|
||||
return [
|
||||
'enabled' => true,
|
||||
'cached' => $cached !== null,
|
||||
'stale' => true,
|
||||
'refresh_pending' => true,
|
||||
'generated_at' => is_array($cached) ? (string)($cached['generated_at'] ?? '') : date('Y-m-d H:i:s'),
|
||||
'interfaces' => is_array($cached) ? (array)($cached['interfaces'] ?? []) : [],
|
||||
'networks' => $observedNetworks,
|
||||
'live_found' => is_array($cached) ? (int)($cached['live_found'] ?? 0) : 0,
|
||||
'total_found' => count($observedNetworks),
|
||||
'observed_hours' => null,
|
||||
'observed_mode' => 'all',
|
||||
'errors' => is_array($cached) ? (array)($cached['errors'] ?? []) : [],
|
||||
];
|
||||
}
|
||||
|
||||
function bluetooth_scan_cache_path(): string
|
||||
{
|
||||
return sys_get_temp_dir() . '/control_bluetooth_scan.json';
|
||||
@@ -3444,30 +3525,10 @@ function merge_bluetooth_scan_devices(array $freshDevices, array $observedDevice
|
||||
return $rows;
|
||||
}
|
||||
|
||||
function bluetooth_scan_data(): array
|
||||
function bluetooth_scan_refresh_payload(): array
|
||||
{
|
||||
$max = (int)setting_value('bluetooth.scan_max_devices');
|
||||
if (!(bool)setting_value('bluetooth.scan_enabled')) {
|
||||
return [
|
||||
'enabled' => false,
|
||||
'cached' => false,
|
||||
'generated_at' => null,
|
||||
'devices' => bluetooth_scan_observed_devices($max),
|
||||
'observed_mode' => 'all',
|
||||
'errors' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$cacheSeconds = (int)setting_value('bluetooth.scan_cache_seconds');
|
||||
$cachePath = bluetooth_scan_cache_path();
|
||||
if (is_file($cachePath) && (time() - filemtime($cachePath)) < $cacheSeconds) {
|
||||
$cached = json_decode((string)@file_get_contents($cachePath), true);
|
||||
if (is_array($cached)) {
|
||||
$cached['cached'] = true;
|
||||
return $cached;
|
||||
}
|
||||
}
|
||||
|
||||
$timeout = (int)setting_value('bluetooth.scan_timeout_seconds');
|
||||
$includeRaw = (bool)setting_value('bluetooth.scan_include_raw');
|
||||
$ctl = bluetoothctl_path();
|
||||
@@ -3512,11 +3573,50 @@ function bluetooth_scan_data(): array
|
||||
'errors' => $errors,
|
||||
];
|
||||
|
||||
@file_put_contents($cachePath, json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
scan_write_cache($cachePath, $payload);
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
function bluetooth_scan_data(): array
|
||||
{
|
||||
$max = (int)setting_value('bluetooth.scan_max_devices');
|
||||
if (!(bool)setting_value('bluetooth.scan_enabled')) {
|
||||
return [
|
||||
'enabled' => false,
|
||||
'cached' => false,
|
||||
'stale' => false,
|
||||
'generated_at' => null,
|
||||
'devices' => bluetooth_scan_observed_devices($max),
|
||||
'observed_mode' => 'all',
|
||||
'errors' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$cacheSeconds = (int)setting_value('bluetooth.scan_cache_seconds');
|
||||
$cachePath = bluetooth_scan_cache_path();
|
||||
$cached = scan_cached_payload($cachePath, $cacheSeconds);
|
||||
if ($cached !== null && empty($cached['stale'])) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
scan_start_background_refresh('bluetooth');
|
||||
$observedDevices = bluetooth_scan_observed_devices($max);
|
||||
|
||||
return [
|
||||
'enabled' => true,
|
||||
'cached' => $cached !== null,
|
||||
'stale' => true,
|
||||
'refresh_pending' => true,
|
||||
'generated_at' => is_array($cached) ? (string)($cached['generated_at'] ?? '') : date('Y-m-d H:i:s'),
|
||||
'devices' => $observedDevices,
|
||||
'live_found' => is_array($cached) ? (int)($cached['live_found'] ?? 0) : 0,
|
||||
'total_found' => count($observedDevices),
|
||||
'observed_mode' => 'all',
|
||||
'errors' => is_array($cached) ? (array)($cached['errors'] ?? []) : [],
|
||||
];
|
||||
}
|
||||
|
||||
function parse_live_wifi_rows(string $iface, string $band, string $text, array $leases, array $aliases): array
|
||||
{
|
||||
$rows = [];
|
||||
|
||||
+2
-2
@@ -134,7 +134,7 @@ html[data-theme="light"] .btn.secondary{background:#d9e2ef;color:#172033}html[da
|
||||
html[data-theme="light"] .details-panel{background:#edf3fa;border-color:#b8c7da}html[data-theme="light"] .service-item{background:#e8f0f8;border-color:#b7c8dd}html[data-theme="light"] .service-name{color:#0f1f35}html[data-theme="light"] .service-desc,html[data-theme="light"] .service-meta{color:#42526a}html[data-theme="light"] .service-state{background:#f8fbff;border-color:#aebed2;color:#304057}html[data-theme="light"] .service-state.active{background:#d8f3e3;border-color:#7fc99c;color:#146c39}html[data-theme="light"] .service-state.failed{background:#ffe2e2;border-color:#e38b8b;color:#9f1d1d}html[data-theme="light"] .service-log,html[data-theme="light"] .dmesg-log{background:#f7fafc;border-color:#b9c8da;color:#0f2742}
|
||||
.resource-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.resource-box{min-width:0}.resource-box h3{margin:0 0 9px;color:var(--sub);font-size:14px;font-weight:500}.resource-table{width:100%;min-width:620px;table-layout:fixed;border-collapse:collapse;border:1px solid var(--line);border-radius:14px;overflow:hidden}.resource-table th,.resource-table td{padding:9px 10px;border-bottom:1px solid rgba(128,145,170,.18);background:var(--input);text-align:left;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.resource-table th{background:var(--table-head);color:var(--sub);font-weight:500}.resource-table .pid{width:72px}.resource-table .metric{width:82px}.resource-table .service{width:150px}.resource-table .cmd{width:auto}
|
||||
.notice{position:fixed;right:20px;bottom:20px;min-width:220px;max-width:420px;padding:14px 16px;border-radius:14px;font-weight:500;background:var(--table-head);color:var(--text);border:1px solid var(--line);box-shadow:var(--shadow);z-index:99}.notice:empty{display:none}.notice[data-type=success]{border-color:rgba(53,196,107,.5)}.notice[data-type=error]{border-color:rgba(255,95,87,.5)}
|
||||
.status-value.interactive{cursor:help;text-decoration:underline;text-decoration-style:dotted;text-decoration-thickness:1px;text-underline-offset:4px}.runtime-tooltip{position:fixed;left:0;top:0;z-index:110;display:none;width:min(520px,calc(100vw - 28px));max-height:calc(100vh - 28px);overflow:hidden;padding:14px 16px;border:1px solid rgba(84,101,128,.78);border-radius:16px;background:var(--card);color:var(--text);box-shadow:0 18px 48px rgba(0,0,0,.38);font-size:13px;line-height:1.55;white-space:pre-line;word-break:keep-all;overflow-wrap:anywhere;pointer-events:none}.runtime-tooltip[data-open="1"]{display:block}.runtime-tooltip::before{content:"";position:absolute;left:18px;top:-7px;width:12px;height:12px;border-left:1px solid rgba(84,101,128,.78);border-top:1px solid rgba(84,101,128,.78);background:var(--card);transform:rotate(45deg)}html[data-theme="light"] .runtime-tooltip{background:#f8fbff;border-color:#aebed2;color:#172033;box-shadow:0 18px 44px rgba(28,43,64,.18)}html[data-theme="light"] .runtime-tooltip::before{background:#f8fbff;border-color:#aebed2}
|
||||
.status-value.interactive{cursor:help;text-decoration:underline;text-decoration-style:dotted;text-decoration-thickness:1px;text-underline-offset:4px}.runtime-tooltip{position:fixed;left:0;top:0;z-index:110;display:none;width:min(520px,calc(100vw - 28px));max-height:calc(100vh - 28px);overflow:hidden;padding:14px 16px;border:1px solid rgba(84,101,128,.78);border-radius:16px;background:var(--card);color:var(--text);box-shadow:0 18px 48px rgba(0,0,0,.38);font-size:13px;line-height:1.55;white-space:pre-line;word-break:keep-all;overflow-wrap:anywhere;pointer-events:none}.runtime-tooltip.signal-tooltip{width:auto;min-width:118px;max-width:220px;padding:9px 12px;border-radius:12px;font-size:12px;line-height:1.45;box-shadow:0 12px 30px rgba(0,0,0,.28)}.runtime-tooltip[data-open="1"]{display:block}.runtime-tooltip::before{content:"";position:absolute;left:18px;top:-7px;width:12px;height:12px;border-left:1px solid rgba(84,101,128,.78);border-top:1px solid rgba(84,101,128,.78);background:var(--card);transform:rotate(45deg)}html[data-theme="light"] .runtime-tooltip{background:#f8fbff;border-color:#aebed2;color:#172033;box-shadow:0 18px 44px rgba(28,43,64,.18)}html[data-theme="light"] .runtime-tooltip.signal-tooltip{box-shadow:0 12px 28px rgba(28,43,64,.16)}html[data-theme="light"] .runtime-tooltip::before{background:#f8fbff;border-color:#aebed2}
|
||||
.dialog-layer{position:fixed;inset:0;display:none;align-items:center;justify-content:center;padding:22px;background:rgba(4,7,12,.68);backdrop-filter:blur(8px);z-index:120}.dialog-layer[data-open="1"]{display:flex}.dialog-box{width:min(430px,100%);border:1px solid rgba(84,101,128,.78);border-radius:20px;background:var(--card);box-shadow:0 24px 70px rgba(0,0,0,.42);padding:20px}.dialog-box h3{margin:0 0 8px;font-size:19px;font-weight:500}.dialog-message{margin:0 0 16px;color:var(--sub);line-height:1.5;white-space:pre-wrap}.dialog-input{width:100%;height:48px;margin-bottom:14px;border-radius:14px;border:1px solid var(--line);background:var(--input);color:var(--text);padding:0 14px;outline:none}.dialog-input:focus{outline:2px solid rgba(59,130,246,.65);outline-offset:2px}.dialog-actions{display:flex;justify-content:flex-end;gap:10px}.dialog-actions .btn{min-width:84px}.dialog-actions .btn.red{background:#c62828}
|
||||
.settings-layer{align-items:stretch}.settings-box{width:min(980px,100%);max-height:calc(100vh - 44px);display:grid;grid-template-rows:auto minmax(0,1fr) auto;padding:0;overflow:hidden}.settings-head{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;padding:18px 20px;border-bottom:1px solid var(--line)}.settings-sub{margin:0;color:var(--sub);font-size:13px}.settings-body{overflow:auto;padding:16px 20px;display:grid;gap:14px}.settings-group{border:1px solid var(--line);border-radius:16px;background:var(--card2);padding:14px}.settings-group h4{margin:0 0 12px;font-size:15px}.settings-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.settings-field{display:grid;gap:7px;min-width:0}.settings-label{display:flex;align-items:center;justify-content:space-between;gap:8px;color:var(--sub);font-size:13px}.settings-label span:last-child{white-space:nowrap;color:var(--sub)}.settings-control{display:flex;align-items:center;gap:9px}.settings-control input,.settings-select{width:100%;height:42px;border-radius:12px;border:1px solid var(--line);background:var(--input);color:var(--text);padding:0 12px;outline:none}.settings-control input:focus,.settings-select:focus{outline:2px solid rgba(59,130,246,.65);outline-offset:2px}.settings-toggle{display:grid;grid-template-columns:1fr 1fr;align-items:center;width:100%;height:42px;border:1px solid var(--line);border-radius:12px;background:var(--input);padding:4px;cursor:pointer;gap:4px}.settings-toggle input{position:absolute;opacity:0;pointer-events:none}.settings-toggle span{display:grid;place-items:center;min-width:0;height:32px;border-radius:10px;color:var(--sub);font-weight:700;font-size:12px;transition:background .12s ease,color .12s ease}.settings-toggle[data-checked="1"] .settings-toggle-on,.settings-toggle[data-checked="0"] .settings-toggle-off{background:var(--blue);color:#fff}.settings-toggle[data-checked="0"] .settings-toggle-off{background:#64748b}.settings-default{font-size:12px;color:var(--sub);white-space:nowrap}.settings-actions{display:flex;justify-content:flex-end;gap:10px;padding:14px 20px;border-top:1px solid var(--line)}
|
||||
@media(max-width:1320px){.chart-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||
@@ -383,7 +383,7 @@ html[data-theme="light"] .details-panel{background:#edf3fa;border-color:#b8c7da}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/assets/app.js?v=20260627_scan_scroll_signal1"></script>
|
||||
<script src="/assets/app.js?v=20260627_async_scan_tooltip1"></script>
|
||||
<script src="/assets/wakelock.js?v=20260607_prefs1"></script>
|
||||
<?php endif; ?>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user