1pub fn dashboard_html() -> &'static str {
2 r##"<!DOCTYPE html>
3<html lang="en">
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; script-src 'none'; object-src 'none'; base-uri 'self';">
8 <title>Rullst ORM - Admin Panel</title>
9 <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&display=swap" rel="stylesheet">
10 <style>
11 :root {
12 --bg-color: #0d1117;
13 --surface-color: rgba(30, 35, 45, 0.6);
14 --border-color: rgba(255, 255, 255, 0.1);
15 --primary: #f9322c;
16 --primary-hover: #ff4c46;
17 --text-main: #ffffff;
18 --text-muted: #8b949e;
19 }
20
21 * {
22 box-sizing: border-box;
23 margin: 0;
24 padding: 0;
25 font-family: 'Outfit', sans-serif;
26 }
27
28 body {
29 background-color: var(--bg-color);
30 color: var(--text-main);
31 min-height: 100vh;
32 display: flex;
33 background-image:
34 radial-gradient(circle at 15% 50%, rgba(249, 50, 44, 0.08), transparent 25%),
35 radial-gradient(circle at 85% 30%, rgba(50, 100, 255, 0.05), transparent 25%);
36 }
37
38 .sidebar {
39 width: 260px;
40 background: var(--surface-color);
41 backdrop-filter: blur(12px);
42 border-right: 1px solid var(--border-color);
43 padding: 2rem 1rem;
44 display: flex;
45 flex-direction: column;
46 gap: 2rem;
47 }
48
49 .brand {
50 display: flex;
51 align-items: center;
52 gap: 12px;
53 padding: 0 1rem;
54 }
55
56 .brand-icon {
57 width: 32px;
58 height: 32px;
59 background: linear-gradient(135deg, var(--primary), #ff7e5f);
60 border-radius: 8px;
61 display: grid;
62 place-items: center;
63 font-weight: 800;
64 font-size: 1.2rem;
65 box-shadow: 0 4px 15px rgba(249, 50, 44, 0.3);
66 }
67
68 .brand-text {
69 font-size: 1.4rem;
70 font-weight: 800;
71 letter-spacing: -0.5px;
72 background: linear-gradient(90deg, #fff, #bbb);
73 -webkit-background-clip: text;
74 -webkit-text-fill-color: transparent;
75 }
76
77 .nav-link {
78 display: flex;
79 align-items: center;
80 gap: 12px;
81 padding: 0.8rem 1rem;
82 color: var(--text-muted);
83 text-decoration: none;
84 border-radius: 8px;
85 transition: all 0.3s ease;
86 font-weight: 600;
87 }
88
89 .nav-link:hover, .nav-link.active {
90 background: rgba(255, 255, 255, 0.05);
91 color: var(--text-main);
92 transform: translateX(4px);
93 }
94
95 .main-content {
96 flex: 1;
97 padding: 3rem;
98 overflow-y: auto;
99 }
100
101 .header {
102 display: flex;
103 justify-content: space-between;
104 align-items: center;
105 margin-bottom: 3rem;
106 }
107
108 .header h1 {
109 font-size: 2.5rem;
110 font-weight: 800;
111 letter-spacing: -1px;
112 }
113
114 .stats-grid {
115 display: grid;
116 grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
117 gap: 1.5rem;
118 margin-bottom: 3rem;
119 }
120
121 .stat-card {
122 background: var(--surface-color);
123 backdrop-filter: blur(12px);
124 border: 1px solid var(--border-color);
125 padding: 1.5rem;
126 border-radius: 16px;
127 transition: transform 0.3s ease, box-shadow 0.3s ease;
128 position: relative;
129 overflow: hidden;
130 }
131
132 .stat-card:hover {
133 transform: translateY(-5px);
134 box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
135 border-color: rgba(249, 50, 44, 0.3);
136 }
137
138 .stat-card::before {
139 content: '';
140 position: absolute;
141 top: 0; left: 0; width: 100%; height: 4px;
142 background: linear-gradient(90deg, var(--primary), #ff7e5f);
143 opacity: 0;
144 transition: opacity 0.3s ease;
145 }
146
147 .stat-card:hover::before { opacity: 1; }
148
149 .stat-title {
150 color: var(--text-muted);
151 font-size: 0.9rem;
152 text-transform: uppercase;
153 letter-spacing: 1px;
154 font-weight: 600;
155 margin-bottom: 0.5rem;
156 }
157
158 .stat-value {
159 font-size: 2.5rem;
160 font-weight: 800;
161 }
162
163 .table-container {
164 background: var(--surface-color);
165 backdrop-filter: blur(12px);
166 border: 1px solid var(--border-color);
167 border-radius: 16px;
168 overflow: hidden;
169 animation: fadeIn 0.8s ease-out;
170 }
171
172 table {
173 width: 100%;
174 border-collapse: collapse;
175 }
176
177 th, td {
178 padding: 1.2rem;
179 text-align: left;
180 border-bottom: 1px solid var(--border-color);
181 }
182
183 th {
184 background: rgba(0,0,0,0.2);
185 color: var(--text-muted);
186 font-weight: 600;
187 font-size: 0.85rem;
188 text-transform: uppercase;
189 letter-spacing: 1px;
190 }
191
192 tr { transition: background 0.2s ease; }
193 tr:hover { background: rgba(255,255,255,0.02); }
194
195 .badge {
196 padding: 4px 12px;
197 border-radius: 20px;
198 font-size: 0.8rem;
199 font-weight: 600;
200 background: rgba(46, 160, 67, 0.15);
201 color: #3fb950;
202 border: 1px solid rgba(46, 160, 67, 0.3);
203 }
204
205 @keyframes fadeIn {
206 from { opacity: 0; transform: translateY(20px); }
207 to { opacity: 1; transform: translateY(0); }
208 }
209 </style>
210</head>
211<body>
212
213 <div class="sidebar">
214 <div class="brand">
215 <div class="brand-icon">R</div>
216 <div class="brand-text">Rullst Admin</div>
217 </div>
218 <nav>
219 <a href="#" class="nav-link active">Dashboard</a>
220 <a href="#" class="nav-link">Models / Tables</a>
221 <a href="#" class="nav-link">Audit Logs</a>
222 <a href="#" class="nav-link">Settings</a>
223 </nav>
224 </div>
225
226 <div class="main-content">
227 <div class="header">
228 <h1>Database Overview</h1>
229 <p style="color: var(--text-muted)">Welcome back, Admin.</p>
230 </div>
231
232 <div class="stats-grid">
233 <div class="stat-card">
234 <div class="stat-title">Total Records</div>
235 <div class="stat-value">14,293</div>
236 </div>
237 <div class="stat-card">
238 <div class="stat-title">Active Models</div>
239 <div class="stat-value">12</div>
240 </div>
241 <div class="stat-card">
242 <div class="stat-title">Recent Audits</div>
243 <div class="stat-value">342</div>
244 </div>
245 </div>
246
247 <div class="table-container">
248 <table>
249 <thead>
250 <tr>
251 <th>Model Name</th>
252 <th>Table</th>
253 <th>Status</th>
254 <th>Actions</th>
255 </tr>
256 </thead>
257 <tbody>
258 <tr>
259 <td><strong>User</strong></td>
260 <td style="color: var(--text-muted)">users</td>
261 <td><span class="badge">Healthy</span></td>
262 <td><a href="#" style="color: var(--primary); text-decoration: none; font-weight: 600;">View Data</a></td>
263 </tr>
264 <tr>
265 <td><strong>Document</strong></td>
266 <td style="color: var(--text-muted)">documents</td>
267 <td><span class="badge">Healthy</span></td>
268 <td><a href="#" style="color: var(--primary); text-decoration: none; font-weight: 600;">View Data</a></td>
269 </tr>
270 <tr>
271 <td><strong>Tenant</strong></td>
272 <td style="color: var(--text-muted)">tenants</td>
273 <td><span class="badge">Healthy</span></td>
274 <td><a href="#" style="color: var(--primary); text-decoration: none; font-weight: 600;">View Data</a></td>
275 </tr>
276 </tbody>
277 </table>
278 </div>
279 </div>
280
281</body>
282</html>"##
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288
289 #[test]
290 fn test_dashboard_html() {
291 let html = dashboard_html();
292 assert!(html.contains("<!DOCTYPE html>"));
293 assert!(html.contains("Rullst ORM - Admin Panel"));
294 assert!(html.contains("Database Overview"));
295 }
296}