diff --git a/src/routes/Apps/Apps.scss b/src/routes/Apps/Apps.scss index a301935..360285f 100644 --- a/src/routes/Apps/Apps.scss +++ b/src/routes/Apps/Apps.scss @@ -11,6 +11,76 @@ @extend .page-content; } +// Compact stats row +.stats-row { + display: flex; + align-items: center; + gap: $spacing-md; + margin-bottom: $spacing-xl; + flex-wrap: wrap; +} + +.stat-chip { + display: flex; + align-items: center; + gap: $spacing-sm; + padding: $spacing-sm $spacing-md; + background: white; + border: 2px solid $border-color; + border-radius: $radius-md; + cursor: pointer; + transition: all $transition-base; + font-size: $font-size-base; + + &:hover:not(:disabled) { + border-color: $primary; + transform: translateY(-2px); + box-shadow: $shadow-sm; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + &.active { + border-color: $primary; + background: rgba($primary, 0.05); + } + + .stat-label { + color: $text-secondary; + font-size: $font-size-sm; + font-weight: $font-weight-medium; + } + + .stat-value { + color: $text-primary; + font-size: $font-size-xl; + font-weight: $font-weight-bold; + min-width: 24px; + text-align: center; + } +} + +.reset-filters-btn { + padding: $spacing-sm $spacing-md; + background: $bg-secondary; + border: 2px solid $border-color; + border-radius: $radius-md; + cursor: pointer; + transition: all $transition-base; + font-size: $font-size-sm; + font-weight: $font-weight-medium; + color: $text-secondary; + + &:hover { + background: white; + color: $text-primary; + border-color: $primary; + } +} + // Apps table specific styles .apps-table-container { @include card; @@ -114,21 +184,28 @@ .action-btn { background: none; border: 1px solid $border-color; - padding: $spacing-xs $spacing-sm; + padding: $spacing-xs $spacing-md; border-radius: $radius-sm; cursor: pointer; - font-size: $font-size-base; + font-size: $font-size-sm; color: $text-primary; + font-weight: $font-weight-medium; transition: all $transition-base; &:hover { - background-color: $bg-tertiary; - transform: scale(1.1); + background-color: $primary; + color: white; + border-color: $primary; } &.upgrade { border-color: $warning; color: $warning; + + &:hover { + background-color: $warning; + color: white; + } } } -} +} \ No newline at end of file diff --git a/src/routes/Apps/Apps.tsx b/src/routes/Apps/Apps.tsx index 1901f5a..9ca5703 100644 --- a/src/routes/Apps/Apps.tsx +++ b/src/routes/Apps/Apps.tsx @@ -14,7 +14,8 @@ export const Apps: React.FC = () => { const [filterServer, setFilterServer] = useState('all'); const [filterStatus, setFilterStatus] = useState('all'); const [showUpgradesOnly, setShowUpgradesOnly] = useState(false); - + const [showChaosOnly, setShowChaosOnly] = useState(false); + const isMockMode = import.meta.env.VITE_MOCK_AUTH === 'true'; useEffect(() => { @@ -86,6 +87,28 @@ export const Apps: React.FC = () => { return { total, needsUpgrade, chaosApps, totalServers }; }, [allApps, servers]); + const resetFilters = () => { + setSearchTerm(''); + setFilterServer('all'); + setShowChaosOnly(false); + setShowUpgradesOnly(false); + }; + + const toggleUpgrades = () => setShowUpgradesOnly(prev => !prev); + const toggleChaos = () => setShowChaosOnly(prev => !prev); + + // Show only apps that need upgrades + const filterByUpgrades = () => { + setShowUpgradesOnly(true); + setFilterStatus('all'); + }; + + // Show only chaos apps + const filterByChaos = () => { + setFilterStatus('chaos'); + setShowUpgradesOnly(false); + }; + if (loading) { return (
@@ -117,32 +140,46 @@ export const Apps: React.FC = () => {

{stats.total} apps across {stats.totalServers} servers

- {/* Stats Overview */} -
-
-
-

{stats.total}

-

Total Apps

-
-
-
-
-

{stats.needsUpgrade}

-

Upgrades Available

-
-
-
-
-

{stats.chaosApps}

-

Chaos Mode

-
-
-
-
-

{stats.totalServers}

-

Servers

-
-
+ {/* Compact Stats Overview */} +
+ + + + + + + +
{/* Filters */} @@ -161,21 +198,6 @@ export const Apps: React.FC = () => { ))} - - - -
{/* Apps Table */} diff --git a/src/routes/Servers/Server.tsx b/src/routes/Servers/Server.tsx index e2970ac..94e7d88 100644 --- a/src/routes/Servers/Server.tsx +++ b/src/routes/Servers/Server.tsx @@ -175,7 +175,7 @@ export const Server: React.FC = () => { {server.host} {server.upgradeCount > 0 && ( - ⬆️ {server.upgradeCount} upgrade{server.upgradeCount !== 1 ? 's' : ''} + {server.upgradeCount} upgrade{server.upgradeCount !== 1 ? 's' : ''} )} @@ -187,14 +187,14 @@ export const Server: React.FC = () => { onClick={() => handleAction('refresh')} disabled={!!actionLoading} > - {actionLoading === 'refresh' ? 'Refreshing...' : '🔄 Refresh'} + {actionLoading === 'refresh' ? 'Refreshing...' : 'Refresh'} diff --git a/src/routes/Servers/Servers.tsx b/src/routes/Servers/Servers.tsx index 5c0170a..ac26ed5 100644 --- a/src/routes/Servers/Servers.tsx +++ b/src/routes/Servers/Servers.tsx @@ -20,59 +20,46 @@ export const Servers: React.FC = () => { const [error, setError] = useState(''); const [searchTerm, setSearchTerm] = useState(''); const [sortBy, setSortBy] = useState<'name' | 'apps' | 'upgrades'>('name'); + const [showUpgradesOnly, setShowUpgradesOnly] = useState(false); const isMockMode = import.meta.env.VITE_MOCK_AUTH === 'true'; useEffect(() => { const fetchData = async () => { try { + let serversData, appsData; + if (isMockMode) { const { mockApiService } = await import('../../services/mockApi'); - const [serversData, appsData] = await Promise.all([ + [serversData, appsData] = await Promise.all([ mockApiService.getServers(), mockApiService.getAppsGrouped(), ]); - - // Enrich servers with stats from apps data - const enrichedServers = serversData.map(server => { - const serverStats = appsData[server.name]; - const chaosCount = serverStats?.apps.filter(app => app.chaos === 'true').length || 0; - - return { - ...server, - appCount: serverStats?.appCount || 0, - versionCount: serverStats?.versionCount || 0, - latestCount: serverStats?.latestCount || 0, - upgradeCount: serverStats?.upgradeCount || 0, - chaosCount, - }; - }); - - setServers(enrichedServers); } else { - const [serversData, appsData] = await Promise.all([ + [serversData, appsData] = await Promise.all([ apiService.getServers(), apiService.getAppsGrouped(), ]); - - // Enrich servers with stats from apps data - const enrichedServers = serversData.map(server => { - const serverStats = appsData[server.name]; - const chaosCount = serverStats?.apps.filter(app => app.chaos === 'true').length || 0; - - return { - ...server, - appCount: serverStats?.appCount || 0, - versionCount: serverStats?.versionCount || 0, - latestCount: serverStats?.latestCount || 0, - upgradeCount: serverStats?.upgradeCount || 0, - chaosCount, - }; - }); - - setServers(enrichedServers); } + + // Enrich servers with stats from apps data + const enrichedServers = serversData.map(server => { + const serverStats = appsData[server.name]; + const chaosCount = serverStats?.apps.filter(app => app.chaos === 'true').length || 0; + + return { + ...server, + appCount: serverStats?.appCount || 0, + versionCount: serverStats?.versionCount || 0, + latestCount: serverStats?.latestCount || 0, + upgradeCount: serverStats?.upgradeCount || 0, + chaosCount, + }; + }); + + setServers(enrichedServers); } catch (err) { + console.error('Error loading servers:', err); setError(err instanceof Error ? err.message : 'Failed to load servers'); } finally { setLoading(false); @@ -94,26 +81,32 @@ export const Servers: React.FC = () => { // Filter and sort servers const filteredServers = useMemo(() => { - const filtered = servers.filter(server => + const filtered = servers.filter(server => { + const matchesSearch = server.name.toLowerCase().includes(searchTerm.toLowerCase()) || - server.host.toLowerCase().includes(searchTerm.toLowerCase()) - ); + server.host.toLowerCase().includes(searchTerm.toLowerCase()); + const matchesUpgrades = !showUpgradesOnly || server.upgradeCount > 0; + return matchesSearch && matchesUpgrades; + }); - // Sort - filtered.sort((a, b) => { - switch (sortBy) { - case 'apps': - return b.appCount - a.appCount; - case 'upgrades': - return b.upgradeCount - a.upgradeCount; - case 'name': - default: - return a.name.localeCompare(b.name); - } - }); + filtered.sort((a, b) => { + switch (sortBy) { + case 'apps': + return b.appCount - a.appCount; + case 'name': + default: + return a.name.localeCompare(b.name); + } + }); - return filtered; - }, [servers, searchTerm, sortBy]); + return filtered; +}, [servers, searchTerm, sortBy, showUpgradesOnly]); + +const resetFilters = () => { + setSearchTerm(''); + setSortBy('name'); + setShowUpgradesOnly(false); +}; if (loading) { return ( @@ -146,36 +139,53 @@ export const Servers: React.FC = () => {

Managing {stats.totalServers} servers with {stats.totalApps} applications

- {/* Stats Overview */} -
-
-
-
-

{stats.totalServers}

-

Total Servers

-
-
-
-
-
-

{stats.totalApps}

-

Total Apps

-
-
-
-
-
-

{stats.totalUpgrades}

-

Apps Need Upgrade

-
-
-
-
-
-

{stats.totalChaos}

-

Chaos Apps

-
-
+ {/* Compact Stats Row */} +
+ + + + + + + + + {(searchTerm || sortBy !== 'name' || showUpgradesOnly) && ( + + )}
{/* Filters */} @@ -187,12 +197,6 @@ export const Servers: React.FC = () => { onChange={(e) => setSearchTerm(e.target.value)} className="search-input" /> - -
{/* Server Cards */} @@ -232,13 +236,17 @@ export const Servers: React.FC = () => { {server.upgradeCount > 0 && (
- Need Upgrade + + Need Upgrade + {server.upgradeCount}
)} {server.chaosCount > 0 && (
- Chaos Mode + + Chaos Mode + {server.chaosCount}
)} @@ -267,7 +275,6 @@ export const Servers: React.FC = () => { {server.upgradeCount > 0 && (
- ⚠️ {server.upgradeCount} app{server.upgradeCount > 1 ? 's' : ''} can be upgraded @@ -284,4 +291,4 @@ export const Servers: React.FC = () => {
); -}; \ No newline at end of file +}; diff --git a/src/services/mock-logs.json b/src/services/mock-logs.json index 2c5b626..5c19923 100644 --- a/src/services/mock-logs.json +++ b/src/services/mock-logs.json @@ -119,7 +119,7 @@ "logs": [ { "type": "info", - "text": "⬆️ Upgrading {appName} to {version}..." + "text": "Upgrading {appName} to {version}..." }, { "type": "command", @@ -287,7 +287,7 @@ "logs": [ { "type": "info", - "text": "⬆️ Upgrading all apps on {serverName}..." + "text": "Upgrading all apps on {serverName}..." }, { "type": "command",