@qwickapps/server 1.1.9 → 1.3.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.
Files changed (240) hide show
  1. package/README.md +318 -0
  2. package/dist/core/control-panel.d.ts +7 -2
  3. package/dist/core/control-panel.d.ts.map +1 -1
  4. package/dist/core/control-panel.js +99 -60
  5. package/dist/core/control-panel.js.map +1 -1
  6. package/dist/core/gateway.d.ts +159 -79
  7. package/dist/core/gateway.d.ts.map +1 -1
  8. package/dist/core/gateway.js +683 -315
  9. package/dist/core/gateway.js.map +1 -1
  10. package/dist/core/index.d.ts +3 -1
  11. package/dist/core/index.d.ts.map +1 -1
  12. package/dist/core/index.js +2 -0
  13. package/dist/core/index.js.map +1 -1
  14. package/dist/core/plugin-registry.d.ts +271 -0
  15. package/dist/core/plugin-registry.d.ts.map +1 -0
  16. package/dist/core/plugin-registry.js +326 -0
  17. package/dist/core/plugin-registry.js.map +1 -0
  18. package/dist/core/types.d.ts +16 -33
  19. package/dist/core/types.d.ts.map +1 -1
  20. package/dist/index.d.ts +8 -5
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +15 -7
  23. package/dist/index.js.map +1 -1
  24. package/dist/plugins/auth/adapters/auth0-adapter.d.ts +14 -0
  25. package/dist/plugins/auth/adapters/auth0-adapter.d.ts.map +1 -0
  26. package/dist/plugins/auth/adapters/auth0-adapter.js +179 -0
  27. package/dist/plugins/auth/adapters/auth0-adapter.js.map +1 -0
  28. package/dist/plugins/auth/adapters/basic-adapter.d.ts +13 -0
  29. package/dist/plugins/auth/adapters/basic-adapter.d.ts.map +1 -0
  30. package/dist/plugins/auth/adapters/basic-adapter.js +51 -0
  31. package/dist/plugins/auth/adapters/basic-adapter.js.map +1 -0
  32. package/dist/plugins/auth/adapters/index.d.ts +9 -0
  33. package/dist/plugins/auth/adapters/index.d.ts.map +1 -0
  34. package/dist/plugins/auth/adapters/index.js +9 -0
  35. package/dist/plugins/auth/adapters/index.js.map +1 -0
  36. package/dist/plugins/auth/adapters/supabase-adapter.d.ts +13 -0
  37. package/dist/plugins/auth/adapters/supabase-adapter.d.ts.map +1 -0
  38. package/dist/plugins/auth/adapters/supabase-adapter.js +109 -0
  39. package/dist/plugins/auth/adapters/supabase-adapter.js.map +1 -0
  40. package/dist/plugins/auth/auth-plugin.d.ts +40 -0
  41. package/dist/plugins/auth/auth-plugin.d.ts.map +1 -0
  42. package/dist/plugins/auth/auth-plugin.js +255 -0
  43. package/dist/plugins/auth/auth-plugin.js.map +1 -0
  44. package/dist/plugins/auth/auth-plugin.test.d.ts +9 -0
  45. package/dist/plugins/auth/auth-plugin.test.d.ts.map +1 -0
  46. package/dist/plugins/auth/auth-plugin.test.js +147 -0
  47. package/dist/plugins/auth/auth-plugin.test.js.map +1 -0
  48. package/dist/plugins/auth/index.d.ts +12 -0
  49. package/dist/plugins/auth/index.d.ts.map +1 -0
  50. package/dist/plugins/auth/index.js +13 -0
  51. package/dist/plugins/auth/index.js.map +1 -0
  52. package/dist/plugins/auth/types.d.ts +148 -0
  53. package/dist/plugins/auth/types.d.ts.map +1 -0
  54. package/dist/plugins/auth/types.js +14 -0
  55. package/dist/plugins/auth/types.js.map +1 -0
  56. package/dist/plugins/bans/bans-plugin.d.ts +59 -0
  57. package/dist/plugins/bans/bans-plugin.d.ts.map +1 -0
  58. package/dist/plugins/bans/bans-plugin.js +428 -0
  59. package/dist/plugins/bans/bans-plugin.js.map +1 -0
  60. package/dist/plugins/bans/index.d.ts +9 -0
  61. package/dist/plugins/bans/index.d.ts.map +1 -0
  62. package/dist/plugins/bans/index.js +10 -0
  63. package/dist/plugins/bans/index.js.map +1 -0
  64. package/dist/plugins/bans/stores/index.d.ts +7 -0
  65. package/dist/plugins/bans/stores/index.d.ts.map +1 -0
  66. package/dist/plugins/bans/stores/index.js +7 -0
  67. package/dist/plugins/bans/stores/index.js.map +1 -0
  68. package/dist/plugins/bans/stores/postgres-store.d.ts +29 -0
  69. package/dist/plugins/bans/stores/postgres-store.d.ts.map +1 -0
  70. package/dist/plugins/bans/stores/postgres-store.js +132 -0
  71. package/dist/plugins/bans/stores/postgres-store.js.map +1 -0
  72. package/dist/plugins/bans/types.d.ts +128 -0
  73. package/dist/plugins/bans/types.d.ts.map +1 -0
  74. package/dist/plugins/bans/types.js +11 -0
  75. package/dist/plugins/bans/types.js.map +1 -0
  76. package/dist/plugins/cache-plugin.d.ts +14 -3
  77. package/dist/plugins/cache-plugin.d.ts.map +1 -1
  78. package/dist/plugins/cache-plugin.js +27 -7
  79. package/dist/plugins/cache-plugin.js.map +1 -1
  80. package/dist/plugins/cache-plugin.test.js +96 -32
  81. package/dist/plugins/cache-plugin.test.js.map +1 -1
  82. package/dist/plugins/config-plugin.d.ts +3 -2
  83. package/dist/plugins/config-plugin.d.ts.map +1 -1
  84. package/dist/plugins/config-plugin.js +17 -10
  85. package/dist/plugins/config-plugin.js.map +1 -1
  86. package/dist/plugins/diagnostics-plugin.d.ts +2 -2
  87. package/dist/plugins/diagnostics-plugin.d.ts.map +1 -1
  88. package/dist/plugins/diagnostics-plugin.js +17 -10
  89. package/dist/plugins/diagnostics-plugin.js.map +1 -1
  90. package/dist/plugins/entitlements/entitlements-plugin.d.ts +95 -0
  91. package/dist/plugins/entitlements/entitlements-plugin.d.ts.map +1 -0
  92. package/dist/plugins/entitlements/entitlements-plugin.js +707 -0
  93. package/dist/plugins/entitlements/entitlements-plugin.js.map +1 -0
  94. package/dist/plugins/entitlements/index.d.ts +12 -0
  95. package/dist/plugins/entitlements/index.d.ts.map +1 -0
  96. package/dist/plugins/entitlements/index.js +16 -0
  97. package/dist/plugins/entitlements/index.js.map +1 -0
  98. package/dist/plugins/entitlements/sources/index.d.ts +9 -0
  99. package/dist/plugins/entitlements/sources/index.d.ts.map +1 -0
  100. package/dist/plugins/entitlements/sources/index.js +9 -0
  101. package/dist/plugins/entitlements/sources/index.js.map +1 -0
  102. package/dist/plugins/entitlements/sources/postgres-source.d.ts +29 -0
  103. package/dist/plugins/entitlements/sources/postgres-source.d.ts.map +1 -0
  104. package/dist/plugins/entitlements/sources/postgres-source.js +169 -0
  105. package/dist/plugins/entitlements/sources/postgres-source.js.map +1 -0
  106. package/dist/plugins/entitlements/types.d.ts +232 -0
  107. package/dist/plugins/entitlements/types.d.ts.map +1 -0
  108. package/dist/plugins/entitlements/types.js +11 -0
  109. package/dist/plugins/entitlements/types.js.map +1 -0
  110. package/dist/plugins/frontend-app-plugin.d.ts +9 -3
  111. package/dist/plugins/frontend-app-plugin.d.ts.map +1 -1
  112. package/dist/plugins/frontend-app-plugin.js +14 -9
  113. package/dist/plugins/frontend-app-plugin.js.map +1 -1
  114. package/dist/plugins/health-plugin.d.ts +5 -2
  115. package/dist/plugins/health-plugin.d.ts.map +1 -1
  116. package/dist/plugins/health-plugin.js +20 -5
  117. package/dist/plugins/health-plugin.js.map +1 -1
  118. package/dist/plugins/index.d.ts +8 -2
  119. package/dist/plugins/index.d.ts.map +1 -1
  120. package/dist/plugins/index.js +8 -2
  121. package/dist/plugins/index.js.map +1 -1
  122. package/dist/plugins/logs-plugin.d.ts +3 -2
  123. package/dist/plugins/logs-plugin.d.ts.map +1 -1
  124. package/dist/plugins/logs-plugin.js +21 -12
  125. package/dist/plugins/logs-plugin.js.map +1 -1
  126. package/dist/plugins/postgres-plugin.d.ts +3 -3
  127. package/dist/plugins/postgres-plugin.d.ts.map +1 -1
  128. package/dist/plugins/postgres-plugin.js +9 -7
  129. package/dist/plugins/postgres-plugin.js.map +1 -1
  130. package/dist/plugins/postgres-plugin.test.js +47 -29
  131. package/dist/plugins/postgres-plugin.test.js.map +1 -1
  132. package/dist/plugins/users/index.d.ts +12 -0
  133. package/dist/plugins/users/index.d.ts.map +1 -0
  134. package/dist/plugins/users/index.js +13 -0
  135. package/dist/plugins/users/index.js.map +1 -0
  136. package/dist/plugins/users/stores/index.d.ts +7 -0
  137. package/dist/plugins/users/stores/index.d.ts.map +1 -0
  138. package/dist/plugins/users/stores/index.js +7 -0
  139. package/dist/plugins/users/stores/index.js.map +1 -0
  140. package/dist/plugins/users/stores/postgres-store.d.ts +28 -0
  141. package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -0
  142. package/dist/plugins/users/stores/postgres-store.js +157 -0
  143. package/dist/plugins/users/stores/postgres-store.js.map +1 -0
  144. package/dist/plugins/users/types.d.ts +189 -0
  145. package/dist/plugins/users/types.d.ts.map +1 -0
  146. package/dist/plugins/users/types.js +12 -0
  147. package/dist/plugins/users/types.js.map +1 -0
  148. package/dist/plugins/users/users-plugin.d.ts +39 -0
  149. package/dist/plugins/users/users-plugin.d.ts.map +1 -0
  150. package/dist/plugins/users/users-plugin.js +242 -0
  151. package/dist/plugins/users/users-plugin.js.map +1 -0
  152. package/dist-ui/assets/index-Bsp2ntcw.js +465 -0
  153. package/dist-ui/assets/index-Bsp2ntcw.js.map +1 -0
  154. package/dist-ui/index.html +1 -1
  155. package/dist-ui-lib/api/controlPanelApi.d.ts +232 -0
  156. package/dist-ui-lib/components/ControlPanelApp.d.ts +61 -0
  157. package/dist-ui-lib/components/index.d.ts +18 -0
  158. package/dist-ui-lib/config/AppConfig.d.ts +7 -0
  159. package/dist-ui-lib/dashboard/DashboardWidgetRegistry.d.ts +62 -0
  160. package/dist-ui-lib/dashboard/DashboardWidgetRenderer.d.ts +8 -0
  161. package/dist-ui-lib/dashboard/PluginWidgetRenderer.d.ts +19 -0
  162. package/dist-ui-lib/dashboard/WidgetComponentRegistry.d.ts +44 -0
  163. package/dist-ui-lib/dashboard/builtInWidgets.d.ts +19 -0
  164. package/dist-ui-lib/dashboard/index.d.ts +13 -0
  165. package/dist-ui-lib/dashboard/widgets/ServiceHealthWidget.d.ts +12 -0
  166. package/dist-ui-lib/dashboard/widgets/index.d.ts +6 -0
  167. package/dist-ui-lib/index.js +6441 -0
  168. package/dist-ui-lib/index.js.map +1 -0
  169. package/dist-ui-lib/pages/ConfigPage.d.ts +1 -0
  170. package/dist-ui-lib/pages/DashboardPage.d.ts +1 -0
  171. package/dist-ui-lib/pages/DiagnosticsPage.d.ts +1 -0
  172. package/dist-ui-lib/pages/EntitlementsPage.d.ts +17 -0
  173. package/dist-ui-lib/pages/LogsPage.d.ts +1 -0
  174. package/dist-ui-lib/pages/NotFoundPage.d.ts +1 -0
  175. package/dist-ui-lib/pages/PluginPage.d.ts +15 -0
  176. package/dist-ui-lib/pages/SystemPage.d.ts +1 -0
  177. package/dist-ui-lib/pages/UsersPage.d.ts +22 -0
  178. package/package.json +18 -6
  179. package/src/core/control-panel.ts +122 -68
  180. package/src/core/gateway.ts +870 -399
  181. package/src/core/index.ts +21 -2
  182. package/src/core/plugin-registry.ts +653 -0
  183. package/src/core/types.ts +31 -37
  184. package/src/index.ts +118 -19
  185. package/src/plugins/auth/adapters/auth0-adapter.ts +214 -0
  186. package/src/plugins/auth/adapters/basic-adapter.ts +61 -0
  187. package/src/plugins/auth/adapters/index.ts +9 -0
  188. package/src/plugins/auth/adapters/supabase-adapter.ts +141 -0
  189. package/src/plugins/auth/auth-plugin.test.ts +176 -0
  190. package/src/plugins/auth/auth-plugin.ts +303 -0
  191. package/src/plugins/auth/index.ts +33 -0
  192. package/src/plugins/auth/types.ts +165 -0
  193. package/src/plugins/bans/bans-plugin.ts +485 -0
  194. package/src/plugins/bans/index.ts +31 -0
  195. package/src/plugins/bans/stores/index.ts +7 -0
  196. package/src/plugins/bans/stores/postgres-store.ts +195 -0
  197. package/src/plugins/bans/types.ts +141 -0
  198. package/src/plugins/cache-plugin.test.ts +105 -32
  199. package/src/plugins/cache-plugin.ts +40 -9
  200. package/src/plugins/config-plugin.ts +23 -12
  201. package/src/plugins/diagnostics-plugin.ts +22 -12
  202. package/src/plugins/entitlements/entitlements-plugin.ts +820 -0
  203. package/src/plugins/entitlements/index.ts +51 -0
  204. package/src/plugins/entitlements/sources/index.ts +9 -0
  205. package/src/plugins/entitlements/sources/postgres-source.ts +253 -0
  206. package/src/plugins/entitlements/types.ts +256 -0
  207. package/src/plugins/frontend-app-plugin.ts +24 -12
  208. package/src/plugins/health-plugin.ts +27 -7
  209. package/src/plugins/index.ts +106 -4
  210. package/src/plugins/logs-plugin.ts +28 -14
  211. package/src/plugins/postgres-plugin.test.ts +49 -29
  212. package/src/plugins/postgres-plugin.ts +11 -9
  213. package/src/plugins/users/index.ts +35 -0
  214. package/src/plugins/users/stores/index.ts +7 -0
  215. package/src/plugins/users/stores/postgres-store.ts +225 -0
  216. package/src/plugins/users/types.ts +209 -0
  217. package/src/plugins/users/users-plugin.ts +281 -0
  218. package/ui/src/App.tsx +185 -31
  219. package/ui/src/api/controlPanelApi.ts +354 -1
  220. package/ui/src/components/ControlPanelApp.tsx +209 -0
  221. package/ui/src/components/index.ts +62 -0
  222. package/ui/src/dashboard/DashboardWidgetRegistry.tsx +129 -0
  223. package/ui/src/dashboard/DashboardWidgetRenderer.tsx +34 -0
  224. package/ui/src/dashboard/PluginWidgetRenderer.tsx +115 -0
  225. package/ui/src/dashboard/WidgetComponentRegistry.tsx +116 -0
  226. package/ui/src/dashboard/builtInWidgets.tsx +29 -0
  227. package/ui/src/dashboard/index.ts +35 -0
  228. package/ui/src/dashboard/widgets/ServiceHealthWidget.tsx +140 -0
  229. package/ui/src/dashboard/widgets/index.ts +7 -0
  230. package/ui/src/pages/DashboardPage.tsx +28 -149
  231. package/ui/src/pages/EntitlementsPage.tsx +557 -0
  232. package/ui/src/pages/LogsPage.tsx +174 -8
  233. package/ui/src/pages/PluginPage.tsx +148 -0
  234. package/ui/src/pages/SystemPage.tsx +445 -0
  235. package/ui/src/pages/UsersPage.tsx +837 -0
  236. package/ui/tsconfig.lib.json +11 -0
  237. package/ui/vite.lib.config.ts +51 -0
  238. package/dist-ui/assets/index-CW1BviRn.js +0 -465
  239. package/dist-ui/assets/index-CW1BviRn.js.map +0 -1
  240. package/ui/src/pages/HealthPage.tsx +0 -204
@@ -1,4 +1,4 @@
1
- import { useState, useEffect } from 'react';
1
+ import { useState, useEffect, useRef, useCallback } from 'react';
2
2
  import {
3
3
  Box,
4
4
  Card,
@@ -19,9 +19,21 @@ import {
19
19
  MenuItem,
20
20
  IconButton,
21
21
  Pagination,
22
+ Tooltip,
23
+ Grid,
24
+ ToggleButton,
25
+ ToggleButtonGroup,
22
26
  } from '@mui/material';
23
27
  import RefreshIcon from '@mui/icons-material/Refresh';
24
28
  import SearchIcon from '@mui/icons-material/Search';
29
+ import PlayArrowIcon from '@mui/icons-material/PlayArrow';
30
+ import PauseIcon from '@mui/icons-material/Pause';
31
+ import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
32
+ import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
33
+ import ErrorIcon from '@mui/icons-material/Error';
34
+ import WarningIcon from '@mui/icons-material/Warning';
35
+ import InfoIcon from '@mui/icons-material/Info';
36
+ import BugReportIcon from '@mui/icons-material/BugReport';
25
37
  import { api, LogEntry, LogSource } from '../api/controlPanelApi';
26
38
 
27
39
  function getLevelColor(level: string): string {
@@ -54,7 +66,21 @@ export function LogsPage() {
54
66
  const [total, setTotal] = useState(0);
55
67
  const limit = 50;
56
68
 
57
- const fetchLogs = async () => {
69
+ // New features
70
+ const [autoRefresh, setAutoRefresh] = useState(false);
71
+ const [sortOrder, setSortOrder] = useState<'desc' | 'asc'>('desc'); // desc = newest first
72
+ const autoRefreshRef = useRef<NodeJS.Timeout | null>(null);
73
+
74
+ // Stats - computed from current logs (could be fetched from API if available)
75
+ const stats = {
76
+ total: total,
77
+ errors: logs.filter(l => l.level.toLowerCase() === 'error').length,
78
+ warnings: logs.filter(l => ['warn', 'warning'].includes(l.level.toLowerCase())).length,
79
+ info: logs.filter(l => l.level.toLowerCase() === 'info').length,
80
+ debug: logs.filter(l => l.level.toLowerCase() === 'debug').length,
81
+ };
82
+
83
+ const fetchLogs = useCallback(async () => {
58
84
  setLoading(true);
59
85
  try {
60
86
  const data = await api.getLogs({
@@ -64,7 +90,13 @@ export function LogsPage() {
64
90
  limit,
65
91
  page,
66
92
  });
67
- setLogs(data.logs);
93
+ // Sort logs based on sortOrder
94
+ const sortedLogs = [...data.logs].sort((a, b) => {
95
+ const dateA = new Date(a.timestamp).getTime();
96
+ const dateB = new Date(b.timestamp).getTime();
97
+ return sortOrder === 'desc' ? dateB - dateA : dateA - dateB;
98
+ });
99
+ setLogs(sortedLogs);
68
100
  setTotal(data.total);
69
101
  setError(null);
70
102
  } catch (err) {
@@ -72,7 +104,7 @@ export function LogsPage() {
72
104
  } finally {
73
105
  setLoading(false);
74
106
  }
75
- };
107
+ }, [selectedSource, selectedLevel, searchQuery, page, sortOrder]);
76
108
 
77
109
  const fetchSources = async () => {
78
110
  try {
@@ -89,13 +121,34 @@ export function LogsPage() {
89
121
 
90
122
  useEffect(() => {
91
123
  fetchLogs();
92
- }, [selectedSource, selectedLevel, page]);
124
+ }, [fetchLogs]);
125
+
126
+ // Auto-refresh effect
127
+ useEffect(() => {
128
+ if (autoRefresh) {
129
+ autoRefreshRef.current = setInterval(fetchLogs, 5000);
130
+ } else if (autoRefreshRef.current) {
131
+ clearInterval(autoRefreshRef.current);
132
+ autoRefreshRef.current = null;
133
+ }
134
+ return () => {
135
+ if (autoRefreshRef.current) {
136
+ clearInterval(autoRefreshRef.current);
137
+ }
138
+ };
139
+ }, [autoRefresh, fetchLogs]);
93
140
 
94
141
  const handleSearch = () => {
95
142
  setPage(1);
96
143
  fetchLogs();
97
144
  };
98
145
 
146
+ const handleSortOrderChange = (_event: React.MouseEvent<HTMLElement>, newOrder: 'desc' | 'asc' | null) => {
147
+ if (newOrder !== null) {
148
+ setSortOrder(newOrder);
149
+ }
150
+ };
151
+
99
152
  const totalPages = Math.ceil(total / limit);
100
153
 
101
154
  return (
@@ -107,6 +160,84 @@ export function LogsPage() {
107
160
  View and search application logs
108
161
  </Typography>
109
162
 
163
+ {/* Stats Widgets */}
164
+ <Grid container spacing={2} sx={{ mb: 3 }}>
165
+ <Grid size={{ xs: 6, sm: 3, md: 2.4 }}>
166
+ <Card sx={{ bgcolor: 'var(--theme-surface)' }}>
167
+ <CardContent sx={{ py: 1.5, px: 2, '&:last-child': { pb: 1.5 } }}>
168
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
169
+ <Typography variant="h5" sx={{ color: 'var(--theme-text-primary)', fontWeight: 600 }}>
170
+ {stats.total.toLocaleString()}
171
+ </Typography>
172
+ </Box>
173
+ <Typography variant="caption" sx={{ color: 'var(--theme-text-secondary)' }}>
174
+ Total Logs
175
+ </Typography>
176
+ </CardContent>
177
+ </Card>
178
+ </Grid>
179
+ <Grid size={{ xs: 6, sm: 3, md: 2.4 }}>
180
+ <Card sx={{ bgcolor: 'var(--theme-surface)' }}>
181
+ <CardContent sx={{ py: 1.5, px: 2, '&:last-child': { pb: 1.5 } }}>
182
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
183
+ <ErrorIcon sx={{ color: 'var(--theme-error)', fontSize: 20 }} />
184
+ <Typography variant="h5" sx={{ color: 'var(--theme-error)', fontWeight: 600 }}>
185
+ {stats.errors}
186
+ </Typography>
187
+ </Box>
188
+ <Typography variant="caption" sx={{ color: 'var(--theme-text-secondary)' }}>
189
+ Errors
190
+ </Typography>
191
+ </CardContent>
192
+ </Card>
193
+ </Grid>
194
+ <Grid size={{ xs: 6, sm: 3, md: 2.4 }}>
195
+ <Card sx={{ bgcolor: 'var(--theme-surface)' }}>
196
+ <CardContent sx={{ py: 1.5, px: 2, '&:last-child': { pb: 1.5 } }}>
197
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
198
+ <WarningIcon sx={{ color: 'var(--theme-warning)', fontSize: 20 }} />
199
+ <Typography variant="h5" sx={{ color: 'var(--theme-warning)', fontWeight: 600 }}>
200
+ {stats.warnings}
201
+ </Typography>
202
+ </Box>
203
+ <Typography variant="caption" sx={{ color: 'var(--theme-text-secondary)' }}>
204
+ Warnings
205
+ </Typography>
206
+ </CardContent>
207
+ </Card>
208
+ </Grid>
209
+ <Grid size={{ xs: 6, sm: 3, md: 2.4 }}>
210
+ <Card sx={{ bgcolor: 'var(--theme-surface)' }}>
211
+ <CardContent sx={{ py: 1.5, px: 2, '&:last-child': { pb: 1.5 } }}>
212
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
213
+ <InfoIcon sx={{ color: 'var(--theme-info)', fontSize: 20 }} />
214
+ <Typography variant="h5" sx={{ color: 'var(--theme-info)', fontWeight: 600 }}>
215
+ {stats.info}
216
+ </Typography>
217
+ </Box>
218
+ <Typography variant="caption" sx={{ color: 'var(--theme-text-secondary)' }}>
219
+ Info
220
+ </Typography>
221
+ </CardContent>
222
+ </Card>
223
+ </Grid>
224
+ <Grid size={{ xs: 6, sm: 3, md: 2.4 }}>
225
+ <Card sx={{ bgcolor: 'var(--theme-surface)' }}>
226
+ <CardContent sx={{ py: 1.5, px: 2, '&:last-child': { pb: 1.5 } }}>
227
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
228
+ <BugReportIcon sx={{ color: 'var(--theme-text-secondary)', fontSize: 20 }} />
229
+ <Typography variant="h5" sx={{ color: 'var(--theme-text-primary)', fontWeight: 600 }}>
230
+ {stats.debug}
231
+ </Typography>
232
+ </Box>
233
+ <Typography variant="caption" sx={{ color: 'var(--theme-text-secondary)' }}>
234
+ Debug
235
+ </Typography>
236
+ </CardContent>
237
+ </Card>
238
+ </Grid>
239
+ </Grid>
240
+
110
241
  {/* Filters */}
111
242
  <Card sx={{ mb: 3, bgcolor: 'var(--theme-surface)' }}>
112
243
  <CardContent>
@@ -162,9 +293,44 @@ export function LogsPage() {
162
293
  }}
163
294
  />
164
295
 
165
- <IconButton onClick={fetchLogs} sx={{ color: 'var(--theme-primary)' }}>
166
- <RefreshIcon />
167
- </IconButton>
296
+ {/* Sort Order Toggle */}
297
+ <ToggleButtonGroup
298
+ value={sortOrder}
299
+ exclusive
300
+ onChange={handleSortOrderChange}
301
+ size="small"
302
+ aria-label="sort order"
303
+ >
304
+ <ToggleButton value="desc" aria-label="newest first">
305
+ <Tooltip title="Newest First">
306
+ <ArrowDownwardIcon fontSize="small" />
307
+ </Tooltip>
308
+ </ToggleButton>
309
+ <ToggleButton value="asc" aria-label="oldest first">
310
+ <Tooltip title="Oldest First">
311
+ <ArrowUpwardIcon fontSize="small" />
312
+ </Tooltip>
313
+ </ToggleButton>
314
+ </ToggleButtonGroup>
315
+
316
+ {/* Auto Refresh Toggle */}
317
+ <Tooltip title={autoRefresh ? 'Pause auto-refresh' : 'Enable auto-refresh (5s)'}>
318
+ <IconButton
319
+ onClick={() => setAutoRefresh(!autoRefresh)}
320
+ sx={{
321
+ color: autoRefresh ? 'var(--theme-success)' : 'var(--theme-text-secondary)',
322
+ bgcolor: autoRefresh ? 'var(--theme-success)20' : 'transparent',
323
+ }}
324
+ >
325
+ {autoRefresh ? <PauseIcon /> : <PlayArrowIcon />}
326
+ </IconButton>
327
+ </Tooltip>
328
+
329
+ <Tooltip title="Refresh">
330
+ <IconButton onClick={fetchLogs} sx={{ color: 'var(--theme-primary)' }}>
331
+ <RefreshIcon />
332
+ </IconButton>
333
+ </Tooltip>
168
334
  </Box>
169
335
  </CardContent>
170
336
  </Card>
@@ -0,0 +1,148 @@
1
+ /**
2
+ * PluginPage Component
3
+ *
4
+ * A generic page component for plugin-contributed routes.
5
+ * Fetches and displays plugin-specific content from the API.
6
+ *
7
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
8
+ */
9
+
10
+ import { useState, useEffect } from 'react';
11
+ import {
12
+ Box,
13
+ Typography,
14
+ Card,
15
+ CardContent,
16
+ CircularProgress,
17
+ Alert,
18
+ } from '@mui/material';
19
+ import { api } from '../api/controlPanelApi';
20
+
21
+ interface PluginPageProps {
22
+ pluginId: string;
23
+ title: string;
24
+ route: string;
25
+ }
26
+
27
+ interface PluginPageData {
28
+ content?: string;
29
+ cards?: Array<{
30
+ title: string;
31
+ value: string | number;
32
+ subtitle?: string;
33
+ color?: string;
34
+ }>;
35
+ sections?: Array<{
36
+ title: string;
37
+ content: string;
38
+ }>;
39
+ }
40
+
41
+ export function PluginPage({ pluginId, title, route }: PluginPageProps) {
42
+ const [loading, setLoading] = useState(true);
43
+ const [error, setError] = useState<string | null>(null);
44
+ const [pageData, setPageData] = useState<PluginPageData | null>(null);
45
+
46
+ useEffect(() => {
47
+ const fetchPageData = async () => {
48
+ try {
49
+ // Try to fetch plugin-specific page data
50
+ const response = await fetch(`${api['baseUrl']}/api${route}`);
51
+ if (response.ok) {
52
+ const data = await response.json();
53
+ setPageData(data);
54
+ setError(null);
55
+ } else if (response.status === 404) {
56
+ // No dedicated page endpoint, show placeholder
57
+ setPageData(null);
58
+ setError(null);
59
+ } else {
60
+ setError(`Failed to load page: ${response.statusText}`);
61
+ }
62
+ } catch (err) {
63
+ setError(err instanceof Error ? err.message : 'Failed to load page');
64
+ } finally {
65
+ setLoading(false);
66
+ }
67
+ };
68
+
69
+ fetchPageData();
70
+ }, [route]);
71
+
72
+ if (loading) {
73
+ return (
74
+ <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '50vh' }}>
75
+ <CircularProgress />
76
+ </Box>
77
+ );
78
+ }
79
+
80
+ return (
81
+ <Box>
82
+ <Typography variant="h4" sx={{ mb: 1, color: 'var(--theme-text-primary)' }}>
83
+ {title}
84
+ </Typography>
85
+ <Typography variant="body2" sx={{ mb: 4, color: 'var(--theme-text-secondary)' }}>
86
+ Plugin: {pluginId}
87
+ </Typography>
88
+
89
+ {error && (
90
+ <Alert severity="error" sx={{ mb: 3 }}>
91
+ {error}
92
+ </Alert>
93
+ )}
94
+
95
+ {pageData?.cards && pageData.cards.length > 0 && (
96
+ <Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: 2, mb: 4 }}>
97
+ {pageData.cards.map((card, index) => (
98
+ <Card key={index} sx={{ bgcolor: 'var(--theme-surface)' }}>
99
+ <CardContent>
100
+ <Typography variant="h4" sx={{ color: card.color || 'var(--theme-text-primary)' }}>
101
+ {card.value}
102
+ </Typography>
103
+ <Typography variant="body2" sx={{ color: 'var(--theme-text-secondary)' }}>
104
+ {card.title}
105
+ </Typography>
106
+ {card.subtitle && (
107
+ <Typography variant="caption" sx={{ color: 'var(--theme-text-secondary)' }}>
108
+ {card.subtitle}
109
+ </Typography>
110
+ )}
111
+ </CardContent>
112
+ </Card>
113
+ ))}
114
+ </Box>
115
+ )}
116
+
117
+ {pageData?.sections && pageData.sections.length > 0 && (
118
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
119
+ {pageData.sections.map((section, index) => (
120
+ <Card key={index} sx={{ bgcolor: 'var(--theme-surface)' }}>
121
+ <CardContent>
122
+ <Typography variant="h6" sx={{ mb: 2, color: 'var(--theme-text-primary)' }}>
123
+ {section.title}
124
+ </Typography>
125
+ <Typography variant="body2" sx={{ color: 'var(--theme-text-secondary)', whiteSpace: 'pre-wrap' }}>
126
+ {section.content}
127
+ </Typography>
128
+ </CardContent>
129
+ </Card>
130
+ ))}
131
+ </Box>
132
+ )}
133
+
134
+ {!pageData && !error && (
135
+ <Card sx={{ bgcolor: 'var(--theme-surface)' }}>
136
+ <CardContent>
137
+ <Typography variant="body1" sx={{ color: 'var(--theme-text-secondary)' }}>
138
+ This plugin page is available. Configure the plugin to add content here.
139
+ </Typography>
140
+ <Typography variant="body2" sx={{ mt: 2, color: 'var(--theme-text-secondary)' }}>
141
+ API endpoint: <code>/api{route}</code>
142
+ </Typography>
143
+ </CardContent>
144
+ </Card>
145
+ )}
146
+ </Box>
147
+ );
148
+ }