Skip to main content

rullst_orm/
admin.rs

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}