diff --git a/audit/internal/platform/sat.go b/audit/internal/platform/sat.go index aa31eaf..76183e1 100644 --- a/audit/internal/platform/sat.go +++ b/audit/internal/platform/sat.go @@ -33,6 +33,10 @@ var ( "/opt/rocm/libexec/rocm_smi/rocm_smi.py", "/opt/rocm-*/libexec/rocm_smi/rocm_smi.py", } + rvsExecutableGlobs = []string{ + "/opt/rocm/bin/rvs", + "/opt/rocm-*/bin/rvs", + } ) // streamExecOutput runs cmd and streams each output line to logFunc (if non-nil). @@ -620,10 +624,23 @@ func resolveSATCommand(cmd []string) ([]string, error) { if len(cmd) == 0 { return nil, errors.New("empty SAT command") } - if cmd[0] != "rocm-smi" { - return cmd, nil + switch cmd[0] { + case "rocm-smi": + return resolveROCmSMICommand(cmd[1:]...) + case "rvs": + return resolveRVSCommand(cmd[1:]...) } - return resolveROCmSMICommand(cmd[1:]...) + return cmd, nil +} + +func resolveRVSCommand(args ...string) ([]string, error) { + if path, err := satLookPath("rvs"); err == nil { + return append([]string{path}, args...), nil + } + for _, path := range expandExistingPaths(rvsExecutableGlobs) { + return append([]string{path}, args...), nil + } + return nil, errors.New("rvs not found in PATH or under /opt/rocm") } func resolveROCmSMICommand(args ...string) ([]string, error) { diff --git a/audit/internal/webui/pages.go b/audit/internal/webui/pages.go index c99e6df..c60a9cc 100644 --- a/audit/internal/webui/pages.go +++ b/audit/internal/webui/pages.go @@ -61,7 +61,8 @@ tbody tr:hover td{background:rgba(0,0,0,.03)} .badge-err{background:var(--crit-bg);color:var(--crit-fg);border:1px solid var(--crit-border)} .badge-unknown{background:var(--surface-2);color:var(--muted);border:1px solid var(--border)} /* Output terminal */ -.terminal{background:#1b1c1d;border:1px solid rgba(0,0,0,.2);border-radius:4px;padding:14px;font-family:monospace;font-size:12px;color:#b5cea8;max-height:400px;overflow-y:auto;white-space:pre-wrap;word-break:break-all} +.terminal{background:#1b1c1d;border:1px solid rgba(0,0,0,.2);border-radius:4px;padding:14px;font-family:monospace;font-size:12px;color:#b5cea8;max-height:400px;overflow-y:auto;white-space:pre-wrap;word-break:break-all;user-select:text;-webkit-user-select:text} +.terminal-wrap{position:relative}.terminal-copy{position:absolute;top:6px;right:6px;background:#2d2f30;border:1px solid #444;color:#aaa;font-size:11px;padding:2px 8px;border-radius:3px;cursor:pointer;opacity:.7}.terminal-copy:hover{opacity:1} /* Forms */ .form-row{margin-bottom:14px} .form-row label{display:block;font-size:12px;color:var(--muted);margin-bottom:5px;font-weight:700} @@ -187,6 +188,16 @@ func renderPage(page string, opts HandlerOptions) string { body + `` + renderAuditModal() + + `` + `` }