터치 디스플레이 밝기 제어 추가
This commit is contained in:
@@ -13,7 +13,7 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를
|
|||||||
- 팬 모드 `auto`, `manual`, `off` 제어
|
- 팬 모드 `auto`, `manual`, `off` 제어
|
||||||
- PWM slider 기반 수동 팬 제어
|
- PWM slider 기반 수동 팬 제어
|
||||||
- 오버레이 설정 모달에서 보안 정책, 팬 자동곡선, 이벤트 판정, 팬 이상감지 파라미터 조정
|
- 오버레이 설정 모달에서 보안 정책, 팬 자동곡선, 이벤트 판정, 팬 이상감지 파라미터 조정
|
||||||
- 설정 모달에서 라즈베리파이 정품 7인치 DSI 터치 디스플레이 상태 확인, 콘솔 회전, 터치 좌표 보정, LCD/터치 비활성화 부팅 설정 조정
|
- 설정 모달에서 라즈베리파이 정품 7인치 DSI 터치 디스플레이 상태 확인, 밝기/백라이트 즉시 적용, 콘솔 회전, 터치 좌표 보정, LCD/터치 비활성화 부팅 설정 조정
|
||||||
- CPU 온도, 팬 RPM 핵심 차트
|
- CPU 온도, 팬 RPM 핵심 차트
|
||||||
- RP1 온도, 팬 효율, CPU 전력 상세 차트는 접힘 영역에서 확인
|
- RP1 온도, 팬 효율, CPU 전력 상세 차트는 접힘 영역에서 확인
|
||||||
- UPS/배터리 잔여 시간 기능은 장치 제거 후 비활성 상태로 두고, 배터리 센서 조회와 장기 학습 계산은 돌리지 않음
|
- UPS/배터리 잔여 시간 기능은 장치 제거 후 비활성 상태로 두고, 배터리 센서 조회와 장기 학습 계산은 돌리지 않음
|
||||||
@@ -48,6 +48,7 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를
|
|||||||
- `public/api.php?action=settings_save`: 설정 모달 값 저장
|
- `public/api.php?action=settings_save`: 설정 모달 값 저장
|
||||||
- `public/api.php?action=settings_reset`: 설정 모달 값을 기본값으로 초기화
|
- `public/api.php?action=settings_reset`: 설정 모달 값을 기본값으로 초기화
|
||||||
- `public/api.php?action=touch_display_save`: `/boot/firmware/config.txt`, `/boot/firmware/cmdline.txt`에 DSI 터치 디스플레이 부팅 설정 저장
|
- `public/api.php?action=touch_display_save`: `/boot/firmware/config.txt`, `/boot/firmware/cmdline.txt`에 DSI 터치 디스플레이 부팅 설정 저장
|
||||||
|
- `public/api.php?action=touch_display_runtime_save`: DSI 터치 디스플레이 백라이트 밝기와 백라이트 전원 즉시 적용
|
||||||
- `public/api.php?action=dmesg`: dmesg 로그 조회
|
- `public/api.php?action=dmesg`: dmesg 로그 조회
|
||||||
- `public/api.php?action=reboot`: 확인 단어와 관리자 암호 재검증 후 sudo reboot 실행
|
- `public/api.php?action=reboot`: 확인 단어와 관리자 암호 재검증 후 sudo reboot 실행
|
||||||
|
|
||||||
@@ -98,12 +99,12 @@ API 응답의 `battery.enabled`는 false, `battery.removed`는 true로 내려가
|
|||||||
- 팬 자동 제어: 팬 시작 온도, 최대 온도, 즉시 최대 PWM 온도, RP1 온도 반영 비율, 팬 판단 온도 여유, 자동 상승/하강 PWM step
|
- 팬 자동 제어: 팬 시작 온도, 최대 온도, 즉시 최대 PWM 온도, RP1 온도 반영 비율, 팬 판단 온도 여유, 자동 상승/하강 PWM step
|
||||||
- 보안 정책: 자동 로그인 유지 기간, remember 쿠키 Secure/SameSite, CSRF 길이, remember 토큰 길이, User-Agent/IP 접두사 검증, 로그인 실패 잠금, 재부팅 허용 여부, 재부팅 확인 문구, 명령 timeout, WiFi 조작 허용 여부
|
- 보안 정책: 자동 로그인 유지 기간, remember 쿠키 Secure/SameSite, CSRF 길이, remember 토큰 길이, User-Agent/IP 접두사 검증, 로그인 실패 잠금, 재부팅 허용 여부, 재부팅 확인 문구, 명령 timeout, WiFi 조작 허용 여부
|
||||||
- 화면/진단 표시: 프로세스 후보 수, 사용자 서비스 로그 줄 수, 사용자 서비스 캐시 시간, 팬 이상 이력 수
|
- 화면/진단 표시: 프로세스 후보 수, 사용자 서비스 로그 줄 수, 사용자 서비스 캐시 시간, 팬 이상 이력 수
|
||||||
- 터치 디스플레이: 현재 DSI 연결 상태, `800x480` 모드, framebuffer, 자동감지/수동 overlay 상태 표시와 콘솔 회전, LCD 출력, 터치 입력, `invx`/`invy`/`swapxy`, 터치 좌표 크기 저장
|
- 터치 디스플레이: 현재 DSI 연결 상태, `800x480` 모드, framebuffer, 백라이트 밝기, 자동감지/수동 overlay 상태 표시와 밝기 즉시 적용, 콘솔 회전, LCD 출력, 터치 입력, `invx`/`invy`/`swapxy`, 터치 좌표 크기 저장
|
||||||
- 저전압/스로틀링: 최근 판정창, 복구 중 유지 시간, 반복 발생 기준 횟수
|
- 저전압/스로틀링: 최근 판정창, 복구 중 유지 시간, 반복 발생 기준 횟수
|
||||||
- 팬 이상감지: 기준 표본 수, 최신 표본 제외 수, RPM/온도 이상 감지 차이, RPM/온도 복구 차이, RPM 감시 시작 PWM, alert 유지 중 반복 기록, 반복 기록 간격, 하강 변화 원인 후보 표시 여부
|
- 팬 이상감지: 기준 표본 수, 최신 표본 제외 수, RPM/온도 이상 감지 차이, RPM/온도 복구 차이, RPM 감시 시작 PWM, alert 유지 중 반복 기록, 반복 기록 간격, 하강 변화 원인 후보 표시 여부
|
||||||
- UPS/배터리 설정: 현재 UPS 제거 상태에서는 설정 모달에서 숨겨지고, `battery.enabled=true`일 때만 배터리 예측 항목을 노출합니다.
|
- UPS/배터리 설정: 현재 UPS 제거 상태에서는 설정 모달에서 숨겨지고, `battery.enabled=true`일 때만 배터리 예측 항목을 노출합니다.
|
||||||
|
|
||||||
디스플레이 설정은 DB가 아니라 라즈베리파이 부팅 파일에 저장합니다. 저장 전 기존 파일을 `.control-YYYYMMDD-HHMMSS.bak` 형식으로 백업하고, 변경 사항은 재부팅 후 적용됩니다. 구형 정품 7인치 Touch Display의 콘솔 회전은 `cmdline.txt`의 `video=DSI-1:800x480@60,rotate=...`를 사용하며, 터치 좌표 보정이 필요하면 `config.txt`의 `vc4-kms-dsi-7inch` overlay로 전환합니다.
|
디스플레이 회전/터치 좌표 설정은 DB가 아니라 라즈베리파이 부팅 파일에 저장합니다. 저장 전 기존 파일을 `.control-YYYYMMDD-HHMMSS.bak` 형식으로 백업하고, 변경 사항은 재부팅 후 적용됩니다. 구형 정품 7인치 Touch Display의 콘솔 회전은 `cmdline.txt`의 `video=DSI-1:800x480@60,rotate=...`를 사용하며, 터치 좌표 보정이 필요하면 `config.txt`의 `vc4-kms-dsi-7inch` overlay로 전환합니다. 백라이트 밝기와 백라이트 전원은 `/sys/class/backlight` 장치를 통해 즉시 적용되며 재부팅이 필요 없습니다.
|
||||||
|
|
||||||
설정 모달이 열려 있는 동안 1초 상태 갱신은 폼 DOM을 다시 만들지 않습니다. 입력 중인 값은 저장, 기본값 초기화, 닫기 전까지 유지되며, 백그라운드 snapshot은 내부 설정 payload만 갱신합니다. 단순 boolean 설정은 `켜기`/`끄기` 세그먼트로 표시합니다.
|
설정 모달이 열려 있는 동안 1초 상태 갱신은 폼 DOM을 다시 만들지 않습니다. 입력 중인 값은 저장, 기본값 초기화, 닫기 전까지 유지되며, 백그라운드 snapshot은 내부 설정 payload만 갱신합니다. 단순 boolean 설정은 `켜기`/`끄기` 세그먼트로 표시합니다.
|
||||||
|
|
||||||
|
|||||||
+102
@@ -3488,6 +3488,29 @@ function touch_display_file(string $kind): string
|
|||||||
return $kind === 'cmdline' ? '/boot/firmware/cmdline.txt' : '/boot/firmware/config.txt';
|
return $kind === 'cmdline' ? '/boot/firmware/cmdline.txt' : '/boot/firmware/config.txt';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function touch_display_backlight_path(): ?string
|
||||||
|
{
|
||||||
|
$candidates = glob('/sys/class/backlight/*') ?: [];
|
||||||
|
foreach ($candidates as $path) {
|
||||||
|
if (is_readable($path . '/brightness') && is_readable($path . '/max_brightness')) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fallback = '/sys/devices/platform/axi/1000120000.pcie/1f00088000.i2c/i2c-10/10-0045/backlight/10-0045';
|
||||||
|
return is_readable($fallback . '/brightness') && is_readable($fallback . '/max_brightness') ? $fallback : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function touch_display_read_int(?string $path): ?int
|
||||||
|
{
|
||||||
|
if ($path === null || !is_readable($path)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$raw = trim((string)@file_get_contents($path));
|
||||||
|
return is_numeric($raw) ? (int)$raw : null;
|
||||||
|
}
|
||||||
|
|
||||||
function touch_display_read_file(string $kind): string
|
function touch_display_read_file(string $kind): string
|
||||||
{
|
{
|
||||||
$path = touch_display_file($kind);
|
$path = touch_display_file($kind);
|
||||||
@@ -3532,6 +3555,21 @@ function touch_display_write_file(string $path, string $content): void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function touch_display_write_value(string $path, string $value): void
|
||||||
|
{
|
||||||
|
if (function_exists('posix_geteuid') && posix_geteuid() === 0) {
|
||||||
|
if (@file_put_contents($path, $value) === false) {
|
||||||
|
throw new RuntimeException(basename($path) . ' 저장에 실패했습니다.');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = sh(['/bin/sh', '-c', 'printf %s ' . escapeshellarg($value) . ' > ' . escapeshellarg($path)], true, 5);
|
||||||
|
if ($result['code'] !== 0) {
|
||||||
|
throw new RuntimeException(trim($result['out']) !== '' ? trim($result['out']) : basename($path) . ' 저장에 실패했습니다.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function touch_display_active_config_lines(string $config): array
|
function touch_display_active_config_lines(string $config): array
|
||||||
{
|
{
|
||||||
return array_values(array_filter(
|
return array_values(array_filter(
|
||||||
@@ -3606,6 +3644,11 @@ function touch_display_status(): array
|
|||||||
$modes = is_readable($modesPath)
|
$modes = is_readable($modesPath)
|
||||||
? array_values(array_filter(array_map('trim', file($modesPath, FILE_IGNORE_NEW_LINES) ?: [])))
|
? array_values(array_filter(array_map('trim', file($modesPath, FILE_IGNORE_NEW_LINES) ?: [])))
|
||||||
: [];
|
: [];
|
||||||
|
$backlightPath = touch_display_backlight_path();
|
||||||
|
$brightness = touch_display_read_int($backlightPath !== null ? $backlightPath . '/brightness' : null);
|
||||||
|
$actualBrightness = touch_display_read_int($backlightPath !== null ? $backlightPath . '/actual_brightness' : null);
|
||||||
|
$maxBrightness = touch_display_read_int($backlightPath !== null ? $backlightPath . '/max_brightness' : null);
|
||||||
|
$blPower = touch_display_read_int($backlightPath !== null ? $backlightPath . '/bl_power' : null);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'connector' => 'DSI-1',
|
'connector' => 'DSI-1',
|
||||||
@@ -3618,6 +3661,16 @@ function touch_display_status(): array
|
|||||||
'current_mode' => $modes[0] ?? '800x480',
|
'current_mode' => $modes[0] ?? '800x480',
|
||||||
'framebuffer' => is_dir('/sys/class/graphics/fb0') ? 'fb0' : '-',
|
'framebuffer' => is_dir('/sys/class/graphics/fb0') ? 'fb0' : '-',
|
||||||
'touch_input' => trim((string)shell_exec("for e in /sys/class/input/event*/device/name; do cat \"\$e\" 2>/dev/null; done | grep -i 'ft5x06' | head -n 1")) ?: '-',
|
'touch_input' => trim((string)shell_exec("for e in /sys/class/input/event*/device/name; do cat \"\$e\" 2>/dev/null; done | grep -i 'ft5x06' | head -n 1")) ?: '-',
|
||||||
|
'backlight' => [
|
||||||
|
'available' => $backlightPath !== null,
|
||||||
|
'path' => $backlightPath,
|
||||||
|
'brightness' => $brightness,
|
||||||
|
'actual_brightness' => $actualBrightness,
|
||||||
|
'max_brightness' => $maxBrightness,
|
||||||
|
'percent' => $maxBrightness !== null && $maxBrightness > 0 && $brightness !== null ? round($brightness / $maxBrightness * 100, 1) : null,
|
||||||
|
'power' => $blPower,
|
||||||
|
'enabled' => $blPower === null || $blPower === 0,
|
||||||
|
],
|
||||||
'config' => [
|
'config' => [
|
||||||
'display_auto_detect' => touch_display_config_flag($lines, 'display_auto_detect'),
|
'display_auto_detect' => touch_display_config_flag($lines, 'display_auto_detect'),
|
||||||
'manual_overlay' => (bool)$overlay['enabled'],
|
'manual_overlay' => (bool)$overlay['enabled'],
|
||||||
@@ -3637,6 +3690,36 @@ function touch_display_status(): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function touch_display_set_runtime(array $input): array
|
||||||
|
{
|
||||||
|
$backlightPath = touch_display_backlight_path();
|
||||||
|
if ($backlightPath === null) {
|
||||||
|
throw new RuntimeException('백라이트 제어 장치를 찾을 수 없습니다.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$maxBrightness = touch_display_read_int($backlightPath . '/max_brightness') ?? 255;
|
||||||
|
$brightness = max(0, min($maxBrightness, (int)($input['brightness'] ?? $maxBrightness)));
|
||||||
|
$enabled = touch_display_bool($input['backlight_enabled'] ?? '1');
|
||||||
|
|
||||||
|
touch_display_write_value($backlightPath . '/brightness', (string)$brightness);
|
||||||
|
if (is_writable($backlightPath . '/bl_power') || is_readable($backlightPath . '/bl_power')) {
|
||||||
|
touch_display_write_value($backlightPath . '/bl_power', $enabled ? '0' : '4');
|
||||||
|
}
|
||||||
|
|
||||||
|
add_fan_action(
|
||||||
|
'touch_display_runtime',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'brightness=' . $brightness . '/' . $maxBrightness . ', backlight_enabled=' . ($enabled ? '1' : '0'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'reboot_required' => false,
|
||||||
|
'display' => touch_display_status(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
function touch_display_comment_directives(string $config, array $patterns): string
|
function touch_display_comment_directives(string $config, array $patterns): string
|
||||||
{
|
{
|
||||||
$lines = preg_split('/\R/', $config) ?: [];
|
$lines = preg_split('/\R/', $config) ?: [];
|
||||||
@@ -4043,6 +4126,25 @@ function control_api_dispatch(): void
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($action === 'touch_display_runtime_save') {
|
||||||
|
$raw = (string)($_POST['runtime'] ?? '{}');
|
||||||
|
$runtime = json_decode($raw, true);
|
||||||
|
if (!is_array($runtime)) {
|
||||||
|
json_out([
|
||||||
|
'ok' => false,
|
||||||
|
'error' => 'bad_display_runtime',
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = touch_display_set_runtime($runtime);
|
||||||
|
$data = collect_snapshot(false);
|
||||||
|
$data['touch_display_runtime_save'] = $result;
|
||||||
|
json_out([
|
||||||
|
'ok' => true,
|
||||||
|
'data' => $data,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if ($action === 'fan') {
|
if ($action === 'fan') {
|
||||||
$mode = (string)($_POST['mode'] ?? 'auto');
|
$mode = (string)($_POST['mode'] ?? 'auto');
|
||||||
$pwm = max(0, min(255, (int)($_POST['pwm'] ?? 120)));
|
$pwm = max(0, min(255, (int)($_POST['pwm'] ?? 120)));
|
||||||
|
|||||||
@@ -205,6 +205,11 @@
|
|||||||
touchDisplayConnected: 'Connected',
|
touchDisplayConnected: 'Connected',
|
||||||
touchDisplayMode: 'Mode',
|
touchDisplayMode: 'Mode',
|
||||||
touchDisplayFramebuffer: 'Framebuffer',
|
touchDisplayFramebuffer: 'Framebuffer',
|
||||||
|
touchBacklight: 'Backlight',
|
||||||
|
touchBrightness: 'Brightness',
|
||||||
|
touchBacklightEnabled: 'Backlight power',
|
||||||
|
touchRuntimeSave: 'Apply brightness now',
|
||||||
|
touchRuntimeSaved: 'Brightness applied.',
|
||||||
touchDisplayOverlay: 'Manual overlay',
|
touchDisplayOverlay: 'Manual overlay',
|
||||||
touchDisplayAutoDetect: 'Auto detect',
|
touchDisplayAutoDetect: 'Auto detect',
|
||||||
touchDisplayEnabled: 'LCD output',
|
touchDisplayEnabled: 'LCD output',
|
||||||
@@ -376,6 +381,11 @@
|
|||||||
touchDisplayConnected: '연결',
|
touchDisplayConnected: '연결',
|
||||||
touchDisplayMode: '모드',
|
touchDisplayMode: '모드',
|
||||||
touchDisplayFramebuffer: '프레임버퍼',
|
touchDisplayFramebuffer: '프레임버퍼',
|
||||||
|
touchBacklight: '백라이트',
|
||||||
|
touchBrightness: '밝기',
|
||||||
|
touchBacklightEnabled: '백라이트 전원',
|
||||||
|
touchRuntimeSave: '밝기 즉시 적용',
|
||||||
|
touchRuntimeSaved: '밝기를 적용했습니다.',
|
||||||
touchDisplayOverlay: '수동 overlay',
|
touchDisplayOverlay: '수동 overlay',
|
||||||
touchDisplayAutoDetect: '자동 감지',
|
touchDisplayAutoDetect: '자동 감지',
|
||||||
touchDisplayEnabled: 'LCD 출력',
|
touchDisplayEnabled: 'LCD 출력',
|
||||||
@@ -699,6 +709,7 @@
|
|||||||
[t('touchDisplayConnected'), `${display.status || 'unknown'} / ${display.enabled_state || 'unknown'} / ${display.dpms || 'unknown'}`],
|
[t('touchDisplayConnected'), `${display.status || 'unknown'} / ${display.enabled_state || 'unknown'} / ${display.dpms || 'unknown'}`],
|
||||||
[t('touchDisplayMode'), display.current_mode || '-'],
|
[t('touchDisplayMode'), display.current_mode || '-'],
|
||||||
[t('touchDisplayFramebuffer'), display.framebuffer || '-'],
|
[t('touchDisplayFramebuffer'), display.framebuffer || '-'],
|
||||||
|
[t('touchBacklight'), display.backlight?.available ? `${display.backlight.brightness ?? '-'} / ${display.backlight.max_brightness ?? '-'} (${display.backlight.percent ?? '-'}%)` : '-'],
|
||||||
[t('touchDisplayAutoDetect'), boolLabel(!!config.display_auto_detect)],
|
[t('touchDisplayAutoDetect'), boolLabel(!!config.display_auto_detect)],
|
||||||
[t('touchDisplayOverlay'), boolLabel(!!config.manual_overlay)],
|
[t('touchDisplayOverlay'), boolLabel(!!config.manual_overlay)],
|
||||||
];
|
];
|
||||||
@@ -725,6 +736,9 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
const backlight = display.backlight || {};
|
||||||
|
const maxBrightness = Number(backlight.max_brightness || 255);
|
||||||
|
const brightness = Number(backlight.brightness ?? maxBrightness);
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<section class="settings-group touch-display-settings">
|
<section class="settings-group touch-display-settings">
|
||||||
@@ -758,6 +772,24 @@
|
|||||||
<button id="touchDisplaySave" class="btn" type="button">${escapeHtml(t('touchDisplaySave'))}</button>
|
<button id="touchDisplaySave" class="btn" type="button">${escapeHtml(t('touchDisplaySave'))}</button>
|
||||||
<span class="settings-note">/boot/firmware/config.txt, /boot/firmware/cmdline.txt</span>
|
<span class="settings-note">/boot/firmware/config.txt, /boot/firmware/cmdline.txt</span>
|
||||||
</div>
|
</div>
|
||||||
|
${backlight.available ? `
|
||||||
|
<div class="touch-runtime-box">
|
||||||
|
<h4>${escapeHtml(t('touchBacklight'))}</h4>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="settings-field">
|
||||||
|
<span class="settings-label"><span>${escapeHtml(t('touchBrightness'))}</span><span id="touchBrightnessValue">${escapeHtml(brightness)} / ${escapeHtml(maxBrightness)}</span></span>
|
||||||
|
<span class="settings-control">
|
||||||
|
<input data-display-runtime-key="brightness" type="range" min="0" max="${escapeHtml(maxBrightness)}" step="1" autocomplete="off" value="${escapeHtml(brightness)}" class="slider">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
${checkboxField('backlight_enabled', t('touchBacklightEnabled'), backlight.enabled !== false).replaceAll('data-display-key', 'data-display-runtime-key')}
|
||||||
|
</div>
|
||||||
|
<div class="settings-inline-actions">
|
||||||
|
<button id="touchRuntimeSave" class="btn secondary" type="button">${escapeHtml(t('touchRuntimeSave'))}</button>
|
||||||
|
<span class="settings-note">${escapeHtml(backlight.path || '')}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
</section>
|
</section>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -875,6 +907,14 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
$('#touchDisplaySave', els.settingsBody)?.addEventListener('click', saveTouchDisplaySettings);
|
$('#touchDisplaySave', els.settingsBody)?.addEventListener('click', saveTouchDisplaySettings);
|
||||||
|
$('#touchRuntimeSave', els.settingsBody)?.addEventListener('click', saveTouchDisplayRuntime);
|
||||||
|
const brightnessSlider = els.settingsBody.querySelector('[data-display-runtime-key="brightness"]');
|
||||||
|
const brightnessValue = $('#touchBrightnessValue', els.settingsBody);
|
||||||
|
brightnessSlider?.addEventListener('input', () => {
|
||||||
|
if (brightnessValue) {
|
||||||
|
brightnessValue.textContent = `${brightnessSlider.value} / ${brightnessSlider.max}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function settingByKey(key, fallback = null) {
|
function settingByKey(key, fallback = null) {
|
||||||
@@ -925,6 +965,16 @@
|
|||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function currentTouchDisplayRuntimeValues() {
|
||||||
|
const values = {};
|
||||||
|
els.settingsBody?.querySelectorAll('[data-display-runtime-key]').forEach(input => {
|
||||||
|
values[input.dataset.displayRuntimeKey] = input.type === 'checkbox'
|
||||||
|
? (input.checked ? '1' : '0')
|
||||||
|
: input.value;
|
||||||
|
});
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
async function saveTouchDisplaySettings() {
|
async function saveTouchDisplaySettings() {
|
||||||
if (!await window.customConfirm('디스플레이 부팅 설정을 저장할까요? 저장 후 재부팅해야 적용됩니다.', { title: t('touchDisplaySettings') })) {
|
if (!await window.customConfirm('디스플레이 부팅 설정을 저장할까요? 저장 후 재부팅해야 적용됩니다.', { title: t('touchDisplaySettings') })) {
|
||||||
return;
|
return;
|
||||||
@@ -944,6 +994,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function saveTouchDisplayRuntime() {
|
||||||
|
try {
|
||||||
|
const data = await api('touch_display_runtime_save', {
|
||||||
|
runtime: JSON.stringify(currentTouchDisplayRuntimeValues()),
|
||||||
|
});
|
||||||
|
state.settingsDirty = false;
|
||||||
|
render(data);
|
||||||
|
renderSettings(data.settings, { force: true });
|
||||||
|
notice(t('touchRuntimeSaved'), 'success');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
notice(e.message || t('settingsSaveFailed'), 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function saveSettings() {
|
async function saveSettings() {
|
||||||
try {
|
try {
|
||||||
const data = await api('settings_save', {
|
const data = await api('settings_save', {
|
||||||
|
|||||||
+2
-1
@@ -146,6 +146,7 @@ html[data-theme="light"] .details-panel{background:#edf3fa;border-color:#b8c7da}
|
|||||||
.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}
|
.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)}
|
.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)}
|
||||||
.touch-display-status{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-bottom:14px}.touch-display-status div{display:grid;gap:4px;border:1px solid rgba(128,145,170,.28);border-radius:12px;background:var(--input);padding:10px 12px;min-width:0}.touch-display-status span{color:var(--sub);font-size:12px}.touch-display-status strong{font-size:13px;font-weight:500;word-break:break-word}.settings-inline-actions{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:14px}.settings-note{color:var(--sub);font-size:12px;word-break:break-all}
|
.touch-display-status{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-bottom:14px}.touch-display-status div{display:grid;gap:4px;border:1px solid rgba(128,145,170,.28);border-radius:12px;background:var(--input);padding:10px 12px;min-width:0}.touch-display-status span{color:var(--sub);font-size:12px}.touch-display-status strong{font-size:13px;font-weight:500;word-break:break-word}.settings-inline-actions{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:14px}.settings-note{color:var(--sub);font-size:12px;word-break:break-all}
|
||||||
|
.touch-runtime-box{margin-top:14px;padding-top:14px;border-top:1px solid rgba(128,145,170,.24)}.touch-runtime-box h4{margin:0 0 12px;font-size:15px;color:var(--text)}
|
||||||
@media(max-width:900px){.chart-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
@media(max-width:900px){.chart-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||||
@media(max-width:1100px){.layout{grid-template-columns:1fr}.resource-grid,.wifi-scan-grid{grid-template-columns:1fr}.topbar{align-items:flex-start;flex-direction:column}.topbar-right{width:100%}.topbar-right .btn{flex:1}.stat-grid{grid-template-columns:1fr}.resource-head{align-items:flex-start;flex-direction:column}.baseline-pill{text-align:left;white-space:normal}}
|
@media(max-width:1100px){.layout{grid-template-columns:1fr}.resource-grid,.wifi-scan-grid{grid-template-columns:1fr}.topbar{align-items:flex-start;flex-direction:column}.topbar-right{width:100%}.topbar-right .btn{flex:1}.stat-grid{grid-template-columns:1fr}.resource-head{align-items:flex-start;flex-direction:column}.baseline-pill{text-align:left;white-space:normal}}
|
||||||
@media(max-width:720px){.chart-grid{grid-template-columns:1fr}.settings-grid,.touch-display-status{grid-template-columns:1fr}.settings-head{align-items:stretch;flex-direction:column}.settings-actions{display:grid;grid-template-columns:1fr 1fr}.settings-box{max-height:calc(100vh - 24px)}}
|
@media(max-width:720px){.chart-grid{grid-template-columns:1fr}.settings-grid,.touch-display-status{grid-template-columns:1fr}.settings-head{align-items:stretch;flex-direction:column}.settings-actions{display:grid;grid-template-columns:1fr 1fr}.settings-box{max-height:calc(100vh - 24px)}}
|
||||||
@@ -364,7 +365,7 @@ html[data-theme="light"] .details-panel{background:#edf3fa;border-color:#b8c7da}
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/assets/app.js?v=20260723_touch_display1"></script>
|
<script src="/assets/app.js?v=20260723_touch_display2"></script>
|
||||||
<script src="/assets/wakelock.js?v=20260607_prefs1"></script>
|
<script src="/assets/wakelock.js?v=20260607_prefs1"></script>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user