feat(debug): add hidden cross-platform diagnostics mode
Some checks failed
Desktop Release / Bootstrap updater signing key (push) Has been cancelled
Desktop Release / Windows x64 NSIS (push) Has been cancelled
Desktop Release / Linux x64 DEB + AppImage (push) Has been cancelled
Desktop Release / Publish public desktop release (push) Has been cancelled
Some checks failed
Desktop Release / Bootstrap updater signing key (push) Has been cancelled
Desktop Release / Windows x64 NSIS (push) Has been cancelled
Desktop Release / Linux x64 DEB + AppImage (push) Has been cancelled
Desktop Release / Publish public desktop release (push) Has been cancelled
This commit is contained in:
parent
e7df32eba8
commit
218a01180a
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@fieldlens/print",
|
"name": "@fieldlens/print",
|
||||||
"version": "0.1.18",
|
"version": "0.1.19",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fieldlens-print"
|
name = "fieldlens-print"
|
||||||
version = "0.1.18"
|
version = "0.1.19"
|
||||||
description = "FieldLens print-stream barcode capture companion"
|
description = "FieldLens print-stream barcode capture companion"
|
||||||
authors = ["Arrowstack"]
|
authors = ["Arrowstack"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@ -10,7 +10,7 @@ use parking_lot::Mutex;
|
|||||||
use tauri::{AppHandle, Manager, State};
|
use tauri::{AppHandle, Manager, State};
|
||||||
|
|
||||||
use capture::CaptureRuntime;
|
use capture::CaptureRuntime;
|
||||||
use model::{AppConfig, AuthStatus, CaptureStatus, JobSummary, LoginPoll, LoginStart, SyncResult};
|
use model::{AppConfig, AuthStatus, CaptureStatus, DebugCheck, DebugSnapshot, JobSummary, LoginPoll, LoginStart, SyncResult};
|
||||||
use printers::{SystemPrinter, VirtualPrinterStatus};
|
use printers::{SystemPrinter, VirtualPrinterStatus};
|
||||||
use state::SharedState;
|
use state::SharedState;
|
||||||
|
|
||||||
@ -152,6 +152,106 @@ fn build_self_test_pdf() -> Vec<u8> {
|
|||||||
pdf
|
pdf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug_command_check(name: &str) -> DebugCheck {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let result = std::process::Command::new("where").arg(name).output();
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
let result = std::process::Command::new("sh")
|
||||||
|
.args(["-lc", &format!("command -v {name}")])
|
||||||
|
.output();
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(output) if output.status.success() => DebugCheck {
|
||||||
|
name: name.into(),
|
||||||
|
ok: true,
|
||||||
|
detail: String::from_utf8_lossy(&output.stdout)
|
||||||
|
.lines()
|
||||||
|
.next()
|
||||||
|
.unwrap_or("available")
|
||||||
|
.trim()
|
||||||
|
.to_string(),
|
||||||
|
},
|
||||||
|
Ok(_) => DebugCheck {
|
||||||
|
name: name.into(),
|
||||||
|
ok: false,
|
||||||
|
detail: "not found".into(),
|
||||||
|
},
|
||||||
|
Err(error) => DebugCheck {
|
||||||
|
name: name.into(),
|
||||||
|
ok: false,
|
||||||
|
detail: error.to_string(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn get_debug_snapshot(state: State<'_, AppState>) -> Result<DebugSnapshot, String> {
|
||||||
|
let config = state.shared.config.lock().clone();
|
||||||
|
let jobs = state.shared.jobs.lock();
|
||||||
|
let installed_printers = printers::list_system_printers()
|
||||||
|
.map(|items| items.len())
|
||||||
|
.unwrap_or(0);
|
||||||
|
let virtual_printer = printers::virtual_printer_status()
|
||||||
|
.map(|value| value.detail)
|
||||||
|
.unwrap_or_else(|error| format!("status error: {error}"));
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
let capture_storage = "/var/lib/fieldlens-print/capture".to_string();
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
let capture_storage = config.spool_dir.clone();
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
let checks = vec![
|
||||||
|
debug_command_check("lp"),
|
||||||
|
debug_command_check("pdfinfo"),
|
||||||
|
debug_command_check("pdftoppm"),
|
||||||
|
debug_command_check("zbarimg"),
|
||||||
|
debug_command_check("pkexec"),
|
||||||
|
];
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let checks = vec![
|
||||||
|
debug_command_check("powershell.exe"),
|
||||||
|
debug_command_check("rundll32.exe"),
|
||||||
|
];
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
|
||||||
|
let checks = Vec::new();
|
||||||
|
|
||||||
|
let recent_errors = jobs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|job| {
|
||||||
|
job.error
|
||||||
|
.as_ref()
|
||||||
|
.map(|error| format!("{}: {}", job.label, error))
|
||||||
|
})
|
||||||
|
.take(5)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(DebugSnapshot {
|
||||||
|
platform: std::env::consts::OS.into(),
|
||||||
|
arch: std::env::consts::ARCH.into(),
|
||||||
|
app_version: env!("CARGO_PKG_VERSION").into(),
|
||||||
|
data_dir: state.shared.data_dir.to_string_lossy().into_owned(),
|
||||||
|
capture_storage,
|
||||||
|
capture_running: state.runtime.lock().is_some(),
|
||||||
|
capture_mode: config.mode.clone(),
|
||||||
|
pending: state.shared.pending_count(),
|
||||||
|
jobs: jobs.len(),
|
||||||
|
signed_in: state.shared.auth.lock().refresh_token.is_some(),
|
||||||
|
forward_printer: config.forward_printer,
|
||||||
|
listen_addr: config.listen_addr,
|
||||||
|
forward_addr: config.forward_addr,
|
||||||
|
client_id: config.client_id,
|
||||||
|
virtual_printer,
|
||||||
|
installed_printers,
|
||||||
|
recent_errors,
|
||||||
|
checks,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn run_capture_self_test() -> Result<String, String> {
|
fn run_capture_self_test() -> Result<String, String> {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
@ -222,6 +322,7 @@ pub fn run() {
|
|||||||
get_virtual_printer_status,
|
get_virtual_printer_status,
|
||||||
install_virtual_printer,
|
install_virtual_printer,
|
||||||
remove_virtual_printer,
|
remove_virtual_printer,
|
||||||
|
get_debug_snapshot,
|
||||||
run_capture_self_test,
|
run_capture_self_test,
|
||||||
begin_login,
|
begin_login,
|
||||||
poll_login,
|
poll_login,
|
||||||
|
|||||||
@ -133,3 +133,35 @@ pub struct SyncResult {
|
|||||||
pub synced: usize,
|
pub synced: usize,
|
||||||
pub pending: usize,
|
pub pending: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct DebugCheck {
|
||||||
|
pub name: String,
|
||||||
|
pub ok: bool,
|
||||||
|
pub detail: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct DebugSnapshot {
|
||||||
|
pub platform: String,
|
||||||
|
pub arch: String,
|
||||||
|
pub app_version: String,
|
||||||
|
pub data_dir: String,
|
||||||
|
pub capture_storage: String,
|
||||||
|
pub capture_running: bool,
|
||||||
|
pub capture_mode: CaptureMode,
|
||||||
|
pub pending: usize,
|
||||||
|
pub jobs: usize,
|
||||||
|
pub signed_in: bool,
|
||||||
|
pub forward_printer: String,
|
||||||
|
pub listen_addr: String,
|
||||||
|
pub forward_addr: String,
|
||||||
|
pub client_id: String,
|
||||||
|
pub virtual_printer: String,
|
||||||
|
pub installed_printers: usize,
|
||||||
|
pub recent_errors: Vec<String>,
|
||||||
|
pub checks: Vec<DebugCheck>,
|
||||||
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "FieldLens Print",
|
"productName": "FieldLens Print",
|
||||||
"version": "0.1.18",
|
"version": "0.1.19",
|
||||||
"identifier": "dev.arrowstack.fieldlens.print",
|
"identifier": "dev.arrowstack.fieldlens.print",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev",
|
"beforeDevCommand": "npm run dev",
|
||||||
|
|||||||
134
src/main.ts
134
src/main.ts
@ -76,6 +76,33 @@ type AuthStatus = {
|
|||||||
role?: string | null
|
role?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DebugCheck = {
|
||||||
|
name: string
|
||||||
|
ok: boolean
|
||||||
|
detail: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DebugSnapshot = {
|
||||||
|
platform: string
|
||||||
|
arch: string
|
||||||
|
appVersion: string
|
||||||
|
dataDir: string
|
||||||
|
captureStorage: string
|
||||||
|
captureRunning: boolean
|
||||||
|
captureMode: CaptureMode
|
||||||
|
pending: number
|
||||||
|
jobs: number
|
||||||
|
signedIn: boolean
|
||||||
|
forwardPrinter: string
|
||||||
|
listenAddr: string
|
||||||
|
forwardAddr: string
|
||||||
|
clientId: string
|
||||||
|
virtualPrinter: string
|
||||||
|
installedPrinters: number
|
||||||
|
recentErrors: string[]
|
||||||
|
checks: DebugCheck[]
|
||||||
|
}
|
||||||
|
|
||||||
type ManualReleaseDownload = {
|
type ManualReleaseDownload = {
|
||||||
url: string
|
url: string
|
||||||
sha256?: string
|
sha256?: string
|
||||||
@ -97,7 +124,7 @@ let config: AppConfig
|
|||||||
let jobs: Job[] = []
|
let jobs: Job[] = []
|
||||||
let status: Status = { running: false, mode: 'raw-proxy', pending: 0, jobs: 0 }
|
let status: Status = { running: false, mode: 'raw-proxy', pending: 0, jobs: 0 }
|
||||||
let loginTimer: number | undefined
|
let loginTimer: number | undefined
|
||||||
let currentVersion = '0.1.18'
|
let currentVersion = '0.1.19'
|
||||||
let bundleType: BundleType | null = null
|
let bundleType: BundleType | null = null
|
||||||
let availableUpdate: Update | null = null
|
let availableUpdate: Update | null = null
|
||||||
let availableManualUpdate: ManualUpdate | null = null
|
let availableManualUpdate: ManualUpdate | null = null
|
||||||
@ -110,6 +137,8 @@ let virtualPrinterStatus: VirtualPrinterStatus = {
|
|||||||
detail: 'Checking FieldLens queues…',
|
detail: 'Checking FieldLens queues…',
|
||||||
}
|
}
|
||||||
let authStatus: AuthStatus = { signedIn: false, accessStatus: 'signed-out', role: null }
|
let authStatus: AuthStatus = { signedIn: false, accessStatus: 'signed-out', role: null }
|
||||||
|
let debugEnabled = false
|
||||||
|
let debugSnapshot: DebugSnapshot | null = null
|
||||||
|
|
||||||
app.innerHTML = `
|
app.innerHTML = `
|
||||||
<div class="shell">
|
<div class="shell">
|
||||||
@ -260,6 +289,21 @@ app.innerHTML = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<section id="debug-panel" class="debug-panel" hidden>
|
||||||
|
<div class="debug-head">
|
||||||
|
<div>
|
||||||
|
<span class="eyebrow">DEBUG MODE</span>
|
||||||
|
<strong>Diagnostics</strong>
|
||||||
|
</div>
|
||||||
|
<div class="inline-actions">
|
||||||
|
<button class="btn ghost" id="debug-refresh" type="button">Refresh</button>
|
||||||
|
<button class="btn ghost" id="debug-copy" type="button">Copy report</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<pre id="debug-output" class="debug-output">Loading diagnostics…</pre>
|
||||||
|
<span id="debug-action-state" class="muted"></span>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@ -326,6 +370,65 @@ function setView(view: 'setup' | 'running') {
|
|||||||
el('running-tab').classList.toggle('active', view === 'running')
|
el('running-tab').classList.toggle('active', view === 'running')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatDebugReport(snapshot: DebugSnapshot) {
|
||||||
|
const checks = snapshot.checks.length
|
||||||
|
? snapshot.checks.map((check) => ` ${check.ok ? 'OK ' : 'ERR'} ${check.name}: ${check.detail}`).join('\n')
|
||||||
|
: ' (no platform command checks)'
|
||||||
|
|
||||||
|
const errors = snapshot.recentErrors.length
|
||||||
|
? snapshot.recentErrors.map((error) => ` - ${error}`).join('\n')
|
||||||
|
: ' none'
|
||||||
|
|
||||||
|
return [
|
||||||
|
`FieldLens Print ${snapshot.appVersion}`,
|
||||||
|
`Platform: ${snapshot.platform} / ${snapshot.arch}`,
|
||||||
|
`Bundle: ${bundleType ?? 'unknown'}`,
|
||||||
|
'',
|
||||||
|
`Capture: ${snapshot.captureRunning ? 'running' : 'stopped'} (${snapshot.captureMode})`,
|
||||||
|
`Pending scans: ${snapshot.pending}`,
|
||||||
|
`Recent jobs: ${snapshot.jobs}`,
|
||||||
|
`Capture storage: ${snapshot.captureStorage}`,
|
||||||
|
`Data dir: ${snapshot.dataDir}`,
|
||||||
|
'',
|
||||||
|
`Physical printer: ${snapshot.forwardPrinter || '(not selected)'}`,
|
||||||
|
`Installed printers: ${snapshot.installedPrinters}`,
|
||||||
|
`Virtual printer: ${snapshot.virtualPrinter}`,
|
||||||
|
`Listen: ${snapshot.listenAddr || '(unset)'}`,
|
||||||
|
`Forward: ${snapshot.forwardAddr || '(unset)'}`,
|
||||||
|
'',
|
||||||
|
`Auth session: ${snapshot.signedIn ? 'present' : 'none'}`,
|
||||||
|
`Access: ${authStatus.accessStatus}${authStatus.role ? ` (${authStatus.role})` : ''}`,
|
||||||
|
`OIDC client: ${snapshot.clientId}`,
|
||||||
|
'',
|
||||||
|
'Platform checks:',
|
||||||
|
checks,
|
||||||
|
'',
|
||||||
|
'Recent job errors:',
|
||||||
|
errors,
|
||||||
|
].join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshDebugSnapshot() {
|
||||||
|
if (!debugEnabled) return
|
||||||
|
const output = el('debug-output')
|
||||||
|
output.textContent = 'Loading diagnostics…'
|
||||||
|
try {
|
||||||
|
debugSnapshot = await invoke<DebugSnapshot>('get_debug_snapshot')
|
||||||
|
output.textContent = formatDebugReport(debugSnapshot)
|
||||||
|
} catch (error) {
|
||||||
|
output.textContent = `Debug snapshot failed: ${String(error)}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDebugEnabled(enabled: boolean) {
|
||||||
|
debugEnabled = enabled
|
||||||
|
el('debug-panel').hidden = !enabled
|
||||||
|
if (enabled) {
|
||||||
|
setView('setup')
|
||||||
|
void refreshDebugSnapshot()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function readForm(): AppConfig {
|
function readForm(): AppConfig {
|
||||||
return {
|
return {
|
||||||
...config,
|
...config,
|
||||||
@ -443,6 +546,7 @@ async function refreshAuthStatus() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
renderAuthStatus()
|
renderAuthStatus()
|
||||||
|
if (debugEnabled) void refreshDebugSnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatBytes(bytes: number) {
|
function formatBytes(bytes: number) {
|
||||||
@ -523,6 +627,7 @@ async function refreshStatus() {
|
|||||||
status = await invoke<Status>('get_status')
|
status = await invoke<Status>('get_status')
|
||||||
jobs = await invoke<Job[]>('recent_jobs')
|
jobs = await invoke<Job[]>('recent_jobs')
|
||||||
render()
|
render()
|
||||||
|
if (debugEnabled) void refreshDebugSnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureCaptureStarted() {
|
async function ensureCaptureStarted() {
|
||||||
@ -549,6 +654,7 @@ el('forwardPrinter').addEventListener('change', () => {
|
|||||||
el<HTMLInputElement>('forwardAddr').value = ''
|
el<HTMLInputElement>('forwardAddr').value = ''
|
||||||
}
|
}
|
||||||
renderPrinters()
|
renderPrinters()
|
||||||
|
if (debugEnabled) void refreshDebugSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
el('refresh-printers').addEventListener('click', () => { void refreshPrinters() })
|
el('refresh-printers').addEventListener('click', () => { void refreshPrinters() })
|
||||||
@ -656,6 +762,32 @@ el('stop').addEventListener('click', async () => {
|
|||||||
await refreshStatus()
|
await refreshStatus()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
window.addEventListener('keydown', (event) => {
|
||||||
|
if ((event.ctrlKey || event.metaKey) && event.shiftKey && event.key.toLowerCase() === 'd') {
|
||||||
|
event.preventDefault()
|
||||||
|
setDebugEnabled(!debugEnabled)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
el('debug-refresh').addEventListener('click', () => {
|
||||||
|
void refreshDebugSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
el('debug-copy').addEventListener('click', async () => {
|
||||||
|
const state = el('debug-action-state')
|
||||||
|
if (!debugSnapshot) await refreshDebugSnapshot()
|
||||||
|
if (!debugSnapshot) {
|
||||||
|
state.textContent = 'No debug report available.'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(formatDebugReport(debugSnapshot))
|
||||||
|
state.textContent = 'Diagnostic report copied. Secrets and tokens are excluded.'
|
||||||
|
} catch (error) {
|
||||||
|
state.textContent = `Could not copy report: ${String(error)}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
el('setup-tab').addEventListener('click', () => setView('setup'))
|
el('setup-tab').addEventListener('click', () => setView('setup'))
|
||||||
el('running-tab').addEventListener('click', () => setView('running'))
|
el('running-tab').addEventListener('click', () => setView('running'))
|
||||||
|
|
||||||
|
|||||||
@ -641,3 +641,52 @@ select:focus {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.debug-panel {
|
||||||
|
margin-top: 12px;
|
||||||
|
border: 1px solid #5b4720;
|
||||||
|
border-radius: 11px;
|
||||||
|
background: #151109;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-panel[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 11px 12px;
|
||||||
|
border-bottom: 1px solid #3a2d16;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-head > div:first-child {
|
||||||
|
display: grid;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-head .eyebrow {
|
||||||
|
color: #e3ad4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-output {
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px;
|
||||||
|
max-height: 340px;
|
||||||
|
overflow: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
color: #d8dee7;
|
||||||
|
background: #0b0e12;
|
||||||
|
font: 11px/1.55 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
#debug-action-state {
|
||||||
|
display: block;
|
||||||
|
min-height: 14px;
|
||||||
|
padding: 0 12px 10px;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user