From 50923682ad789af50d338e618f2419ecc738ab16 Mon Sep 17 00:00:00 2001 From: sirpiglr <49359077-sirpiglr@users.noreply.replit.com> Date: Sun, 21 Dec 2025 21:55:24 +0000 Subject: [PATCH] Add administrator tools for managing architects and enhance OS widget functionality Includes service worker registration for offline support, adds bulk actions and filtering to the admin architects page, and implements widget visibility controls and a mobile drawer for the OS. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 279f1558-c0e3-40e4-8217-be7e9f4c6eca Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: ad12b0de-1689-4465-b8e3-8b92d06f17d1 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/b984cb14-1d19-4944-922b-bc79e821ed35/279f1558-c0e3-40e4-8217-be7e9f4c6eca/4z9y3HV Replit-Helium-Checkpoint-Created: true --- client/index.html | 9 + client/public/opengraph.jpg | Bin 10628 -> 10628 bytes client/public/sw.js | 67 ++++++ client/src/pages/admin-architects.tsx | 197 +++++++++++++++-- client/src/pages/os.tsx | 304 ++++++++++++++++++++++---- 5 files changed, 509 insertions(+), 68 deletions(-) create mode 100644 client/public/sw.js diff --git a/client/index.html b/client/index.html index 58728ca..cb1cd2b 100644 --- a/client/index.html +++ b/client/index.html @@ -28,5 +28,14 @@
+ diff --git a/client/public/opengraph.jpg b/client/public/opengraph.jpg index dd895df0f89ac6fd1b8b65c9a3cea1099ce4677e..93b52be5759434e73b1ab08d42bb2f60fc15b690 100644 GIT binary patch delta 4248 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 ADF6Tf delta 4249 zcmbVOX;_oT+74o?9xRpu0YTvudq9JLkxg0KQhGcLJS;4 zlq8t6p(c<>5LqGwh#~<50*xUk2@+*rOpq-sVL8xV-;eL-_s#s7>zVtWYvz9Dxo3U} z{3Y~;P;Zx%i=;-0oGCXGY!^4f@MG1HY6_}lKMH;n=!=`I-g`XEwtD`$&8&b4cYTN?n*L9$G(BL48Tj$)+|7|Q%#`oz z!Oo?BuZB!N{V>z6ud;OoveF_Z*}*u!qu2fp2@NZfF~U>Qp=ZO41`8}T(G4Dy$x|~b z4t!DEFh}@8^6V0_5Uw2ufNOle%OmM-fnpp@W)TRE7)PK52@QJ0nwDqGqVFfCu1fu$ zh)Vo#KVv+SAW>-4EBBD$<_BiFSzk$;t*6BAZW*V`R=O(t-fvXBD_$%NUJ-u@>_cAz7>n@ovTHBm+?A-zxb$a-?o}LQ1 ztJFTr&T}=qD3s5AP))8c0X1=&=8^e_k&i=rJ|pS#*0^L626CI4I^c2_jR=-Y4CC5i z-HG%&@ki88Hil>$dS1Zh*h{ZR_dFPi;*6O5Oqut|Xvi z$OIDeG|bbx@m{2Qk5_AlIFmlzfbni%mr#q}$Vx6r^*hNNoD1{FUElgSqkF2Jbel0> zl%%}$=hjg_jot^9Dmy)SGfb(mMs6xMXHnTixH<3sEtbGIICs|%Hm8fu)nLLsNNapS zQ8|(Q?vU{7zcaQiPD2@Waa$a!&$c+%AO4hPG=B^KvNGn0W9}mMYN#p_k?*ZvFeqvo zx5N_ObKdd-Vs$!S<7QXAltmlKn|5S4qT#r#Z8l?Qni9zZi<=kh-H&i!`&KKmS~Gcd z>XA`8&uE>N+-n+9MVz=RXYfPf`9rf!%rZIdj13Ey5pZU*U|uo?R@`lVg`QrF?wblZ zKH}q^)aIMz?8L@cM*T|9erq@xAc|ETK`0wRpU_V}0$=Rni0L6I^Ghv6&WS^;|LmmN z!A9s)Cq3RKsaZ;Lm}NDM+yL{h9a?_8V<9@0m%`dl%fL22&MXF@JX7Yy*NqzLwK}5y zx+9;kZWQ@o4s+DwmXs;QYCxTF%A&i|hK!38GGo~(D^dje}$vzh^g7 zBFAoB)3rakt>-&nsiP{xezBqu)FnWSZNmX;h z>uaPhaAbCJ&QMhJvaj>Ql0%#~=MN@7LROlZ#SnQGo;La+ep?_4+7dyTVqMlexc$@T zj}M${Z#pF@0CQ9HLUs?nrx|nN)C#61L&pMhB(_8ApCDj=ldE4(ABRhF)v>Z|IFepo zh2<2&GA5!UkqpjNsdp!aGDXfUXFCUn#5ITW zGww?7eng!HFUe~9*%Iy4c#G*yt#_WR&d2h3+@@#^G2BGi@Qy%*3zpw#Rl(Q)9(ixxpZ7lzPovJDuZ?ka_ zK{0mH=X%hZc3n-}2}y9FS9ec%)#_qnYHA|j*WRUPI4fW_4=Ch3(#_w*#wj%=m&096 zE~8%Ajt+LJQtBN=4a>9zuDBC$ntWaJlz;zCc!V?+Y&0MwQTgy<-%9|YJJxz#|WAz3y5!GqBcw%)bH!7dr18#YBRCKU5gEi*? znKJqGI6O!UEWkv{3{*IBIl@z_&xLz8#d;8vB;E$2NG2tQ#IS(S;GeZurKaOjFvqGm zqX>*JwBjk#f))Z%O<=Jcpi6&N7U~qfHvgpZB2u2GObic88j4EMD@ccqw?@>1Et?nk z!!$FSx?%ZtNt}NSjnu_p?6yFU-~JIT9r+3HIfS@}mT64;>3Dr}^RPZgH`jBkb=76< zRHsEvn!$M{y0Qz*W^Y#<`UtUP3&d4b8=tFH^Q?`|^6(-Jxhl%pjKb`eBE2@xP#7+~ zHWP*Lsz=lJPO2`Q>#L3%L*|*x!n4b`Wy^kW*WT|USBGGdY%2@(>xm3;yXO$#ui3-{ z9+^V|J(yRh6H0=g7acYyIKrgC<-EI+)DKc8IWL*7l;Ln-MBbm+V24L8wxKVo5l*U5 zCO;{Bgh635$s~pq-L_=orOSz=mwmWtqq(?H`g0{_Ik>8v@9k`fhOlk?f+Wzog*uHd zA`QDS6ReWgCXazttw3M`1zVguY?Z}yZyZUftvKt_?_EMXU+;jbhQYC8&e+^Cri-_I z^PKt7W3SNS)a8Zj$mgdX91iO4IUGDU5G@@Wmt&jq5A!~3y!ABOJnYPFXU`huv=OV@ z*QR)W2(LgWkB6&>s&T>%^jB--SH;cVqNnA>iiNZK*7jE4+F2$fJh4M{kO@?tHHAaY zFImXInlOJa_kP-!_+)-~X#McUH22Ev>$r*gzBJ3PLckODBls5zZJ)}2G+>*SLbI|8 zoey5y*FS4x{Y`6I+f3oJg8*=4c7WHC8R5wshDp*HVmT*bHiPO@aUHMYuk`DzI$D*` z{}4Cio;4Wg;A@!ZAol2xIGrxng{L zbTq8iG>oZ~adMB0>AO8Bm)4pjtv+LU+MbBwxgjotl2D8#6)PPBx)eHI zLPiW1V~I}tP~%gnQ#fGqE8yu#x9v)Wi{I3UVNs2(3dubUDzAx;AIZO2RFI#&>ESM~ ziywyY!wp~(oYfyvkCu|-?8jIU%WQEt5S4$N7-2jsr{EPhV!tCUg3<~ z@niLRJr-qog8GALdW7JwE=JrA<;+@khq&p72yQXAKu)Op3Zs3&>0Q%x3Y!g8;kc=X zHfZm;YPfcQ7NCVi1N&X6;mhVm`{UzRd%;^E4>Qzc=N9OpXLfR#&urfI{ipA7)+tS| z-+qMZm7KM(>UD@3=t`5W+o*xgpce6~ZIYq+%toE~=KX21vWSKH*t;&6KgR=Z0qQN# zsP%=JfbYZBPXRLI>=AS<+r8dSt0ql!mrz zu=sPna)ON=ctc%sRvethH#_`uzPHn`OXR#gw(nD9Mt|MgxxZ18&D4yz`z^QqvVZ-z z|NG>H|3CGAGo3@XKzrYDZe^**cxGE5)vr6->DKbTw*BH=1P-;ry1f#~qi>cDLnxK+ zLW%Zhn(c7I)6`UT4$Y3XzPf(Bf`B9jUzvx_2-$DL$LA3IqAL?|46_3W-;n)Yq@HvitL0n0_)EH^gRhs19OWLkzVhj4m==U~`e%xpFp^T_Ucg zIYl=i05~RfWl~ng&cE!V7~g$Li8^o?sR#96XGEn9IOls;I##1E4;0rE6O;&%Uc2y6 z7!1+x@=e3=UKsKnpDA*(IXCo{jcLH-nb!2I&*Tb3`YMH7tdGCd$ZAyFNFA1Q+cdh2 z0BhZ$PnQOnxPUAuj>0Mm^1?SNd*$XTV7~uU<~)R%Ya_vE!JTo4;ZE&`M9*fym8{#mdVGJh*u_#b)p0MZI&ZqaURv4;{26X#p*l#;y z8OdxifgqQuS@srWHu^)Ws7DAJJ)_bT3C{fi;ImRJ&jBy%;>q|_2$vnx+;HoS!w>1J?htN zT)G%2d~x^)F;2aTYXw~#6bX_!j^SKus)NwLJIJfmE3-}pq?|tZdfqmSJ1QUH@%Brw zl=P%2J%P{B{bfBJw>f0Dcm-0Chbb=RWb@nP1{0$avcm*CQL_bFAB3vpEdSd-96Uv? rH!cV|$F+bIlL<@+Y2=TEyugJ>Y0ysZmfAOVYmnO>;ju>>v^DTw6mRvW diff --git a/client/public/sw.js b/client/public/sw.js new file mode 100644 index 0000000..f2a4eda --- /dev/null +++ b/client/public/sw.js @@ -0,0 +1,67 @@ +const CACHE_NAME = 'aethex-os-v1'; +const STATIC_ASSETS = [ + '/', + '/manifest.json', + '/favicon.png' +]; + +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => { + return cache.addAll(STATIC_ASSETS); + }) + ); + self.skipWaiting(); +}); + +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames + .filter((name) => name !== CACHE_NAME) + .map((name) => caches.delete(name)) + ); + }) + ); + self.clients.claim(); +}); + +self.addEventListener('fetch', (event) => { + const { request } = event; + const url = new URL(request.url); + + if (request.method !== 'GET') return; + + if (url.pathname.startsWith('/api/')) { + event.respondWith( + fetch(request) + .then((response) => { + if (response.ok) { + const responseClone = response.clone(); + caches.open(CACHE_NAME).then((cache) => { + cache.put(request, responseClone); + }); + } + return response; + }) + .catch(() => caches.match(request)) + ); + return; + } + + event.respondWith( + caches.match(request).then((cached) => { + const fetchPromise = fetch(request).then((response) => { + if (response.ok) { + const responseClone = response.clone(); + caches.open(CACHE_NAME).then((cache) => { + cache.put(request, responseClone); + }); + } + return response; + }); + return cached || fetchPromise; + }) + ); +}); diff --git a/client/src/pages/admin-architects.tsx b/client/src/pages/admin-architects.tsx index f41a1f0..60c19a8 100644 --- a/client/src/pages/admin-architects.tsx +++ b/client/src/pages/admin-architects.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useMemo } from "react"; import { motion } from "framer-motion"; import { Link, useLocation } from "wouter"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; @@ -6,7 +6,8 @@ import { useAuth } from "@/lib/auth"; import { Users, FileCode, Shield, Activity, LogOut, Home, BarChart3, Settings, User, Search, - CheckCircle, XCircle, Eye, Edit, ChevronRight + CheckCircle, XCircle, Eye, Edit, ChevronRight, + Download, Trash2, Square, CheckSquare } from "lucide-react"; export default function AdminArchitects() { @@ -14,6 +15,9 @@ export default function AdminArchitects() { const [, setLocation] = useLocation(); const [searchQuery, setSearchQuery] = useState(""); const [selectedProfile, setSelectedProfile] = useState(null); + const [selectedIds, setSelectedIds] = useState>(new Set()); + const [roleFilter, setRoleFilter] = useState("all"); + const [verifiedFilter, setVerifiedFilter] = useState("all"); const queryClient = useQueryClient(); const { data: profiles, isLoading } = useQuery({ @@ -40,10 +44,70 @@ export default function AdminArchitects() { }, }); - const filteredProfiles = profiles?.filter((p: any) => - p.username?.toLowerCase().includes(searchQuery.toLowerCase()) || - p.email?.toLowerCase().includes(searchQuery.toLowerCase()) - ) || []; + const filteredProfiles = useMemo(() => { + if (!profiles) return []; + return profiles.filter((p: any) => { + const matchesSearch = p.username?.toLowerCase().includes(searchQuery.toLowerCase()) || + p.email?.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesRole = roleFilter === "all" || p.role === roleFilter; + const matchesVerified = verifiedFilter === "all" || + (verifiedFilter === "verified" && p.is_verified) || + (verifiedFilter === "unverified" && !p.is_verified); + return matchesSearch && matchesRole && matchesVerified; + }); + }, [profiles, searchQuery, roleFilter, verifiedFilter]); + + const toggleSelectAll = () => { + if (selectedIds.size === filteredProfiles.length) { + setSelectedIds(new Set()); + } else { + setSelectedIds(new Set(filteredProfiles.map((p: any) => p.id))); + } + }; + + const toggleSelect = (id: string) => { + const newSet = new Set(selectedIds); + if (newSet.has(id)) { + newSet.delete(id); + } else { + newSet.add(id); + } + setSelectedIds(newSet); + }; + + const exportToCSV = () => { + const dataToExport = selectedIds.size > 0 + ? filteredProfiles.filter((p: any) => selectedIds.has(p.id)) + : filteredProfiles; + + const headers = ["Username", "Email", "Role", "Level", "XP", "Status", "Verified"]; + const csvContent = [ + headers.join(","), + ...dataToExport.map((p: any) => [ + p.username || "", + p.email || "", + p.role || "", + p.level || 0, + p.total_xp || 0, + p.status || "", + p.is_verified ? "Yes" : "No" + ].join(",")) + ].join("\n"); + + const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" }); + const link = document.createElement("a"); + link.href = URL.createObjectURL(blob); + link.download = `architects_${new Date().toISOString().split("T")[0]}.csv`; + link.click(); + }; + + const bulkVerify = async (verify: boolean) => { + const ids = Array.from(selectedIds); + for (const id of ids) { + await updateProfileMutation.mutateAsync({ id, updates: { is_verified: verify } }); + } + setSelectedIds(new Set()); + }; const handleLogout = async () => { await logout(); @@ -100,35 +164,115 @@ export default function AdminArchitects() { {/* Main Content */}
-
+

Architects

- {profiles?.length || 0} registered architects + {filteredProfiles.length} of {profiles?.length || 0} architects

- {/* Search */} -
- - setSearchQuery(e.target.value)} - className="bg-card border border-white/10 pl-10 pr-4 py-2 text-sm text-white placeholder-muted-foreground focus:border-primary/50 focus:outline-none w-64" - data-testid="input-search" - /> +
+ + + + +
+ + setSearchQuery(e.target.value)} + className="bg-card border border-white/10 pl-10 pr-4 py-2 text-sm text-white placeholder-muted-foreground focus:border-primary/50 focus:outline-none w-64" + data-testid="input-search" + /> +
+ {selectedIds.size > 0 && ( +
+ {selectedIds.size} selected + + + +
+ +
+ )} + + {selectedIds.size === 0 && ( +
+ +
+ )} + {/* Table */}
+ @@ -141,19 +285,28 @@ export default function AdminArchitects() { {isLoading ? ( - ) : filteredProfiles.length === 0 ? ( - ) : ( filteredProfiles.map((profile: any) => ( - + +
+ + User Role Level
+ Loading...
+ No architects found
+ +
>(() => { + const saved = localStorage.getItem('aethex-widget-visibility'); + return saved ? JSON.parse(saved) : { clock: true, weather: true, status: true, notifications: true, leaderboard: true, pipeline: true, kpi: true, heartbeat: true }; + }); + const [showWidgetSettings, setShowWidgetSettings] = useState(false); + const [mobileWidgetsOpen, setMobileWidgetsOpen] = useState(false); + const [isMobile, setIsMobile] = useState(false); + + useEffect(() => { + const checkMobile = () => setIsMobile(window.innerWidth < 768); + checkMobile(); + window.addEventListener('resize', checkMobile); + return () => window.removeEventListener('resize', checkMobile); + }, []); + + const toggleWidgetVisibility = (id: string) => { + setWidgetVisibility(prev => { + const updated = { ...prev, [id]: !prev[id] }; + localStorage.setItem('aethex-widget-visibility', JSON.stringify(updated)); + return updated; + }); + }; + + const resetWidgetPositions = () => { + const defaults = getDefaultWidgetPositions(); + setWidgetPositions(defaults); + setPositionResetKey(k => k + 1); + localStorage.setItem('aethex-widget-positions', JSON.stringify(defaults)); + }; + + const widgetOptions = [ + { id: 'clock', label: 'Clock' }, + { id: 'weather', label: 'Weather' }, + { id: 'status', label: 'System Status' }, + { id: 'notifications', label: 'Notifications' }, + { id: 'leaderboard', label: 'Leaderboard' }, + { id: 'pipeline', label: 'Pipeline' }, + { id: 'kpi', label: 'KPI Dashboard' }, + { id: 'heartbeat', label: 'Network Heartbeat' }, + ]; const { data: metrics } = useQuery({ queryKey: ['os-metrics'], @@ -1234,21 +1275,190 @@ function DesktopWidgets({ time, weather, notifications }: { return { color: 'text-cyan-400', icon: }; }; - return ( -
- -
-
- {time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} -
-
- {time.toLocaleDateString([], { weekday: 'long', month: 'short', day: 'numeric' })} -
-
-
+ if (isMobile) { + return ( + <> + + + {mobileWidgetsOpen && ( + +
+ Widgets + +
+
+ {widgetVisibility.clock !== false && ( +
+
+ {time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} +
+
+ {time.toLocaleDateString([], { weekday: 'long', month: 'short', day: 'numeric' })} +
+
+ )} + {widgetVisibility.weather !== false && weather?.current_weather && ( +
+
Weather
+
+ {getWeatherIcon(weather.current_weather.weathercode)} +
+
{Math.round(weather.current_weather.temperature)}°F
+
Wind: {weather.current_weather.windspeed} mph
+
+
+
+ )} + {widgetVisibility.status !== false && metrics && ( +
+
System Status
+
+
+ Architects + {metrics.totalProfiles || 0} +
+
+ Projects + {metrics.totalProjects || 0} +
+
+ Verified + {metrics.verifiedUsers || 0} +
+
+ Online + {metrics.onlineUsers || 0} +
+
+
+ )} + {widgetVisibility.notifications !== false && notifications && notifications.length > 0 && ( +
+
Notifications
+
+ {notifications.slice(0, 4).map((n, i) => { + const cat = getNotificationCategory(n); + return ( +
+ {cat.icon} + {n} +
+ ); + })} +
+
+ )} + {widgetVisibility.leaderboard !== false && leaderboard && leaderboard.length > 0 && ( +
+
+ + Top Architects +
+
+ {leaderboard.map((arch: any, i: number) => ( +
+ + {i + 1} + + {arch.username || arch.display_name} + Lv{arch.level || 1} +
+ ))} +
+
+ )} +
+
+ )} +
+ + ); + } - {weather?.current_weather && ( - + return ( +
+ + + + {showWidgetSettings && ( + setShowWidgetSettings(false)} + > + e.stopPropagation()} + > +
+

Widget Settings

+ +
+
+ {widgetOptions.map(opt => ( + + ))} +
+ +
+
+ )} +
+ + {widgetVisibility.clock !== false && ( + +
+
+ {time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} +
+
+ {time.toLocaleDateString([], { weekday: 'long', month: 'short', day: 'numeric' })} +
+
+
+ )} + + {widgetVisibility.weather !== false && weather?.current_weather && ( +
Weather
@@ -1262,8 +1472,8 @@ function DesktopWidgets({ time, weather, notifications }: { )} - {metrics && ( - + {widgetVisibility.status !== false && metrics && ( +
System Status
@@ -1294,8 +1504,8 @@ function DesktopWidgets({ time, weather, notifications }: { )} - {notifications && notifications.length > 0 && ( - + {widgetVisibility.notifications !== false && notifications && notifications.length > 0 && ( +
Notifications
@@ -1313,8 +1523,8 @@ function DesktopWidgets({ time, weather, notifications }: { )} - {leaderboard && leaderboard.length > 0 && ( - + {widgetVisibility.leaderboard !== false && leaderboard && leaderboard.length > 0 && ( +
@@ -1335,8 +1545,8 @@ function DesktopWidgets({ time, weather, notifications }: { )} - {metrics && ( - + {widgetVisibility.pipeline !== false && metrics && ( +
@@ -1375,8 +1585,8 @@ function DesktopWidgets({ time, weather, notifications }: { )} - {metrics && ( - + {widgetVisibility.kpi !== false && metrics && ( +
@@ -1404,30 +1614,32 @@ function DesktopWidgets({ time, weather, notifications }: { )} - -
-
- - Network Pulse -
-
- + {widgetVisibility.heartbeat !== false && ( + +
+
+ + Network Pulse +
+
- + animate={{ scale: [1, 1.2, 1] }} + transition={{ repeat: Infinity, duration: 1.5, ease: "easeInOut" }} + className="w-8 h-8 rounded-full bg-red-500/20 flex items-center justify-center" + > + + +
+
+ All Systems Operational +
-
- All Systems Operational -
-
- + + )}
); }