better process details modal

This commit is contained in:
Abdenasser 2024-12-04 19:11:51 +01:00
parent dbc28d731f
commit cda6450ad2
2 changed files with 409 additions and 182 deletions

View File

@ -1,247 +1,373 @@
<script lang="ts"> <script lang="ts">
import { Modal } from "$lib/components"; import { Modal } from "$lib/components";
import { formatUptime, formatBytes, formatDate } from "$lib/utils"; import { formatBytes } from "$lib/utils";
import type { Process } from "$lib/types"; import type { Process } from "$lib/types";
import Fa from "svelte-fa"; import Fa from "svelte-fa";
import { import {
faClock,
faMemory, faMemory,
faMicrochip, faMicrochip,
faHardDrive, faCodeFork,
faTerminal,
faList,
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
export let show = false; export let show = false;
export let process: Process | null = null; export let process: Process | null = null;
export let onClose: () => void; export let onClose: () => void;
export let processes: Process[] = [];
export let onShowDetails: (process: Process) => void;
$: childProcesses = process
? processes.filter((p) => p.ppid === process.pid)
: [];
</script> </script>
<Modal {show} title="Process Details" maxWidth="700px" {onClose}> <Modal {show} title="Process Details" maxWidth="1000px" {onClose}>
{#if process} {#if process}
<div class="process-details"> <div class="modal-content">
<!-- Basic Info Section --> <!-- Header Stats -->
<section class="detail-section"> <div class="header-stats">
<h3>Basic Information</h3> <div class="stat-item">
<div class="detail-grid"> <div class="stat-label">PID</div>
<div class="detail-row"> <div class="stat-value">{process.pid}</div>
<span class="detail-label">Name:</span> </div>
<span class="detail-value">{process.name}</span> <div class="stat-item">
</div> <div class="stat-label">Status</div>
<div class="detail-row"> <div
<span class="detail-label">PID:</span> class="stat-value status"
<span class="detail-value">{process.pid}</span> class:running={process.status === "Running"}
</div> >
<div class="detail-row"> {process.status}
<span class="detail-label">Parent PID:</span>
<span class="detail-value">{process.ppid}</span>
</div>
<div class="detail-row">
<span class="detail-label">User:</span>
<span class="detail-value">{process.user}</span>
</div>
<div class="detail-row">
<span class="detail-label">Status:</span>
<span class="detail-value">{process.status}</span>
</div> </div>
</div> </div>
</section> <div class="stat-item">
<div class="stat-label">CPU</div>
<div class="stat-value">{process.cpu_usage.toFixed(1)}%</div>
</div>
<div class="stat-item">
<div class="stat-label">Memory</div>
<div class="stat-value">{formatBytes(process.memory_usage)}</div>
</div>
</div>
<!-- Resource Usage Section --> <!-- Main Content -->
<section class="detail-section"> <div class="content-grid">
<h3>Resource Usage</h3> <!-- Left Column -->
<div class="resource-grid"> <div class="content-column">
<!-- CPU Usage --> <!-- Process Info -->
<div class="resource-card"> <div class="card">
<div class="resource-header"> <div class="card-header">
<Fa icon={faMicrochip} /> <Fa icon={faMicrochip} />
<span>CPU Usage</span> <span>Process Information</span>
</div> </div>
<div class="resource-value"> <div class="card-content">
<div class="progress-bar"> <div class="info-grid">
<div <div class="info-item">
class="progress-fill" <span class="info-label">Name</span>
style="width: {process.cpu_usage}%" <span class="info-value">{process.name}</span>
class:high={process.cpu_usage > 50} </div>
class:critical={process.cpu_usage > 80} <div class="info-item">
></div> <span class="info-label">User</span>
<span class="info-value">{process.user}</span>
</div>
<div class="info-item">
<span class="info-label">Parent PID</span>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<span
class="info-value clickable"
on:click={() => {
const parent = processes.find(
(p) => p.pid === process.ppid,
);
if (parent) onShowDetails(parent);
}}
>
{process.ppid}
</span>
</div>
<div class="info-item">
<span class="info-label">Session ID</span>
<span class="info-value">{process.session_id}</span>
</div>
</div> </div>
<span>{process.cpu_usage.toFixed(1)}%</span>
</div> </div>
</div> </div>
<!-- Memory Usage --> <!-- Resource Usage -->
<div class="resource-card"> <div class="card">
<div class="resource-header"> <div class="card-header">
<Fa icon={faMemory} /> <Fa icon={faMemory} />
<span>Memory Usage</span> <span>Resource Usage</span>
</div> </div>
<div class="resource-stats"> <div class="card-content">
<div>Physical: {formatBytes(process.memory_usage)}</div> <div class="resource-grid">
<div>Virtual: {formatBytes(process.virtual_memory)}</div> <div class="resource-item">
</div> <div class="resource-header">
</div> <span>CPU Usage</span>
<span class="resource-value"
<!-- Disk I/O --> >{process.cpu_usage.toFixed(1)}%</span
<div class="resource-card"> >
<div class="resource-header"> </div>
<Fa icon={faHardDrive} /> <div class="progress-bar">
<span>Disk I/O</span> <div
</div> class="progress-fill"
<div class="resource-stats"> style="width: {process.cpu_usage}%"
<div>Read: {formatBytes(process.disk_usage[0])}</div> class:high={process.cpu_usage > 50}
<div>Written: {formatBytes(process.disk_usage[1])}</div> class:critical={process.cpu_usage > 80}
</div> ></div>
</div> </div>
</div>
<!-- Time Info --> <div class="resource-item">
<div class="resource-card"> <div class="resource-header">
<div class="resource-header"> <span>Memory Usage</span>
<Fa icon={faClock} /> </div>
<span>Time Information</span> <div class="memory-stats">
</div> <div>Physical: {formatBytes(process.memory_usage)}</div>
<div class="resource-stats"> <div>Virtual: {formatBytes(process.virtual_memory)}</div>
<div>Started: {formatDate(process.start_time)}</div> </div>
<div>Running: {formatUptime(process.run_time)}</div> </div>
<div class="resource-item">
<div class="resource-header">
<span>Disk I/O</span>
</div>
<div class="disk-stats">
<div>Read: {formatBytes(process.disk_usage[0])}</div>
<div>Written: {formatBytes(process.disk_usage[1])}</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</section>
<!-- Command & Environment Section --> <!-- Right Column -->
<section class="detail-section"> <div class="content-column">
<h3>Process Details</h3> <!-- Command -->
<div class="detail-grid"> <div class="card">
<div class="detail-row full-width"> <div class="card-header">
<span class="detail-label">Command:</span> <Fa icon={faTerminal} />
<span class="detail-value command">{process.command}</span> <span>Command</span>
</div> </div>
<div class="detail-row full-width"> <div class="card-content">
<span class="detail-label">Root:</span> <div class="command-text">{process.command}</div>
<span class="detail-value path">{process.root}</span> <div class="path-text">{process.root}</div>
</div>
</div> </div>
<!-- Child Processes -->
{#if childProcesses.length > 0}
<div class="card">
<div class="card-header">
<Fa icon={faCodeFork} />
<span>Child Processes ({childProcesses.length})</span>
</div>
<div class="card-content">
<table class="process-table">
<thead>
<tr>
<th>Name</th>
<th>PID</th>
<th>CPU</th>
<th>Memory</th>
</tr>
</thead>
<tbody>
{#each childProcesses as child}
<tr
class="clickable"
on:click={() => onShowDetails(child)}
>
<td>{child.name}</td>
<td>{child.pid}</td>
<td>{child.cpu_usage.toFixed(1)}%</td>
<td>{formatBytes(child.memory_usage)}</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
{/if}
<!-- Environment Variables -->
{#if process.environ.length > 0} {#if process.environ.length > 0}
<div class="detail-row full-width"> <div class="card">
<span class="detail-label">Environment:</span> <div class="card-header">
<div class="detail-value env-vars"> <Fa icon={faList} />
{#each process.environ as env} <span>Environment Variables</span>
<div class="env-var">{env}</div> </div>
{/each} <div class="card-content">
<div class="env-list">
{#each process.environ as env}
<div class="env-item">{env}</div>
{/each}
</div>
</div> </div>
</div> </div>
{/if} {/if}
</div> </div>
</section> </div>
</div> </div>
{/if} {/if}
</Modal> </Modal>
<style> <style>
.process-details { /* Base Modal Content */
.modal-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 24px; gap: 24px;
} font-size: 13px;
.detail-section {
display: flex;
flex-direction: column;
gap: 16px;
}
.detail-section h3 {
font-size: 14px;
font-weight: 600;
color: var(--text); color: var(--text);
margin: 0;
padding-bottom: 8px;
border-bottom: 1px solid var(--surface0);
} }
.detail-grid { /* Header Stats */
display: flex; .header-stats {
flex-direction: column;
gap: 12px;
}
.detail-row {
display: flex;
gap: 12px;
padding: 8px;
border-radius: 4px;
background: var(--surface0);
}
.detail-row.full-width {
flex-direction: column;
gap: 8px;
}
.detail-label {
display: flex;
color: var(--subtext0);
font-weight: 500;
}
.detail-value {
flex: 1;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
monospace;
word-break: break-all;
}
.detail-value.command,
.detail-value.path {
font-size: 12px;
color: var(--subtext1);
padding: 8px;
background: var(--mantle);
border-radius: 4px;
}
.resource-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 16px; gap: 16px;
}
.resource-card {
background: var(--surface0);
padding: 16px; padding: 16px;
background: var(--surface0);
border-radius: 8px; border-radius: 8px;
} }
.resource-header { .stat-item {
display: flex; display: flex;
align-items: center; flex-direction: column;
gap: 8px; gap: 4px;
margin-bottom: 12px; }
.stat-label {
font-size: 12px;
color: var(--subtext0); color: var(--subtext0);
font-weight: 500; font-weight: 500;
} }
.resource-header :global(svg) { .stat-value {
font-size: 16px;
font-weight: 600;
}
.stat-value.status {
color: var(--subtext0);
}
.stat-value.status.running {
color: var(--green);
}
/* Main Content Grid */
.content-grid {
display: grid;
grid-template-columns: minmax(300px, 0.4fr) minmax(400px, 0.6fr);
gap: 24px;
}
.content-column {
display: flex;
flex-direction: column;
gap: 24px;
min-width: 0; /* Prevent overflow issues */
}
/* Cards */
.card {
background: var(--surface0);
border-radius: 8px;
overflow: hidden;
min-width: 0; /* Prevent overflow issues */
}
.card-header {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
background: var(--surface1);
color: var(--subtext0);
font-weight: 500;
}
.card-header :global(svg) {
width: 14px; width: 14px;
height: 14px; height: 14px;
color: var(--blue); color: var(--blue);
} }
.resource-value { .card-content {
display: flex; padding: 16px;
align-items: center; overflow: auto;
gap: 12px;
} }
/* Info Grid */
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 16px;
}
.info-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.info-label {
color: var(--subtext0);
font-size: 12px;
}
.info-value {
color: var(--text);
}
.info-value.clickable {
cursor: pointer;
color: var(--blue);
}
.info-value.clickable:hover {
text-decoration: underline;
}
/* Resource Usage */
.resource-grid {
display: flex;
flex-direction: column;
gap: 16px;
}
.resource-item {
display: flex;
flex-direction: column;
gap: 8px;
}
.resource-header {
display: flex;
justify-content: space-between;
align-items: center;
color: var(--subtext0);
font-size: 12px;
}
.resource-value {
color: var(--text);
}
/* Progress Bar */
.progress-bar { .progress-bar {
flex: 1; height: 6px;
height: 8px;
background: var(--surface1); background: var(--surface1);
border-radius: 4px; border-radius: 3px;
overflow: hidden; overflow: hidden;
} }
.progress-fill { .progress-fill {
height: 100%; height: 100%;
background: var(--blue); background: var(--blue);
transition: width 0.3s ease; transition: width 0.2s ease;
} }
.progress-fill.high { .progress-fill.high {
@ -252,33 +378,132 @@
background: var(--red); background: var(--red);
} }
.resource-stats { /* Memory and Disk Stats */
display: flex; .memory-stats,
flex-direction: column; .disk-stats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px; gap: 8px;
font-size: 13px;
color: var(--text); color: var(--text);
} }
.env-vars { /* Command and Path */
.command-text,
.path-text {
word-break: break-all;
white-space: pre-wrap;
}
.path-text {
margin-top: 8px;
font-size: 12px;
color: var(--subtext0);
}
/* Process Table */
.process-table {
width: 100%;
border-collapse: collapse;
}
.process-table th {
text-align: left;
padding: 8px;
color: var(--subtext0);
font-weight: 500;
border-bottom: 1px solid var(--surface1);
}
.process-table td {
padding: 8px;
border-bottom: 1px solid var(--surface1);
}
.process-table tr:last-child td {
border-bottom: none;
}
.process-table tr.clickable {
cursor: pointer;
}
.process-table tr.clickable:hover {
background: var(--surface1);
}
/* Environment Variables */
.env-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 4px; gap: 4px;
max-height: 200px; max-height: 200px;
overflow-y: auto; overflow-y: auto;
padding: 8px; margin: -16px;
padding: 16px;
}
.env-item {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
padding: 4px 8px;
border-radius: 4px;
color: var(--subtext1);
font-size: 12px;
}
.env-item:hover {
background: var(--surface1);
}
/* Update scrollbar styles to match the container edges */
.env-list::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.env-list::-webkit-scrollbar-track {
background: var(--surface0);
border-radius: 0;
}
.env-list::-webkit-scrollbar-thumb {
background: var(--surface2);
border-radius: 4px;
border: 2px solid var(--surface0);
}
.env-list::-webkit-scrollbar-thumb:hover {
background: var(--surface1);
}
/* Scrollbar Styles */
:global(.modal-content *::-webkit-scrollbar) {
width: 8px;
height: 8px;
}
:global(.modal-content *::-webkit-scrollbar-track) {
background: var(--mantle); background: var(--mantle);
border-radius: 4px; border-radius: 4px;
} }
.env-var { :global(.modal-content *::-webkit-scrollbar-thumb) {
font-size: 12px; background: var(--surface2);
color: var(--subtext1);
padding: 4px 8px;
border-radius: 4px; border-radius: 4px;
} }
.env-var:hover { :global(.modal-content *::-webkit-scrollbar-thumb:hover) {
background: var(--surface1); background: var(--surface1);
} }
/* Responsive Design */
@media (max-width: 900px) {
.content-grid {
grid-template-columns: 1fr;
}
.header-stats {
grid-template-columns: repeat(2, 1fr);
}
}
</style> </style>

View File

@ -136,7 +136,9 @@
<ProcessDetailsModal <ProcessDetailsModal
show={showInfoModal} show={showInfoModal}
process={selectedProcess} process={selectedProcess}
{processes}
onClose={processStore.closeProcessDetails} onClose={processStore.closeProcessDetails}
onShowDetails={processStore.showProcessDetails}
/> />
<KillProcessModal <KillProcessModal