ghcr-manager-visualizer 0.0.1-dev.0
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/README.md +45 -0
- package/dist/public/app.js +839 -0
- package/dist/public/index.html +116 -0
- package/dist/public/styles.css +235 -0
- package/dist/src/_graph-repository.d.ts +18 -0
- package/dist/src/_graph-repository.js +399 -0
- package/dist/src/_server.d.ts +14 -0
- package/dist/src/_server.js +158 -0
- package/dist/src/_sql-placeholders.d.ts +1 -0
- package/dist/src/_sql-placeholders.js +6 -0
- package/dist/src/_types.d.ts +62 -0
- package/dist/src/_types.js +1 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.js +60 -0
- package/package.json +47 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>ghcr-manager visualizer</title>
|
|
7
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main class="layout">
|
|
11
|
+
<section class="controls">
|
|
12
|
+
<h1>ghcr-manager visualizer</h1>
|
|
13
|
+
<form id="search-form" autocomplete="off">
|
|
14
|
+
<label>
|
|
15
|
+
Owner
|
|
16
|
+
<select id="owner" name="owner" required>
|
|
17
|
+
<option value="">Select owner</option>
|
|
18
|
+
</select>
|
|
19
|
+
</label>
|
|
20
|
+
<label>
|
|
21
|
+
Package
|
|
22
|
+
<select id="package" name="package" required disabled>
|
|
23
|
+
<option value="">Select package</option>
|
|
24
|
+
</select>
|
|
25
|
+
</label>
|
|
26
|
+
<label>
|
|
27
|
+
Scan ID
|
|
28
|
+
<select id="scan-id" name="scan_id" disabled>
|
|
29
|
+
<option value="">Latest completed scan</option>
|
|
30
|
+
</select>
|
|
31
|
+
</label>
|
|
32
|
+
<label>
|
|
33
|
+
Compare Scan ID
|
|
34
|
+
<select id="compare-scan-id" name="compare_scan_id" disabled>
|
|
35
|
+
<option value="">None</option>
|
|
36
|
+
</select>
|
|
37
|
+
</label>
|
|
38
|
+
<label>
|
|
39
|
+
Lookup
|
|
40
|
+
<select id="lookup-mode" name="lookup_mode">
|
|
41
|
+
<option value="tag">Tag</option>
|
|
42
|
+
<option value="digest">Digest</option>
|
|
43
|
+
</select>
|
|
44
|
+
</label>
|
|
45
|
+
<label>
|
|
46
|
+
Value
|
|
47
|
+
<input id="lookup-value" name="lookup_value" list="lookup-suggestions" autocomplete="off" required />
|
|
48
|
+
</label>
|
|
49
|
+
<label>
|
|
50
|
+
Depth
|
|
51
|
+
<input id="depth" name="depth" type="number" min="0" max="30" value="5" required />
|
|
52
|
+
</label>
|
|
53
|
+
<button type="submit">Load graph</button>
|
|
54
|
+
</form>
|
|
55
|
+
<div class="legend">
|
|
56
|
+
<h2>Edges</h2>
|
|
57
|
+
<p><span class="legend-line solid"></span> image-child</p>
|
|
58
|
+
<p><span class="legend-line dashed"></span> referrer</p>
|
|
59
|
+
<p><span class="legend-line dotted"></span> digest-tag-referrer</p>
|
|
60
|
+
</div>
|
|
61
|
+
<div id="status" class="status"></div>
|
|
62
|
+
</section>
|
|
63
|
+
<section class="graph-panel">
|
|
64
|
+
<div class="graph-toolbar" aria-label="Graph controls">
|
|
65
|
+
<button id="zoom-in" type="button" title="Zoom in">+</button>
|
|
66
|
+
<button id="zoom-out" type="button" title="Zoom out">-</button>
|
|
67
|
+
<button id="zoom-fit" type="button" title="Fit graph">Fit</button>
|
|
68
|
+
</div>
|
|
69
|
+
<div id="graph"></div>
|
|
70
|
+
</section>
|
|
71
|
+
<aside class="details">
|
|
72
|
+
<h2>Manifest</h2>
|
|
73
|
+
<div id="details-empty">Select a manifest.</div>
|
|
74
|
+
<dl id="details" hidden>
|
|
75
|
+
<dt>Actions</dt>
|
|
76
|
+
<dd>
|
|
77
|
+
<div class="detail-actions">
|
|
78
|
+
<button id="expand-node" type="button" disabled>Expand 1 hop</button>
|
|
79
|
+
<button id="center-node" type="button" disabled>Center here</button>
|
|
80
|
+
<button id="show-raw-json" type="button" disabled>View raw JSON</button>
|
|
81
|
+
</div>
|
|
82
|
+
</dd>
|
|
83
|
+
<dt>Digest</dt>
|
|
84
|
+
<dd id="detail-digest"></dd>
|
|
85
|
+
<dt>Version</dt>
|
|
86
|
+
<dd id="detail-version"></dd>
|
|
87
|
+
<dt>Created</dt>
|
|
88
|
+
<dd id="detail-created-at"></dd>
|
|
89
|
+
<dt>Updated</dt>
|
|
90
|
+
<dd id="detail-updated-at"></dd>
|
|
91
|
+
<dt>Kind</dt>
|
|
92
|
+
<dd id="detail-kind"></dd>
|
|
93
|
+
<dt>Media Type</dt>
|
|
94
|
+
<dd id="detail-media-type"></dd>
|
|
95
|
+
<dt>Platform</dt>
|
|
96
|
+
<dd id="detail-platform"></dd>
|
|
97
|
+
<dt>Artifact Type</dt>
|
|
98
|
+
<dd id="detail-artifact-type"></dd>
|
|
99
|
+
<dt>Subject</dt>
|
|
100
|
+
<dd id="detail-subject"></dd>
|
|
101
|
+
<dt>Tags</dt>
|
|
102
|
+
<dd id="detail-tags"></dd>
|
|
103
|
+
</dl>
|
|
104
|
+
</aside>
|
|
105
|
+
</main>
|
|
106
|
+
<datalist id="lookup-suggestions"></datalist>
|
|
107
|
+
<dialog id="raw-json-dialog" class="raw-json-dialog">
|
|
108
|
+
<div class="raw-json-dialog__header">
|
|
109
|
+
<h2>Raw JSON</h2>
|
|
110
|
+
<button id="close-raw-json" type="button">Close</button>
|
|
111
|
+
</div>
|
|
112
|
+
<pre id="raw-json-content"></pre>
|
|
113
|
+
</dialog>
|
|
114
|
+
<script type="module" src="/app.js"></script>
|
|
115
|
+
</body>
|
|
116
|
+
</html>
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
body {
|
|
2
|
+
margin: 0;
|
|
3
|
+
font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
|
|
4
|
+
background: linear-gradient(180deg, #edf2f0 0%, #dde7e2 100%);
|
|
5
|
+
color: #112218;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.layout {
|
|
9
|
+
display: grid;
|
|
10
|
+
grid-template-columns: 20rem 1fr 24rem;
|
|
11
|
+
min-height: 100vh;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.controls,
|
|
15
|
+
.details {
|
|
16
|
+
padding: 1rem;
|
|
17
|
+
background: rgba(255, 255, 255, 0.78);
|
|
18
|
+
backdrop-filter: blur(8px);
|
|
19
|
+
min-width: 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.controls {
|
|
23
|
+
border-right: 1px solid #b7c8bf;
|
|
24
|
+
position: relative;
|
|
25
|
+
z-index: 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.details {
|
|
29
|
+
border-left: 1px solid #b7c8bf;
|
|
30
|
+
overflow: auto;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.graph-panel {
|
|
34
|
+
position: relative;
|
|
35
|
+
min-width: 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.graph-toolbar {
|
|
39
|
+
position: absolute;
|
|
40
|
+
top: 1rem;
|
|
41
|
+
right: 1rem;
|
|
42
|
+
display: flex;
|
|
43
|
+
gap: 0.5rem;
|
|
44
|
+
z-index: 2;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.graph-toolbar button {
|
|
48
|
+
min-width: 3rem;
|
|
49
|
+
background: rgba(29, 79, 58, 0.92);
|
|
50
|
+
border-color: #1d4f3a;
|
|
51
|
+
box-shadow: 0 0.2rem 0.8rem rgba(16, 32, 23, 0.18);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#graph {
|
|
55
|
+
width: 100%;
|
|
56
|
+
height: 100vh;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
form {
|
|
60
|
+
display: grid;
|
|
61
|
+
gap: 0.75rem;
|
|
62
|
+
min-width: 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
label {
|
|
66
|
+
display: grid;
|
|
67
|
+
gap: 0.35rem;
|
|
68
|
+
font-size: 0.95rem;
|
|
69
|
+
min-width: 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
input,
|
|
73
|
+
select,
|
|
74
|
+
button {
|
|
75
|
+
font: inherit;
|
|
76
|
+
padding: 0.5rem 0.65rem;
|
|
77
|
+
border-radius: 0.5rem;
|
|
78
|
+
border: 1px solid #94a99d;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.controls input,
|
|
82
|
+
.controls select,
|
|
83
|
+
.controls form > button {
|
|
84
|
+
width: 100%;
|
|
85
|
+
max-width: 100%;
|
|
86
|
+
box-sizing: border-box;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
button {
|
|
90
|
+
cursor: pointer;
|
|
91
|
+
background: #1d4f3a;
|
|
92
|
+
color: #fff;
|
|
93
|
+
border-color: #1d4f3a;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.detail-actions {
|
|
97
|
+
display: flex;
|
|
98
|
+
gap: 0.5rem;
|
|
99
|
+
flex-wrap: wrap;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.detail-actions button {
|
|
103
|
+
background: #325745;
|
|
104
|
+
border-color: #325745;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.raw-json-dialog {
|
|
108
|
+
width: min(70rem, calc(100vw - 4rem));
|
|
109
|
+
height: min(80vh, 52rem);
|
|
110
|
+
border: 1px solid #94a99d;
|
|
111
|
+
border-radius: 0.75rem;
|
|
112
|
+
padding: 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.raw-json-dialog::backdrop {
|
|
116
|
+
background: rgba(16, 32, 23, 0.45);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.raw-json-dialog__header {
|
|
120
|
+
display: flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
justify-content: space-between;
|
|
123
|
+
gap: 1rem;
|
|
124
|
+
padding: 1rem 1.25rem;
|
|
125
|
+
border-bottom: 1px solid #d1ddd6;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.raw-json-dialog__header h2 {
|
|
129
|
+
margin: 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.raw-json-dialog__header button {
|
|
133
|
+
background: #325745;
|
|
134
|
+
border-color: #325745;
|
|
135
|
+
color: #fff;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#raw-json-content {
|
|
139
|
+
margin: 0;
|
|
140
|
+
padding: 1rem 1.25rem 1.5rem;
|
|
141
|
+
height: calc(100% - 4.5rem);
|
|
142
|
+
overflow: auto;
|
|
143
|
+
white-space: pre;
|
|
144
|
+
word-break: normal;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.status {
|
|
148
|
+
margin-top: 1rem;
|
|
149
|
+
min-height: 1.5rem;
|
|
150
|
+
font-size: 0.95rem;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.legend {
|
|
154
|
+
margin-top: 1.5rem;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.legend-line {
|
|
158
|
+
display: inline-block;
|
|
159
|
+
width: 2.5rem;
|
|
160
|
+
border-top: 3px solid #3d5a4a;
|
|
161
|
+
margin-right: 0.5rem;
|
|
162
|
+
vertical-align: middle;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.legend-line.dashed {
|
|
166
|
+
border-top-style: dashed;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.legend-line.dotted {
|
|
170
|
+
border-top-style: dotted;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
dl {
|
|
174
|
+
margin: 0;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
dt {
|
|
178
|
+
font-weight: 700;
|
|
179
|
+
margin-top: 0.9rem;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
dd {
|
|
183
|
+
margin: 0.2rem 0 0;
|
|
184
|
+
word-break: break-word;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.tag-list {
|
|
188
|
+
display: flex;
|
|
189
|
+
flex-wrap: wrap;
|
|
190
|
+
gap: 0.35rem 0.5rem;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.tag {
|
|
194
|
+
display: inline-block;
|
|
195
|
+
padding: 0.1rem 0.35rem;
|
|
196
|
+
border-radius: 999px;
|
|
197
|
+
background: #e7eeea;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.tag.added {
|
|
201
|
+
color: #1f6f2c;
|
|
202
|
+
background: #d9f0dc;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.tag.removed {
|
|
206
|
+
color: #8d1f17;
|
|
207
|
+
background: #f6dbd8;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
pre {
|
|
211
|
+
margin: 0;
|
|
212
|
+
white-space: pre-wrap;
|
|
213
|
+
word-break: break-word;
|
|
214
|
+
font-size: 0.85rem;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
@media (max-width: 1100px) {
|
|
218
|
+
.layout {
|
|
219
|
+
grid-template-columns: 1fr;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.controls,
|
|
223
|
+
.details {
|
|
224
|
+
border: 0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.graph-toolbar {
|
|
228
|
+
top: 0.75rem;
|
|
229
|
+
right: 0.75rem;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#graph {
|
|
233
|
+
height: 60vh;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
import type { GraphResponse, ManifestDetails, ManifestResolution, OwnerOption, PackageOption, ScanOption, TagOption } from "./_types.js";
|
|
3
|
+
export declare class GraphRepository {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(database: Database.Database);
|
|
6
|
+
listOwners(): OwnerOption[];
|
|
7
|
+
listPackages(owner: string): PackageOption[];
|
|
8
|
+
listScans(owner: string, packageName: string): ScanOption[];
|
|
9
|
+
listTags(owner: string, packageName: string, scanId: number | undefined, compareScanId: number | undefined, query: string, limit: number): TagOption[];
|
|
10
|
+
resolveLatestScanId(owner: string, packageName: string): number;
|
|
11
|
+
resolveScanId(owner: string, packageName: string, scanId: number | undefined): number;
|
|
12
|
+
resolveManifest(owner: string, packageName: string, scanId: number | undefined, compareScanId: number | undefined, args: {
|
|
13
|
+
digest?: string;
|
|
14
|
+
tag?: string;
|
|
15
|
+
}): ManifestResolution;
|
|
16
|
+
getManifest(owner: string, packageName: string, scanId: number | undefined, compareScanId: number | undefined, digest: string): ManifestDetails;
|
|
17
|
+
getGraph(owner: string, packageName: string, scanId: number | undefined, compareScanId: number | undefined, centerDigest: string, depth: number): GraphResponse;
|
|
18
|
+
}
|