main
tsx 280 lines 6.68 KB
Raw
1 import React from 'react';
2 import {
3 AbsoluteFill,
4 Img,
5 interpolate,
6 spring,
7 staticFile,
8 useCurrentFrame,
9 useVideoConfig,
10 } from 'remotion';
11
12 const BG = '#0b0912';
13 const PRIMARY = '#f85d3b';
14 const ACCENT = '#884fff';
15
16 const Caption: React.FC<{title: string; body: string; color?: string}> = ({
17 title,
18 body,
19 color = PRIMARY,
20 }) => {
21 return (
22 <div
23 style={{
24 position: 'absolute',
25 left: 40,
26 right: 40,
27 bottom: 36,
28 padding: '18px 18px 16px',
29 borderRadius: 18,
30 background: 'rgba(255,255,255,0.04)',
31 border: '1px solid rgba(255,255,255,0.10)',
32 backdropFilter: 'blur(10px)',
33 fontFamily:
34 'IBM Plex Sans, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial',
35 }}
36 >
37 <div style={{fontSize: 20, fontWeight: 800, color: 'rgba(255,255,255,0.94)'}}>
38 <span style={{color}}>{title}</span>
39 </div>
40 <div style={{marginTop: 6, fontSize: 15.5, lineHeight: 1.35, color: 'rgba(255,255,255,0.72)'}}>
41 {body}
42 </div>
43 </div>
44 );
45 };
46
47 const GlowBox: React.FC<{
48 x: number;
49 y: number;
50 w: number;
51 h: number;
52 color: string;
53 label?: string;
54 progress: number;
55 }> = ({x, y, w, h, color, label, progress}) => {
56 const opacity = interpolate(progress, [0, 1], [0, 1]);
57 const scale = interpolate(progress, [0, 1], [0.98, 1]);
58 return (
59 <div
60 style={{
61 position: 'absolute',
62 left: x,
63 top: y,
64 width: w,
65 height: h,
66 borderRadius: 18,
67 border: `3px solid ${color}`,
68 boxShadow: `0 0 18px ${color}88, 0 0 40px ${color}44`,
69 opacity,
70 transform: `scale(${scale})`,
71 }}
72 >
73 {label ? (
74 <div
75 style={{
76 position: 'absolute',
77 left: 12,
78 top: -34,
79 padding: '6px 10px',
80 borderRadius: 999,
81 background: 'rgba(0,0,0,0.55)',
82 border: `1px solid ${color}66`,
83 color: 'rgba(255,255,255,0.92)',
84 fontSize: 14,
85 fontWeight: 700,
86 backdropFilter: 'blur(6px)',
87 }}
88 >
89 {label}
90 </div>
91 ) : null}
92 </div>
93 );
94 };
95
96 const Slide: React.FC<{
97 src: string;
98 zoomFrom: number;
99 zoomTo: number;
100 offsetX?: number;
101 offsetY?: number;
102 from: number;
103 to: number;
104 children?: React.ReactNode;
105 }> = ({src, zoomFrom, zoomTo, offsetX = 0, offsetY = 0, from, to, children}) => {
106 const frame = useCurrentFrame();
107 const local = Math.min(Math.max(frame - from, 0), to - from);
108 const dur = Math.max(1, to - from);
109 const t = local / dur;
110 const zoom = interpolate(t, [0, 1], [zoomFrom, zoomTo]);
111 const fadeIn = interpolate(t, [0, 0.08], [0, 1]);
112 const fadeOut = interpolate(t, [0.92, 1], [1, 0]);
113 const opacity = Math.min(fadeIn, fadeOut);
114
115 return (
116 <AbsoluteFill style={{opacity}}>
117 <AbsoluteFill
118 style={{
119 transform: `translate(${offsetX}px, ${offsetY}px) scale(${zoom})`,
120 transformOrigin: '50% 50%',
121 }}
122 >
123 <Img src={src} style={{width: '100%', height: '100%', objectFit: 'cover'}} />
124 </AbsoluteFill>
125 {children}
126 </AbsoluteFill>
127 );
128 };
129
130 export const CustomerProvisioningWalkthrough: React.FC = () => {
131 const frame = useCurrentFrame();
132 const {fps} = useVideoConfig();
133
134 // Frame ranges
135 const s1 = {from: 0, to: 80};
136 const s2 = {from: 80, to: 160};
137 const s3 = {from: 160, to: 240};
138
139 const p1 = spring({frame: frame - s1.from, fps, config: {damping: 18, stiffness: 120}});
140 const p2 = spring({frame: frame - s2.from, fps, config: {damping: 18, stiffness: 120}});
141 const p3 = spring({frame: frame - s3.from, fps, config: {damping: 18, stiffness: 120}});
142
143 return (
144 <AbsoluteFill style={{backgroundColor: BG}}>
145 {/* Background glow */}
146 <AbsoluteFill
147 style={{
148 background:
149 `radial-gradient(900px 500px at 20% 30%, rgba(248,93,59,0.12), transparent 60%),` +
150 `radial-gradient(900px 500px at 80% 70%, rgba(136,79,255,0.14), transparent 60%)`,
151 }}
152 />
153
154 {/* Slide 1: Customers list + Add Customer */}
155 <Slide
156 src={staticFile('ui/customers.png')}
157 zoomFrom={1.0}
158 zoomTo={1.08}
159 offsetX={-40}
160 offsetY={-10}
161 from={s1.from}
162 to={s1.to}
163 >
164 <GlowBox
165 x={1180}
166 y={120}
167 w={350}
168 h={90}
169 color={PRIMARY}
170 label="Add Customer"
171 progress={p1}
172 />
173 <Caption
174 title="1) Create the Customer"
175 body="Start in Customers and add a new customer. The customer code becomes the tenancy key used across provisioning and per-customer integrations."
176 color={PRIMARY}
177 />
178 </Slide>
179
180 {/* Slide 2: Provision tab */}
181 <Slide
182 src={staticFile('ui/customer-tab-provision.png')}
183 zoomFrom={1.0}
184 zoomTo={1.06}
185 offsetX={0}
186 offsetY={-10}
187 from={s2.from}
188 to={s2.to}
189 >
190 <GlowBox
191 x={170}
192 y={155}
193 w={190}
194 h={70}
195 color={ACCENT}
196 label="Provision"
197 progress={p2}
198 />
199 <Caption
200 title="2) Provision Tenancy (Wazuh + Graylog + Grafana)"
201 body="Provisioning creates the customer’s dedicated index/stream routing, Wazuh agent groups, and Grafana org + default dashboards so data and visuals land correctly from day one."
202 color={ACCENT}
203 />
204 </Slide>
205
206 {/* Slide 3: 3rd party integrations + network connectors */}
207 <Slide
208 src={staticFile('ui/customer-tab-3rd-party-integrations.png')}
209 zoomFrom={1.0}
210 zoomTo={1.04}
211 offsetX={0}
212 offsetY={-10}
213 from={s3.from}
214 to={s3.to}
215 >
216 <GlowBox
217 x={290}
218 y={155}
219 w={300}
220 h={70}
221 color={PRIMARY}
222 label="3rd Party Integrations"
223 progress={p3}
224 />
225 <GlowBox
226 x={610}
227 y={155}
228 w={260}
229 h={70}
230 color={ACCENT}
231 label="Network Connectors"
232 progress={p3}
233 />
234
235 {/* Show Network Connectors as a small inset preview */}
236 <div
237 style={{
238 position: 'absolute',
239 right: 44,
240 top: 260,
241 width: 520,
242 borderRadius: 18,
243 overflow: 'hidden',
244 border: '1px solid rgba(255,255,255,0.10)',
245 boxShadow: `0 0 28px ${ACCENT}33`,
246 opacity: interpolate(p3, [0, 1], [0, 1]),
247 }}
248 >
249 <Img
250 src={staticFile('ui/customer-tab-network-connectors.png')}
251 style={{width: '100%', height: '100%', objectFit: 'cover'}}
252 />
253 <div
254 style={{
255 position: 'absolute',
256 left: 14,
257 top: 14,
258 padding: '6px 10px',
259 borderRadius: 999,
260 background: 'rgba(0,0,0,0.55)',
261 border: `1px solid ${ACCENT}66`,
262 color: 'rgba(255,255,255,0.92)',
263 fontSize: 14,
264 fontWeight: 800,
265 backdropFilter: 'blur(6px)',
266 }}
267 >
268 Network Connectors
269 </div>
270 </div>
271
272 <Caption
273 title="3) Enable per-customer integrations"
274 body="After provisioning, configure per-customer 3rd-party integrations (e.g., Office 365, Mimecast) and network connectors (firewalls/network devices). These are customer-scoped so data routes into the right tenant automatically."
275 color={PRIMARY}
276 />
277 </Slide>
278 </AbsoluteFill>
279 );
280 };