servicemap 0.1.23 → 0.1.24
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/package.json +1 -1
- package/servicemap/scripts/generate-page.mjs +101 -57
package/package.json
CHANGED
|
@@ -49,33 +49,64 @@ function escapeHtml(value) {
|
|
|
49
49
|
|
|
50
50
|
function pageCss() {
|
|
51
51
|
return `
|
|
52
|
-
:root { color-scheme:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
.servicemap-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
.
|
|
59
|
-
.
|
|
60
|
-
.
|
|
61
|
-
.
|
|
62
|
-
.
|
|
63
|
-
.
|
|
64
|
-
.
|
|
65
|
-
.
|
|
66
|
-
.
|
|
67
|
-
.
|
|
68
|
-
.
|
|
69
|
-
.
|
|
70
|
-
.service-
|
|
71
|
-
.
|
|
72
|
-
.
|
|
73
|
-
.
|
|
74
|
-
.
|
|
75
|
-
|
|
52
|
+
:root { color-scheme: dark; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
53
|
+
* { box-sizing: border-box; }
|
|
54
|
+
body { margin: 0; background: #020806; color: #eff8f2; }
|
|
55
|
+
.servicemap-shell { min-height: 100vh; overflow: hidden; background: #030806; }
|
|
56
|
+
.servicemap-topbar { height: 88px; display: flex; align-items: center; justify-content: space-between; gap: 18px; padding: 0 28px; border-bottom: 1px solid rgba(255,255,255,0.08); background: #222823; }
|
|
57
|
+
.brand { display: flex; align-items: center; gap: 16px; min-width: 0; }
|
|
58
|
+
.brand-mark { width: 48px; height: 48px; border-radius: 10px; border: 1px solid #2a7958; background: #164d39; color: #7cf1b5; display: grid; place-items: center; box-shadow: 0 0 26px rgba(82,255,166,0.16); font-size: 22px; }
|
|
59
|
+
.brand h1 { margin: 0; font-size: 20px; line-height: 1; letter-spacing: 0; }
|
|
60
|
+
.brand p, .top-meta { margin: 6px 0 0; color: #8aa097; font-size: 14px; }
|
|
61
|
+
.top-actions { display: flex; align-items: center; gap: 12px; color: #8da59a; font-size: 14px; white-space: nowrap; }
|
|
62
|
+
.mode-pill { border-radius: 8px; background: #0b130f; color: #7cf1b5; padding: 8px 12px; }
|
|
63
|
+
.map { position: relative; height: calc(100vh - 88px); min-height: 760px; background: radial-gradient(circle at 50% 52%, rgba(41, 96, 69, 0.22), transparent 24%), radial-gradient(circle at 1px 1px, rgba(88, 160, 120, 0.2) 1px, transparent 1px), #020806; background-size: auto, 28px 28px, auto; }
|
|
64
|
+
.intro { position: absolute; left: 32px; top: 32px; z-index: 20; }
|
|
65
|
+
.intro strong { display: block; color: #779286; font-size: 12px; letter-spacing: 0.22em; text-transform: uppercase; }
|
|
66
|
+
.intro span { display: block; margin-top: 8px; color: #53665d; font-size: 14px; }
|
|
67
|
+
.edges { position: absolute; inset: 0; width: 100%; height: 100%; }
|
|
68
|
+
.hub { position: absolute; left: 50%; top: 50%; z-index: 20; width: 132px; height: 132px; transform: translate(-50%, -50%); border-radius: 24px; border: 1px solid #315c47; background: #09120d; display: grid; place-items: center; box-shadow: 0 0 0 9px rgba(79,183,128,0.12), 0 0 42px rgba(100,255,174,0.16); }
|
|
69
|
+
.hub-inner { width: 92px; height: 92px; border-radius: 18px; background: #fbfbf7; color: #111612; display: grid; place-items: center; font-size: 40px; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08); }
|
|
70
|
+
.service-node { position: absolute; z-index: 10; width: 250px; height: 74px; transform: translate(-50%, -50%); display: flex; align-items: center; gap: 16px; padding: 0 16px; border-radius: 8px; border: 1px solid #274036; background: rgba(16,24,19,0.95); box-shadow: 0 0 0 1px rgba(140,255,190,0.05), 0 18px 44px rgba(0,0,0,0.38); }
|
|
71
|
+
.logo { width: 48px; height: 48px; flex: 0 0 auto; border-radius: 8px; display: grid; place-items: center; overflow: hidden; color: #fff; font-weight: 700; }
|
|
72
|
+
.logo img { width: 28px; height: 28px; object-fit: contain; filter: invert(1); }
|
|
73
|
+
.service-copy { min-width: 0; }
|
|
74
|
+
.service-copy strong { display: block; color: #f2faf5; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
75
|
+
.service-copy span { display: block; margin-top: 5px; color: #7f9188; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
76
|
+
.status-dot { margin-left: auto; width: 8px; height: 8px; flex: 0 0 auto; border-radius: 999px; box-shadow: 0 0 14px currentColor; }
|
|
77
|
+
.status-high { color: #65f0ad; background: #65f0ad; }
|
|
78
|
+
.status-medium, .status-low { color: #f1c85b; background: #f1c85b; }
|
|
79
|
+
.legend { position: absolute; left: 50%; bottom: 32px; z-index: 20; transform: translateX(-50%); display: flex; align-items: center; gap: 20px; border: 1px solid rgba(255,255,255,0.1); border-radius: 999px; background: rgba(11,20,15,0.9); color: #789086; padding: 8px 20px; font-size: 12px; }
|
|
80
|
+
.legend i { display: inline-block; width: 8px; height: 8px; margin-right: 8px; border-radius: 999px; }
|
|
81
|
+
.scan-foot { position: absolute; right: 32px; bottom: 32px; z-index: 20; color: #53665d; text-align: right; font-size: 12px; line-height: 1.6; }
|
|
82
|
+
.access-warning { position: absolute; left: 32px; right: 32px; bottom: 88px; z-index: 25; max-width: 720px; border: 1px solid rgba(241,200,91,0.42); border-radius: 8px; background: rgba(31,27,12,0.92); color: #f7daa0; padding: 12px 14px; font-size: 14px; line-height: 1.45; }
|
|
83
|
+
@media (max-width: 920px) {
|
|
84
|
+
.map { min-height: 1120px; }
|
|
85
|
+
.service-node { width: 220px; }
|
|
86
|
+
.top-actions { display: none; }
|
|
87
|
+
}
|
|
76
88
|
`;
|
|
77
89
|
}
|
|
78
90
|
|
|
91
|
+
const palette = ['#ff7a00', '#f48120', '#111827', '#6d5dfc', '#4285f4', '#5e5ce6', '#e5e7eb', '#7c3aed', '#24292f', '#22c55e'];
|
|
92
|
+
const layout = [
|
|
93
|
+
{ x: 17, y: 30 }, { x: 17, y: 55 }, { x: 35, y: 28 }, { x: 35, y: 72 }, { x: 25, y: 82 },
|
|
94
|
+
{ x: 59, y: 34 }, { x: 79, y: 23 }, { x: 79, y: 48 }, { x: 80, y: 64 }, { x: 62, y: 73 },
|
|
95
|
+
{ x: 19, y: 18 }, { x: 81, y: 82 }, { x: 41, y: 17 }, { x: 61, y: 18 }, { x: 43, y: 84 }, { x: 57, y: 84 },
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
function edgePath(node) {
|
|
99
|
+
const isLeft = node.x < 50;
|
|
100
|
+
const fromX = isLeft ? node.x + 8 : node.x - 8;
|
|
101
|
+
const endX = isLeft ? 47 : 53;
|
|
102
|
+
const midX = isLeft ? Math.max(fromX + 7, 42) : Math.min(fromX - 7, 58);
|
|
103
|
+
return `M ${fromX} ${node.y} H ${midX} V 50 H ${endX}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function initials(name) {
|
|
107
|
+
return name.split(/\s+/).filter(Boolean).slice(0, 2).map((part) => part[0]).join('').toUpperCase();
|
|
108
|
+
}
|
|
109
|
+
|
|
79
110
|
function htmlBody(graph) {
|
|
80
111
|
const services = graph.services ?? [];
|
|
81
112
|
const access = graph.access ?? {};
|
|
@@ -83,43 +114,56 @@ function htmlBody(graph) {
|
|
|
83
114
|
const accessNotice = hasAccessRequest && access.enforced !== true
|
|
84
115
|
? '<div class="access-warning">Email access was requested, but this project does not expose a supported server-side auth guard for generated pages. Put this route behind your app auth before sharing it.</div>'
|
|
85
116
|
: '';
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
`;
|
|
103
|
-
}).join('')
|
|
104
|
-
: '<div class="empty">No external services were detected yet.</div>';
|
|
117
|
+
const nodes = services.slice(0, layout.length).map((service, index) => ({
|
|
118
|
+
...service,
|
|
119
|
+
...layout[index],
|
|
120
|
+
color: palette[index % palette.length],
|
|
121
|
+
}));
|
|
122
|
+
const edges = nodes.map((node) => `<path d="${edgePath(node)}" fill="none" stroke="rgba(103, 130, 118, 0.48)" stroke-width="0.16" vector-effect="non-scaling-stroke"></path>`).join('');
|
|
123
|
+
const serviceNodes = nodes.map((service) => `
|
|
124
|
+
<article class="service-node" style="left:${service.x}%;top:${service.y}%">
|
|
125
|
+
<div class="logo" style="background:${service.color}">${service.iconUrl ? `<img src="${escapeHtml(service.iconUrl)}" alt="">` : escapeHtml(initials(service.name))}</div>
|
|
126
|
+
<div class="service-copy">
|
|
127
|
+
<strong>${escapeHtml(service.name)}</strong>
|
|
128
|
+
<span>${escapeHtml(service.category)}</span>
|
|
129
|
+
</div>
|
|
130
|
+
<i class="status-dot status-${escapeHtml(service.confidence)}"></i>
|
|
131
|
+
</article>
|
|
132
|
+
`).join('');
|
|
105
133
|
|
|
106
134
|
return `
|
|
107
135
|
<main class="servicemap-shell">
|
|
108
|
-
<
|
|
109
|
-
<div>
|
|
110
|
-
<
|
|
111
|
-
<
|
|
136
|
+
<header class="servicemap-topbar">
|
|
137
|
+
<div class="brand">
|
|
138
|
+
<div class="brand-mark">⌘</div>
|
|
139
|
+
<div>
|
|
140
|
+
<h1>servicemap</h1>
|
|
141
|
+
<p>${escapeHtml(graph.project?.name ?? 'Project')} command center</p>
|
|
142
|
+
</div>
|
|
112
143
|
</div>
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
144
|
+
<div class="top-actions">
|
|
145
|
+
<span>${services.length} services</span>
|
|
146
|
+
<span class="mode-pill">2D</span>
|
|
147
|
+
</div>
|
|
148
|
+
</header>
|
|
149
|
+
<section class="map">
|
|
150
|
+
<div class="intro">
|
|
151
|
+
<strong>${escapeHtml(graph.project?.name ?? 'Project')} infrastructure</strong>
|
|
152
|
+
<span>Select a service to trace its dependencies</span>
|
|
153
|
+
</div>
|
|
154
|
+
<svg class="edges" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">${edges}</svg>
|
|
155
|
+
<div class="hub"><div class="hub-inner">⌘</div></div>
|
|
156
|
+
${serviceNodes || '<div class="intro"><span>No external services were detected yet.</span></div>'}
|
|
157
|
+
${accessNotice}
|
|
158
|
+
<div class="legend">
|
|
159
|
+
<span><i style="background:#4a7cff"></i>Cloud</span>
|
|
160
|
+
<span><i style="background:#8be4b2"></i>Product</span>
|
|
161
|
+
<span><i style="background:#65f0ad"></i>Active path</span>
|
|
162
|
+
</div>
|
|
163
|
+
<footer class="scan-foot">
|
|
164
|
+
<div>Generated ${escapeHtml(new Date(graph.generatedAt).toLocaleString())}</div>
|
|
165
|
+
<div>${services.length} services detected</div>
|
|
166
|
+
</footer>
|
|
123
167
|
</section>
|
|
124
168
|
</main>
|
|
125
169
|
`;
|