bgrun 3.12.3 → 3.12.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dashboard/app/api/history/route.ts +73 -7
- package/dashboard/app/api/logs/[name]/route.ts +100 -67
- package/dashboard/app/globals.css +176 -168
- package/dashboard/app/page.client.tsx +36 -0
- package/dashboard/app/page.tsx +9 -0
- package/package.json +19 -4
- package/src/bgrun.test.ts +0 -313
- package/src/index_copy.ts +0 -614
|
@@ -1,39 +1,105 @@
|
|
|
1
1
|
import { getProcessHistory, getRecentHistory, addHistoryEntry } from '../../../../src/db';
|
|
2
2
|
|
|
3
|
+
function stringifyMetadata(metadata: unknown) {
|
|
4
|
+
try {
|
|
5
|
+
return JSON.stringify(metadata ?? {});
|
|
6
|
+
} catch {
|
|
7
|
+
return '{}';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function escapeCsv(value: unknown) {
|
|
12
|
+
const text = String(value ?? '');
|
|
13
|
+
if (/[",\n\r]/.test(text)) {
|
|
14
|
+
return `"${text.replace(/"/g, '""')}"`;
|
|
15
|
+
}
|
|
16
|
+
return text;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function buildHistoryCsv(rows: Array<{
|
|
20
|
+
process_name: string;
|
|
21
|
+
event: string;
|
|
22
|
+
pid: number | null;
|
|
23
|
+
timestamp: string;
|
|
24
|
+
metadata: unknown;
|
|
25
|
+
}>) {
|
|
26
|
+
const header = ['process_name', 'event', 'pid', 'timestamp', 'metadata'];
|
|
27
|
+
const lines = rows.map((row) => [
|
|
28
|
+
row.process_name,
|
|
29
|
+
row.event,
|
|
30
|
+
row.pid ?? '',
|
|
31
|
+
row.timestamp,
|
|
32
|
+
stringifyMetadata(row.metadata),
|
|
33
|
+
].map(escapeCsv).join(','));
|
|
34
|
+
return [header.join(','), ...lines].join('\n');
|
|
35
|
+
}
|
|
36
|
+
|
|
3
37
|
export async function GET(req: Request) {
|
|
4
38
|
const url = new URL(req.url);
|
|
5
39
|
const name = url.searchParams.get('name');
|
|
40
|
+
const event = (url.searchParams.get('event') || '').trim().toLowerCase();
|
|
41
|
+
const metadataFilter = (url.searchParams.get('metadata') || '')
|
|
42
|
+
.split(',')
|
|
43
|
+
.map((value) => value.toLowerCase().trim())
|
|
44
|
+
.filter(Boolean);
|
|
6
45
|
const limit = parseInt(url.searchParams.get('limit') || '50');
|
|
7
|
-
|
|
46
|
+
const format = (url.searchParams.get('format') || 'json').toLowerCase();
|
|
47
|
+
const download = url.searchParams.get('download') === '1';
|
|
48
|
+
|
|
8
49
|
let history;
|
|
9
50
|
if (name) {
|
|
10
51
|
history = getProcessHistory(name, limit);
|
|
11
52
|
} else {
|
|
12
53
|
history = getRecentHistory(limit);
|
|
13
54
|
}
|
|
14
|
-
|
|
15
|
-
|
|
55
|
+
|
|
56
|
+
let rows = history.map((h: any) => ({
|
|
16
57
|
process_name: h.process_name,
|
|
17
58
|
event: h.event,
|
|
18
59
|
pid: h.pid,
|
|
19
60
|
timestamp: h.timestamp,
|
|
20
61
|
metadata: h.metadata ? JSON.parse(h.metadata) : {},
|
|
21
|
-
}))
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
if (event) {
|
|
65
|
+
rows = rows.filter((row) => row.event.toLowerCase() === event);
|
|
66
|
+
}
|
|
67
|
+
if (metadataFilter.length > 0) {
|
|
68
|
+
rows = rows.filter((row) => {
|
|
69
|
+
const haystack = stringifyMetadata(row.metadata).toLowerCase();
|
|
70
|
+
return metadataFilter.every((term) => haystack.includes(term));
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (format === 'csv') {
|
|
75
|
+
return new Response(buildHistoryCsv(rows), {
|
|
76
|
+
headers: {
|
|
77
|
+
'content-type': 'text/csv; charset=utf-8',
|
|
78
|
+
...(download ? { 'content-disposition': `attachment; filename="bgr-history${name ? `-${encodeURIComponent(name)}` : ''}.csv"` } : {}),
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return Response.json(rows, {
|
|
84
|
+
headers: download
|
|
85
|
+
? { 'content-disposition': `attachment; filename="bgr-history${name ? `-${encodeURIComponent(name)}` : ''}.json"` }
|
|
86
|
+
: undefined,
|
|
87
|
+
});
|
|
22
88
|
}
|
|
23
89
|
|
|
24
90
|
export async function POST(req: Request) {
|
|
25
91
|
try {
|
|
26
92
|
const body = await req.json();
|
|
27
93
|
const { process_name, event, pid, metadata } = body;
|
|
28
|
-
|
|
94
|
+
|
|
29
95
|
if (!process_name || !event) {
|
|
30
96
|
return Response.json({ error: 'process_name and event are required' }, { status: 400 });
|
|
31
97
|
}
|
|
32
|
-
|
|
98
|
+
|
|
33
99
|
addHistoryEntry(process_name, event, pid, metadata);
|
|
34
100
|
return Response.json({ success: true });
|
|
35
101
|
} catch (err) {
|
|
36
102
|
console.error('[api/history] Error adding history:', err);
|
|
37
103
|
return Response.json({ error: 'Failed to add history' }, { status: 500 });
|
|
38
104
|
}
|
|
39
|
-
}
|
|
105
|
+
}
|
|
@@ -1,67 +1,100 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GET /api/logs/:name — Read process stdout/stderr logs
|
|
3
|
-
*
|
|
4
|
-
* Supports incremental loading via query params:
|
|
5
|
-
* ?tab=stdout|stderr — which log to read (default: stdout)
|
|
6
|
-
* ?offset=N — byte offset to start reading from (default: 0 = full file)
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/logs/:name — Read process stdout/stderr logs
|
|
3
|
+
*
|
|
4
|
+
* Supports incremental loading via query params:
|
|
5
|
+
* ?tab=stdout|stderr — which log to read (default: stdout)
|
|
6
|
+
* ?offset=N — byte offset to start reading from (default: 0 = full file)
|
|
7
|
+
* ?format=json|text|csv — response format (default: json)
|
|
8
|
+
*
|
|
9
|
+
* Returns JSON by default:
|
|
10
|
+
* { text, size, mtime, filePath }
|
|
11
|
+
*/
|
|
12
|
+
import { getProcess } from '../../../../../src/db';
|
|
13
|
+
import { stat, open } from 'fs/promises';
|
|
14
|
+
|
|
15
|
+
interface FileInfo {
|
|
16
|
+
text: string;
|
|
17
|
+
size: number;
|
|
18
|
+
mtime: string | null;
|
|
19
|
+
filePath: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function escapeCsv(value: unknown) {
|
|
23
|
+
const text = String(value ?? '');
|
|
24
|
+
if (/[",\n\r]/.test(text)) {
|
|
25
|
+
return `"${text.replace(/"/g, '""')}"`;
|
|
26
|
+
}
|
|
27
|
+
return text;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function buildLogCsv(text: string) {
|
|
31
|
+
const header = 'line,text';
|
|
32
|
+
const lines = text.split('\n').map((line, index) => `${index + 1},${escapeCsv(line)}`);
|
|
33
|
+
return [header, ...lines].join('\n');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function readLogFile(path: string, offset: number): Promise<FileInfo> {
|
|
37
|
+
try {
|
|
38
|
+
const s = await stat(path);
|
|
39
|
+
const size = s.size;
|
|
40
|
+
const mtime = s.mtime.toISOString();
|
|
41
|
+
|
|
42
|
+
if (offset >= size) {
|
|
43
|
+
return { text: '', size, mtime, filePath: path };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const handle = await open(path, 'r');
|
|
47
|
+
try {
|
|
48
|
+
const bytesToRead = size - offset;
|
|
49
|
+
const buffer = Buffer.alloc(bytesToRead);
|
|
50
|
+
await handle.read(buffer, 0, bytesToRead, offset);
|
|
51
|
+
return { text: buffer.toString('utf-8'), size, mtime, filePath: path };
|
|
52
|
+
} finally {
|
|
53
|
+
await handle.close();
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
return { text: '', size: 0, mtime: null, filePath: path };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function GET(req: Request, { params }: { params: { name: string } }) {
|
|
61
|
+
const name = decodeURIComponent(params.name);
|
|
62
|
+
const proc = getProcess(name);
|
|
63
|
+
|
|
64
|
+
if (!proc) {
|
|
65
|
+
return Response.json({ error: 'Process not found' }, { status: 404 });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const url = new URL(req.url);
|
|
69
|
+
const tab = url.searchParams.get('tab') || 'stdout';
|
|
70
|
+
const offset = parseInt(url.searchParams.get('offset') || '0', 10) || 0;
|
|
71
|
+
const format = (url.searchParams.get('format') || 'json').toLowerCase();
|
|
72
|
+
const download = url.searchParams.get('download') === '1';
|
|
73
|
+
|
|
74
|
+
const path = tab === 'stderr' ? proc.stderr_path : proc.stdout_path;
|
|
75
|
+
const info = await readLogFile(path, offset);
|
|
76
|
+
|
|
77
|
+
if (format === 'text') {
|
|
78
|
+
return new Response(info.text, {
|
|
79
|
+
headers: {
|
|
80
|
+
'content-type': 'text/plain; charset=utf-8',
|
|
81
|
+
...(download ? { 'content-disposition': `attachment; filename="${encodeURIComponent(name)}-${tab}.log"` } : {}),
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (format === 'csv') {
|
|
87
|
+
return new Response(buildLogCsv(info.text), {
|
|
88
|
+
headers: {
|
|
89
|
+
'content-type': 'text/csv; charset=utf-8',
|
|
90
|
+
...(download ? { 'content-disposition': `attachment; filename="${encodeURIComponent(name)}-${tab}.csv"` } : {}),
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return Response.json(info, {
|
|
96
|
+
headers: download
|
|
97
|
+
? { 'content-disposition': `attachment; filename="${encodeURIComponent(name)}-${tab}.json"` }
|
|
98
|
+
: undefined,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -2463,6 +2463,14 @@ a.port-link:hover {
|
|
|
2463
2463
|
border-bottom: 1px solid var(--border-subtle);
|
|
2464
2464
|
background: rgba(0, 0, 0, 0.08);
|
|
2465
2465
|
flex-shrink: 0;
|
|
2466
|
+
flex-wrap: wrap;
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
.toolbar-export-actions {
|
|
2470
|
+
display: inline-flex;
|
|
2471
|
+
align-items: center;
|
|
2472
|
+
gap: 0.5rem;
|
|
2473
|
+
flex-wrap: wrap;
|
|
2466
2474
|
}
|
|
2467
2475
|
|
|
2468
2476
|
.log-search {
|
|
@@ -4000,171 +4008,171 @@ tr.keyboard-focus td:first-child .process-name span {
|
|
|
4000
4008
|
opacity: 0.6;
|
|
4001
4009
|
cursor: wait;
|
|
4002
4010
|
}
|
|
4003
|
-
|
|
4004
|
-
/* ─── Dependencies Graph ─── */
|
|
4005
|
-
|
|
4006
|
-
.deps-controls {
|
|
4007
|
-
display: flex;
|
|
4008
|
-
align-items: center;
|
|
4009
|
-
gap: 0.75rem;
|
|
4010
|
-
margin-bottom: 1.25rem;
|
|
4011
|
-
flex-wrap: wrap;
|
|
4012
|
-
}
|
|
4013
|
-
|
|
4014
|
-
.deps-arrow {
|
|
4015
|
-
color: var(--text-secondary);
|
|
4016
|
-
font-size: 0.85rem;
|
|
4017
|
-
white-space: nowrap;
|
|
4018
|
-
}
|
|
4019
|
-
|
|
4020
|
-
.deps-graph-container {
|
|
4021
|
-
background: rgba(0, 0, 0, 0.15);
|
|
4022
|
-
border-radius: 12px;
|
|
4023
|
-
border: 1px solid var(--border);
|
|
4024
|
-
margin-bottom: 1.25rem;
|
|
4025
|
-
overflow: hidden;
|
|
4026
|
-
min-height: 300px;
|
|
4027
|
-
position: relative;
|
|
4028
|
-
}
|
|
4029
|
-
|
|
4030
|
-
.deps-graph-container svg {
|
|
4031
|
-
display: block;
|
|
4032
|
-
}
|
|
4033
|
-
|
|
4034
|
-
.deps-node {
|
|
4035
|
-
cursor: pointer;
|
|
4036
|
-
}
|
|
4037
|
-
|
|
4038
|
-
.deps-node rect {
|
|
4039
|
-
rx: 8;
|
|
4040
|
-
ry: 8;
|
|
4041
|
-
stroke-width: 2;
|
|
4042
|
-
transition: filter 0.15s;
|
|
4043
|
-
}
|
|
4044
|
-
|
|
4045
|
-
.deps-node:hover rect {
|
|
4046
|
-
filter: brightness(1.3);
|
|
4047
|
-
}
|
|
4048
|
-
|
|
4049
|
-
.deps-node text {
|
|
4050
|
-
font-family: var(--font-mono);
|
|
4051
|
-
font-size: 12px;
|
|
4052
|
-
fill: var(--text-primary);
|
|
4053
|
-
pointer-events: none;
|
|
4054
|
-
}
|
|
4055
|
-
|
|
4056
|
-
.deps-edge {
|
|
4057
|
-
stroke: var(--text-secondary);
|
|
4058
|
-
stroke-width: 1.5;
|
|
4059
|
-
fill: none;
|
|
4060
|
-
opacity: 0.6;
|
|
4061
|
-
marker-end: url(#deps-arrowhead);
|
|
4062
|
-
}
|
|
4063
|
-
|
|
4064
|
-
.deps-edge-highlight {
|
|
4065
|
-
stroke: var(--accent);
|
|
4066
|
-
opacity: 1;
|
|
4067
|
-
stroke-width: 2;
|
|
4068
|
-
}
|
|
4069
|
-
|
|
4070
|
-
.deps-list {
|
|
4071
|
-
margin-bottom: 1rem;
|
|
4072
|
-
}
|
|
4073
|
-
|
|
4074
|
-
.deps-list-item {
|
|
4075
|
-
display: flex;
|
|
4076
|
-
align-items: center;
|
|
4077
|
-
gap: 0.5rem;
|
|
4078
|
-
padding: 0.5rem 0.75rem;
|
|
4079
|
-
border-bottom: 1px solid var(--border);
|
|
4080
|
-
font-size: 0.85rem;
|
|
4081
|
-
}
|
|
4082
|
-
|
|
4083
|
-
.deps-list-item:last-child {
|
|
4084
|
-
border-bottom: none;
|
|
4085
|
-
}
|
|
4086
|
-
|
|
4087
|
-
.deps-list-item .deps-item-process {
|
|
4088
|
-
font-weight: 600;
|
|
4089
|
-
color: var(--accent);
|
|
4090
|
-
font-family: var(--font-mono);
|
|
4091
|
-
}
|
|
4092
|
-
|
|
4093
|
-
.deps-list-item .deps-item-arrow {
|
|
4094
|
-
color: var(--text-secondary);
|
|
4095
|
-
}
|
|
4096
|
-
|
|
4097
|
-
.deps-list-item .deps-item-target {
|
|
4098
|
-
font-family: var(--font-mono);
|
|
4099
|
-
color: var(--text-primary);
|
|
4100
|
-
}
|
|
4101
|
-
|
|
4102
|
-
.deps-list-item .deps-remove-btn {
|
|
4103
|
-
margin-left: auto;
|
|
4104
|
-
background: none;
|
|
4105
|
-
border: none;
|
|
4106
|
-
color: var(--red);
|
|
4107
|
-
cursor: pointer;
|
|
4108
|
-
font-size: 1rem;
|
|
4109
|
-
padding: 0 0.25rem;
|
|
4110
|
-
opacity: 0.6;
|
|
4111
|
-
transition: opacity 0.15s;
|
|
4112
|
-
}
|
|
4113
|
-
|
|
4114
|
-
.deps-list-item .deps-remove-btn:hover {
|
|
4115
|
-
opacity: 1;
|
|
4116
|
-
}
|
|
4117
|
-
|
|
4118
|
-
.deps-start-order {
|
|
4119
|
-
padding: 0.75rem;
|
|
4120
|
-
background: rgba(0, 0, 0, 0.1);
|
|
4121
|
-
border-radius: 8px;
|
|
4122
|
-
font-size: 0.8rem;
|
|
4123
|
-
color: var(--text-secondary);
|
|
4124
|
-
}
|
|
4125
|
-
|
|
4126
|
-
.deps-start-order .deps-order-title {
|
|
4127
|
-
font-weight: 600;
|
|
4128
|
-
color: var(--text-primary);
|
|
4129
|
-
margin-bottom: 0.35rem;
|
|
4130
|
-
}
|
|
4131
|
-
|
|
4132
|
-
.deps-start-order .deps-order-list {
|
|
4133
|
-
display: flex;
|
|
4134
|
-
flex-wrap: wrap;
|
|
4135
|
-
gap: 0.35rem;
|
|
4136
|
-
}
|
|
4137
|
-
|
|
4138
|
-
.deps-order-badge {
|
|
4139
|
-
display: inline-flex;
|
|
4140
|
-
align-items: center;
|
|
4141
|
-
gap: 0.3rem;
|
|
4142
|
-
padding: 0.2rem 0.5rem;
|
|
4143
|
-
background: rgba(var(--accent-rgb, 99, 102, 241), 0.15);
|
|
4144
|
-
border-radius: 6px;
|
|
4145
|
-
font-family: var(--font-mono);
|
|
4146
|
-
font-size: 0.75rem;
|
|
4147
|
-
}
|
|
4148
|
-
|
|
4149
|
-
.deps-order-badge .deps-order-num {
|
|
4150
|
-
color: var(--accent);
|
|
4151
|
-
font-weight: 700;
|
|
4152
|
-
font-size: 0.7rem;
|
|
4153
|
-
}
|
|
4154
|
-
|
|
4155
|
-
.deps-empty {
|
|
4156
|
-
text-align: center;
|
|
4157
|
-
padding: 2rem;
|
|
4158
|
-
color: var(--text-secondary);
|
|
4159
|
-
font-size: 0.9rem;
|
|
4160
|
-
}
|
|
4161
|
-
|
|
4162
|
-
@media (max-width: 768px) {
|
|
4163
|
-
.deps-controls {
|
|
4164
|
-
flex-direction: column;
|
|
4165
|
-
align-items: stretch;
|
|
4166
|
-
}
|
|
4167
|
-
.deps-arrow {
|
|
4168
|
-
text-align: center;
|
|
4169
|
-
}
|
|
4170
|
-
}
|
|
4011
|
+
|
|
4012
|
+
/* ─── Dependencies Graph ─── */
|
|
4013
|
+
|
|
4014
|
+
.deps-controls {
|
|
4015
|
+
display: flex;
|
|
4016
|
+
align-items: center;
|
|
4017
|
+
gap: 0.75rem;
|
|
4018
|
+
margin-bottom: 1.25rem;
|
|
4019
|
+
flex-wrap: wrap;
|
|
4020
|
+
}
|
|
4021
|
+
|
|
4022
|
+
.deps-arrow {
|
|
4023
|
+
color: var(--text-secondary);
|
|
4024
|
+
font-size: 0.85rem;
|
|
4025
|
+
white-space: nowrap;
|
|
4026
|
+
}
|
|
4027
|
+
|
|
4028
|
+
.deps-graph-container {
|
|
4029
|
+
background: rgba(0, 0, 0, 0.15);
|
|
4030
|
+
border-radius: 12px;
|
|
4031
|
+
border: 1px solid var(--border);
|
|
4032
|
+
margin-bottom: 1.25rem;
|
|
4033
|
+
overflow: hidden;
|
|
4034
|
+
min-height: 300px;
|
|
4035
|
+
position: relative;
|
|
4036
|
+
}
|
|
4037
|
+
|
|
4038
|
+
.deps-graph-container svg {
|
|
4039
|
+
display: block;
|
|
4040
|
+
}
|
|
4041
|
+
|
|
4042
|
+
.deps-node {
|
|
4043
|
+
cursor: pointer;
|
|
4044
|
+
}
|
|
4045
|
+
|
|
4046
|
+
.deps-node rect {
|
|
4047
|
+
rx: 8;
|
|
4048
|
+
ry: 8;
|
|
4049
|
+
stroke-width: 2;
|
|
4050
|
+
transition: filter 0.15s;
|
|
4051
|
+
}
|
|
4052
|
+
|
|
4053
|
+
.deps-node:hover rect {
|
|
4054
|
+
filter: brightness(1.3);
|
|
4055
|
+
}
|
|
4056
|
+
|
|
4057
|
+
.deps-node text {
|
|
4058
|
+
font-family: var(--font-mono);
|
|
4059
|
+
font-size: 12px;
|
|
4060
|
+
fill: var(--text-primary);
|
|
4061
|
+
pointer-events: none;
|
|
4062
|
+
}
|
|
4063
|
+
|
|
4064
|
+
.deps-edge {
|
|
4065
|
+
stroke: var(--text-secondary);
|
|
4066
|
+
stroke-width: 1.5;
|
|
4067
|
+
fill: none;
|
|
4068
|
+
opacity: 0.6;
|
|
4069
|
+
marker-end: url(#deps-arrowhead);
|
|
4070
|
+
}
|
|
4071
|
+
|
|
4072
|
+
.deps-edge-highlight {
|
|
4073
|
+
stroke: var(--accent);
|
|
4074
|
+
opacity: 1;
|
|
4075
|
+
stroke-width: 2;
|
|
4076
|
+
}
|
|
4077
|
+
|
|
4078
|
+
.deps-list {
|
|
4079
|
+
margin-bottom: 1rem;
|
|
4080
|
+
}
|
|
4081
|
+
|
|
4082
|
+
.deps-list-item {
|
|
4083
|
+
display: flex;
|
|
4084
|
+
align-items: center;
|
|
4085
|
+
gap: 0.5rem;
|
|
4086
|
+
padding: 0.5rem 0.75rem;
|
|
4087
|
+
border-bottom: 1px solid var(--border);
|
|
4088
|
+
font-size: 0.85rem;
|
|
4089
|
+
}
|
|
4090
|
+
|
|
4091
|
+
.deps-list-item:last-child {
|
|
4092
|
+
border-bottom: none;
|
|
4093
|
+
}
|
|
4094
|
+
|
|
4095
|
+
.deps-list-item .deps-item-process {
|
|
4096
|
+
font-weight: 600;
|
|
4097
|
+
color: var(--accent);
|
|
4098
|
+
font-family: var(--font-mono);
|
|
4099
|
+
}
|
|
4100
|
+
|
|
4101
|
+
.deps-list-item .deps-item-arrow {
|
|
4102
|
+
color: var(--text-secondary);
|
|
4103
|
+
}
|
|
4104
|
+
|
|
4105
|
+
.deps-list-item .deps-item-target {
|
|
4106
|
+
font-family: var(--font-mono);
|
|
4107
|
+
color: var(--text-primary);
|
|
4108
|
+
}
|
|
4109
|
+
|
|
4110
|
+
.deps-list-item .deps-remove-btn {
|
|
4111
|
+
margin-left: auto;
|
|
4112
|
+
background: none;
|
|
4113
|
+
border: none;
|
|
4114
|
+
color: var(--red);
|
|
4115
|
+
cursor: pointer;
|
|
4116
|
+
font-size: 1rem;
|
|
4117
|
+
padding: 0 0.25rem;
|
|
4118
|
+
opacity: 0.6;
|
|
4119
|
+
transition: opacity 0.15s;
|
|
4120
|
+
}
|
|
4121
|
+
|
|
4122
|
+
.deps-list-item .deps-remove-btn:hover {
|
|
4123
|
+
opacity: 1;
|
|
4124
|
+
}
|
|
4125
|
+
|
|
4126
|
+
.deps-start-order {
|
|
4127
|
+
padding: 0.75rem;
|
|
4128
|
+
background: rgba(0, 0, 0, 0.1);
|
|
4129
|
+
border-radius: 8px;
|
|
4130
|
+
font-size: 0.8rem;
|
|
4131
|
+
color: var(--text-secondary);
|
|
4132
|
+
}
|
|
4133
|
+
|
|
4134
|
+
.deps-start-order .deps-order-title {
|
|
4135
|
+
font-weight: 600;
|
|
4136
|
+
color: var(--text-primary);
|
|
4137
|
+
margin-bottom: 0.35rem;
|
|
4138
|
+
}
|
|
4139
|
+
|
|
4140
|
+
.deps-start-order .deps-order-list {
|
|
4141
|
+
display: flex;
|
|
4142
|
+
flex-wrap: wrap;
|
|
4143
|
+
gap: 0.35rem;
|
|
4144
|
+
}
|
|
4145
|
+
|
|
4146
|
+
.deps-order-badge {
|
|
4147
|
+
display: inline-flex;
|
|
4148
|
+
align-items: center;
|
|
4149
|
+
gap: 0.3rem;
|
|
4150
|
+
padding: 0.2rem 0.5rem;
|
|
4151
|
+
background: rgba(var(--accent-rgb, 99, 102, 241), 0.15);
|
|
4152
|
+
border-radius: 6px;
|
|
4153
|
+
font-family: var(--font-mono);
|
|
4154
|
+
font-size: 0.75rem;
|
|
4155
|
+
}
|
|
4156
|
+
|
|
4157
|
+
.deps-order-badge .deps-order-num {
|
|
4158
|
+
color: var(--accent);
|
|
4159
|
+
font-weight: 700;
|
|
4160
|
+
font-size: 0.7rem;
|
|
4161
|
+
}
|
|
4162
|
+
|
|
4163
|
+
.deps-empty {
|
|
4164
|
+
text-align: center;
|
|
4165
|
+
padding: 2rem;
|
|
4166
|
+
color: var(--text-secondary);
|
|
4167
|
+
font-size: 0.9rem;
|
|
4168
|
+
}
|
|
4169
|
+
|
|
4170
|
+
@media (max-width: 768px) {
|
|
4171
|
+
.deps-controls {
|
|
4172
|
+
flex-direction: column;
|
|
4173
|
+
align-items: stretch;
|
|
4174
|
+
}
|
|
4175
|
+
.deps-arrow {
|
|
4176
|
+
text-align: center;
|
|
4177
|
+
}
|
|
4178
|
+
}
|