app route
This commit is contained in:
@ -3,6 +3,7 @@ import { AuthProvider } from './context/AuthContext';
|
||||
import { LoginForm } from './routes/Login/LoginForm';
|
||||
import { Authenticated } from './routes/Login/Authenticated';
|
||||
import { Dashboard } from './routes/Authenticated/Dashboard/Dashboard';
|
||||
import { Apps } from './routes/Authenticated/Apps/Apps';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@ -27,7 +28,7 @@ function App() {
|
||||
path="/apps"
|
||||
element={
|
||||
<Authenticated>
|
||||
<div style={{ padding: '2rem' }}>Apps page - Coming soon</div>
|
||||
<Apps />
|
||||
</Authenticated>
|
||||
}
|
||||
/>
|
||||
|
||||
325
src/routes/Authenticated/Apps/Apps.scss
Normal file
325
src/routes/Authenticated/Apps/Apps.scss
Normal file
@ -0,0 +1,325 @@
|
||||
@use '../../../assets/scss/variables' as *;
|
||||
@use '../../../assets/scss/mixins' as *;
|
||||
|
||||
.apps-page {
|
||||
min-height: 100vh;
|
||||
background-color: $bg-secondary;
|
||||
}
|
||||
|
||||
.apps-content {
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
padding: $spacing-2xl $spacing-xl;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding: $spacing-xl $spacing-md;
|
||||
}
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: $spacing-2xl;
|
||||
|
||||
h1 {
|
||||
font-size: $font-size-3xl;
|
||||
margin: 0 0 $spacing-sm;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: $font-size-lg;
|
||||
color: $text-secondary;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: $spacing-lg;
|
||||
margin-bottom: $spacing-2xl;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
@include card;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-lg;
|
||||
padding: $spacing-xl;
|
||||
transition: transform $transition-base, box-shadow $transition-base;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: $shadow-lg;
|
||||
}
|
||||
|
||||
&.upgrade {
|
||||
border-left: 4px solid $warning;
|
||||
}
|
||||
|
||||
&.chaos {
|
||||
border-left: 4px solid $info;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
.stat-number {
|
||||
font-size: $font-size-3xl;
|
||||
font-weight: $font-weight-bold;
|
||||
color: $text-primary;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: $font-size-sm;
|
||||
color: $text-secondary;
|
||||
margin: $spacing-xs 0 0;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filters {
|
||||
@include card;
|
||||
display: flex;
|
||||
gap: $spacing-md;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: $spacing-xl;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
min-width: 250px;
|
||||
padding: $spacing-sm $spacing-md;
|
||||
border: 2px solid $border-color;
|
||||
border-radius: $radius-md;
|
||||
font-size: $font-size-base;
|
||||
transition: border-color $transition-base;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
padding: $spacing-sm $spacing-md;
|
||||
border: 2px solid $border-color;
|
||||
border-radius: $radius-md;
|
||||
font-size: $font-size-base;
|
||||
background-color: white;
|
||||
cursor: pointer;
|
||||
transition: border-color $transition-base;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-sm;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: $font-size-sm;
|
||||
color: $text-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.apps-table-container {
|
||||
@include card;
|
||||
overflow-x: auto;
|
||||
margin-bottom: $spacing-lg;
|
||||
}
|
||||
|
||||
.apps-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
thead {
|
||||
background-color: $bg-tertiary;
|
||||
|
||||
th {
|
||||
padding: $spacing-md $spacing-lg;
|
||||
text-align: left;
|
||||
font-weight: $font-weight-semibold;
|
||||
color: $text-primary;
|
||||
font-size: $font-size-sm;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
border-bottom: 2px solid $border-color;
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
border-bottom: 1px solid $border-color;
|
||||
transition: background-color $transition-base;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($primary, 0.05);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: $spacing-md $spacing-lg;
|
||||
font-size: $font-size-base;
|
||||
color: $text-primary;
|
||||
|
||||
&.no-results {
|
||||
text-align: center;
|
||||
padding: $spacing-3xl;
|
||||
color: $text-secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.app-name-cell {
|
||||
.app-name {
|
||||
font-weight: $font-weight-medium;
|
||||
color: $text-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.recipe-badge {
|
||||
display: inline-block;
|
||||
padding: $spacing-xs $spacing-sm;
|
||||
background-color: rgba($info, 0.1);
|
||||
color: darken($info, 20%);
|
||||
border-radius: $radius-sm;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
|
||||
.domain-link {
|
||||
color: $primary;
|
||||
text-decoration: none;
|
||||
transition: color $transition-base;
|
||||
|
||||
&:hover {
|
||||
color: $primary-dark;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.no-domain {
|
||||
color: $text-muted;
|
||||
}
|
||||
|
||||
.server-badge {
|
||||
display: inline-block;
|
||||
padding: $spacing-xs $spacing-sm;
|
||||
background-color: rgba($text-secondary, 0.1);
|
||||
color: $text-secondary;
|
||||
border-radius: $radius-sm;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
|
||||
.version-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-sm;
|
||||
|
||||
.version {
|
||||
font-family: monospace;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
|
||||
.chaos-badge,
|
||||
.upgrade-available {
|
||||
font-size: $font-size-base;
|
||||
}
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: $spacing-xs $spacing-sm;
|
||||
border-radius: $radius-full;
|
||||
font-size: $font-size-xs;
|
||||
font-weight: $font-weight-semibold;
|
||||
text-transform: capitalize;
|
||||
|
||||
&.status-deployed {
|
||||
background-color: rgba($success, 0.1);
|
||||
color: darken($success, 20%);
|
||||
}
|
||||
|
||||
&.status-stopped {
|
||||
background-color: rgba($text-muted, 0.1);
|
||||
color: $text-muted;
|
||||
}
|
||||
|
||||
&.status-error {
|
||||
background-color: rgba($error, 0.1);
|
||||
color: darken($error, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: $spacing-sm;
|
||||
|
||||
.action-btn {
|
||||
border: 1px solid $border-color;
|
||||
padding: $spacing-xs $spacing-sm;
|
||||
border-radius: $radius-sm;
|
||||
cursor: pointer;
|
||||
font-size: $font-size-base;
|
||||
transition: all $transition-base;
|
||||
|
||||
&:hover {
|
||||
background-color: $bg-tertiary;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
&.upgrade {
|
||||
border-color: $warning;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.results-count {
|
||||
text-align: center;
|
||||
color: $text-secondary;
|
||||
font-size: $font-size-sm;
|
||||
padding: $spacing-md;
|
||||
}
|
||||
|
||||
.loading,
|
||||
.error {
|
||||
text-align: center;
|
||||
padding: $spacing-3xl;
|
||||
font-size: $font-size-lg;
|
||||
}
|
||||
|
||||
.loading {
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: $error;
|
||||
}
|
||||
259
src/routes/Authenticated/Apps/Apps.tsx
Normal file
259
src/routes/Authenticated/Apps/Apps.tsx
Normal file
@ -0,0 +1,259 @@
|
||||
// routes/Apps/Apps.tsx
|
||||
import React, { useEffect, useState, useMemo } from 'react';
|
||||
import { Header } from '../../../components/Header/Header';
|
||||
import { apiService } from '../../../services/api';
|
||||
import type { AbraApp, AppWithServer, ServerAppsResponse } from '../../../types';
|
||||
import './Apps.scss';
|
||||
|
||||
export const Apps: React.FC = () => {
|
||||
const [appsData, setAppsData] = useState<ServerAppsResponse | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [filterServer, setFilterServer] = useState<string>('all');
|
||||
const [filterStatus, setFilterStatus] = useState<string>('all');
|
||||
const [showUpgradesOnly, setShowUpgradesOnly] = useState(false);
|
||||
|
||||
const isMockMode = import.meta.env.VITE_MOCK_AUTH === 'true';
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
if (isMockMode) {
|
||||
const { mockAppsData } = await import('../../../services/mockApi');
|
||||
setAppsData(mockAppsData);
|
||||
} else {
|
||||
const data = await apiService.getAppsGrouped();
|
||||
setAppsData(data);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to load apps');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, [isMockMode]);
|
||||
|
||||
// Flatten and enrich apps data
|
||||
const allApps: AppWithServer[] = useMemo(() => {
|
||||
if (!appsData) return [];
|
||||
|
||||
return Object.entries(appsData).flatMap(([serverName, serverData]) =>
|
||||
serverData.apps.map(app => ({
|
||||
...app,
|
||||
serverStats: {
|
||||
appCount: serverData.appCount,
|
||||
upgradeCount: serverData.upgradeCount,
|
||||
},
|
||||
}))
|
||||
);
|
||||
}, [appsData]);
|
||||
|
||||
// Get unique servers
|
||||
const servers = useMemo(() => {
|
||||
if (!appsData) return [];
|
||||
return Object.keys(appsData);
|
||||
}, [appsData]);
|
||||
|
||||
// Filter apps
|
||||
const filteredApps = useMemo(() => {
|
||||
return allApps.filter(app => {
|
||||
const matchesSearch =
|
||||
app.appName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
app.recipe.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
app.domain.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
|
||||
const matchesServer = filterServer === 'all' || app.server === filterServer;
|
||||
const matchesChaos = filterStatus === 'all' ||
|
||||
(filterStatus === 'chaos' && app.chaos === 'true') ||
|
||||
(filterStatus === 'stable' && app.chaos === 'false');
|
||||
const matchesUpgrade = !showUpgradesOnly || app.upgrade !== 'latest';
|
||||
|
||||
return matchesSearch && matchesServer && matchesChaos && matchesUpgrade;
|
||||
});
|
||||
}, [allApps, searchTerm, filterServer, filterStatus, showUpgradesOnly]);
|
||||
|
||||
const stats = useMemo(() => {
|
||||
const total = allApps.length;
|
||||
const needsUpgrade = allApps.filter(app => app.upgrade !== 'latest').length;
|
||||
const chaosApps = allApps.filter(app => app.chaos === 'true').length;
|
||||
const totalServers = servers.length;
|
||||
|
||||
return { total, needsUpgrade, chaosApps, totalServers };
|
||||
}, [allApps, servers]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="apps-page">
|
||||
<Header />
|
||||
<main className="apps-content">
|
||||
<div className="loading">Loading applications...</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="apps-page">
|
||||
<Header />
|
||||
<main className="apps-content">
|
||||
<div className="error">Error: {error}</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="apps-page">
|
||||
<Header />
|
||||
<main className="apps-content">
|
||||
<div className="page-header">
|
||||
<h1>Applications</h1>
|
||||
<p className="subtitle">{stats.total} apps across {stats.totalServers} servers</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Overview */}
|
||||
<div className="stats-grid">
|
||||
<div className="stat-card">
|
||||
<div className="stat-info">
|
||||
<p className="stat-number">{stats.total}</p>
|
||||
<p className="stat-label">Total Apps</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="stat-card upgrade">
|
||||
<div className="stat-info">
|
||||
<p className="stat-number">{stats.needsUpgrade}</p>
|
||||
<p className="stat-label">Upgrades Available</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="stat-card chaos">
|
||||
<div className="stat-info">
|
||||
<p className="stat-number">{stats.chaosApps}</p>
|
||||
<p className="stat-label">Chaos Mode</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-info">
|
||||
<p className="stat-number">{stats.totalServers}</p>
|
||||
<p className="stat-label">Servers</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<div className="filters">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search apps by name, recipe, or domain..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="search-input"
|
||||
/>
|
||||
|
||||
<select value={filterServer} onChange={(e) => setFilterServer(e.target.value)}>
|
||||
<option value="all">All Servers</option>
|
||||
{servers.map(server => (
|
||||
<option key={server} value={server}>{server}</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<select value={filterStatus} onChange={(e) => setFilterStatus(e.target.value)}>
|
||||
<option value="all">All Status</option>
|
||||
<option value="stable">Stable</option>
|
||||
<option value="chaos">Chaos Mode</option>
|
||||
</select>
|
||||
|
||||
<label className="checkbox-filter">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showUpgradesOnly}
|
||||
onChange={(e) => setShowUpgradesOnly(e.target.checked)}
|
||||
/>
|
||||
<span>Show only apps with upgrades</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Apps Table */}
|
||||
<div className="apps-table-container">
|
||||
<table className="apps-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>App Name</th>
|
||||
<th>Recipe</th>
|
||||
<th>Domain</th>
|
||||
<th>Server</th>
|
||||
<th>Version</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredApps.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={7} className="no-results">
|
||||
No apps found matching your filters
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
filteredApps.map((app, index) => (
|
||||
<tr key={`${app.server}-${app.appName}-${index}`}>
|
||||
<td className="app-name-cell">
|
||||
<span className="app-name">{app.appName}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span className="recipe-badge">{app.recipe}</span>
|
||||
</td>
|
||||
<td>
|
||||
{app.domain ? (
|
||||
<a href={`https://${app.domain}`} target="_blank" rel="noopener noreferrer" className="domain-link">
|
||||
{app.domain}
|
||||
</a>
|
||||
) : (
|
||||
<span className="no-domain">—</span>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<span className="server-badge">{app.server}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div className="version-cell">
|
||||
<span className="version">{app.version}</span>
|
||||
{app.chaos === 'true' && (
|
||||
<span className="chaos-badge" title="Chaos mode enabled">☠️</span>
|
||||
)}
|
||||
{app.upgrade !== 'latest' && (
|
||||
<span className="upgrade-available" title="Upgrade available">
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span className={`status-badge status-${app.status}`}>
|
||||
{app.status}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div className="actions">
|
||||
<button className="action-btn" title="View details">details</button>
|
||||
{app.upgrade !== 'latest' && (
|
||||
<button className="action-btn upgrade" title="Upgrade">upgrade</button>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="results-count">
|
||||
Showing {filteredApps.length} of {allApps.length} apps
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -19,18 +19,24 @@ export const Dashboard: React.FC = () => {
|
||||
// Use mock API in development
|
||||
const { mockApiService } = await import('../../../services/mockApi');
|
||||
const [appsData, serversData] = await Promise.all([
|
||||
mockApiService.getApps(),
|
||||
mockApiService.getAppsGrouped(),
|
||||
mockApiService.getServers(),
|
||||
]);
|
||||
setApps(appsData);
|
||||
|
||||
// Flatten the grouped apps data
|
||||
const flatApps = Object.values(appsData).flatMap(serverData => serverData.apps);
|
||||
setApps(flatApps);
|
||||
setServers(serversData);
|
||||
} else {
|
||||
// Use real API in production
|
||||
const [appsData, serversData] = await Promise.all([
|
||||
apiService.getApps(),
|
||||
apiService.getAppsGrouped(),
|
||||
apiService.getServers(),
|
||||
]);
|
||||
setApps(appsData);
|
||||
|
||||
// Flatten the grouped apps data
|
||||
const flatApps = Object.values(appsData).flatMap(serverData => serverData.apps);
|
||||
setApps(flatApps);
|
||||
setServers(serversData);
|
||||
}
|
||||
} catch (err) {
|
||||
@ -76,7 +82,7 @@ export const Dashboard: React.FC = () => {
|
||||
<h3>Apps</h3>
|
||||
<p className="stat-number">{apps.length}</p>
|
||||
<p className="stat-label">
|
||||
{apps.filter(a => a.status === 'running').length} running
|
||||
{apps.filter(a => a.status === 'deployed').length} deployed
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -84,7 +90,7 @@ export const Dashboard: React.FC = () => {
|
||||
<h3>Servers</h3>
|
||||
<p className="stat-number">{servers.length}</p>
|
||||
<p className="stat-label">
|
||||
{servers.filter(s => s.connected).length} connected
|
||||
{servers.length} connected
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -92,11 +98,12 @@ export const Dashboard: React.FC = () => {
|
||||
<section className="recent-apps">
|
||||
<h3>Recent Applications</h3>
|
||||
<div className="apps-list">
|
||||
{apps.slice(0, 5).map((app) => (
|
||||
<div key={app.id} className="app-item">
|
||||
{apps.slice(0, 5).map((app, index) => (
|
||||
<div key={`${app.server}-${app.appName}-${index}`} className="app-item">
|
||||
<div className="app-info">
|
||||
<h4>{app.name}</h4>
|
||||
<p className="app-domain">{app.domain}</p>
|
||||
<h4>{app.appName}</h4>
|
||||
<p className="app-domain">{app.domain || 'No domain'}</p>
|
||||
<p className="app-server">{app.server}</p>
|
||||
</div>
|
||||
<span className={`status-badge status-${app.status}`}>
|
||||
{app.status}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// services/api.ts
|
||||
|
||||
import type { AbraApp, AbraServer, AuthResponse, LoginCredentials, User } from '../types';
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000/api';
|
||||
@ -58,8 +60,8 @@ class ApiService {
|
||||
}
|
||||
|
||||
// Abra CLI wrapper methods
|
||||
async getApps(): Promise<AbraApp[]> {
|
||||
return this.request<AbraApp[]>('/abra/apps');
|
||||
async getAppsGrouped(): Promise<ServerAppsResponse> {
|
||||
return this.request<ServerAppsResponse>('/abra/apps');
|
||||
}
|
||||
|
||||
async getServers(): Promise<AbraServer[]> {
|
||||
|
||||
593
src/services/example abra app ls.json
Normal file
593
src/services/example abra app ls.json
Normal file
@ -0,0 +1,593 @@
|
||||
{
|
||||
"mydomain.com": {
|
||||
"apps": [
|
||||
{
|
||||
"server": "mydomain.com",
|
||||
"recipe": "authentik",
|
||||
"appName": "accounts.mydomain.com",
|
||||
"domain": "accounts.mydomain.com",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "unknown",
|
||||
"version": "7.4.0+2025.6.3",
|
||||
"upgrade": "9.0.1+2025.8.1\n9.0.0+2025.8.1\n8.0.0+2025.8.1\n7.4.1+2025.6.4"
|
||||
},
|
||||
{
|
||||
"server": "mydomain.com",
|
||||
"recipe": "backup-bot-two",
|
||||
"appName": "backup.mydomain.com",
|
||||
"domain": "",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "2.3.0+2.3.0-beta",
|
||||
"version": "2.3.0+2.3.0-beta",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "mydomain.com",
|
||||
"recipe": "collabora",
|
||||
"appName": "docs.mydomain.com",
|
||||
"domain": "docs.mydomain.com",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "3.2.0+24.04.12.3.1",
|
||||
"version": "3.2.0+24.04.12.3.1",
|
||||
"upgrade": "4.0.0+25.04.4.1.1\n3.3.0+24.04.13.3.1"
|
||||
},
|
||||
{
|
||||
"server": "mydomain.com",
|
||||
"recipe": "nextcloud",
|
||||
"appName": "nc.mydomain.com",
|
||||
"domain": "nc.mydomain.com",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "unknown",
|
||||
"version": "12.0.1+31.0.6-fpm",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "mydomain.com",
|
||||
"recipe": "traefik",
|
||||
"appName": "traefik.mydomain.com",
|
||||
"domain": "traefik.mydomain.com",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "unknown",
|
||||
"version": "3.4.2+v3.4.5",
|
||||
"upgrade": "3.6.2+v3.4.5\n3.6.1+v3.4.5\n3.6.0+v3.4.5\n3.5.0+v3.4.5"
|
||||
}
|
||||
],
|
||||
"appCount": 5,
|
||||
"versionCount": 5,
|
||||
"unversionedCount": 0,
|
||||
"latestCount": 2,
|
||||
"upgradeCount": 3
|
||||
},
|
||||
"coop.test.org": {
|
||||
"apps": [
|
||||
{
|
||||
"server": "coop.test.org",
|
||||
"recipe": "cryptpad",
|
||||
"appName": "cryptpad.coop.test.org",
|
||||
"domain": "cryptpad.coop.test.org",
|
||||
"status": "deployed",
|
||||
"chaos": "true",
|
||||
"chaosVersion": "cb2a47fb",
|
||||
"version": "0.4.0+version-2024.3.0",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "coop.test.org",
|
||||
"recipe": "garage",
|
||||
"appName": "garage.coop.test.org",
|
||||
"domain": "garage.coop.test.org",
|
||||
"status": "deployed",
|
||||
"chaos": "true",
|
||||
"chaosVersion": "3a729d56+U",
|
||||
"version": "3a729d56+U",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "coop.test.org",
|
||||
"recipe": "mobilizon",
|
||||
"appName": "mobilizon.test.org",
|
||||
"domain": "mobilizon.test.org",
|
||||
"status": "deployed",
|
||||
"chaos": "true",
|
||||
"chaosVersion": "f8f874a5 + unstaged changes",
|
||||
"version": "0.2.1+5.1.2",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "coop.test.org",
|
||||
"recipe": "owncast",
|
||||
"appName": "tv.test.org",
|
||||
"domain": "tv.test.org",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "0.3.0+0.2.1",
|
||||
"version": "0.3.0+0.2.1",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "coop.test.org",
|
||||
"recipe": "privatebin",
|
||||
"appName": "paste.test.org",
|
||||
"domain": "paste.test.org",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "0.6.1+0.17.4",
|
||||
"version": "0.6.0+1.7.6",
|
||||
"upgrade": "0.6.1+0.17.4"
|
||||
},
|
||||
{
|
||||
"server": "coop.test.org",
|
||||
"recipe": "traefik",
|
||||
"appName": "traefik.coop.test.org",
|
||||
"domain": "traefik.coop.test.org",
|
||||
"status": "deployed",
|
||||
"chaos": "true",
|
||||
"chaosVersion": "27d5c092",
|
||||
"version": "27d5c092",
|
||||
"upgrade": "latest"
|
||||
}
|
||||
],
|
||||
"appCount": 6,
|
||||
"versionCount": 6,
|
||||
"unversionedCount": 0,
|
||||
"latestCount": 5,
|
||||
"upgradeCount": 1
|
||||
},
|
||||
"internal.website.com": {
|
||||
"apps": [
|
||||
{
|
||||
"server": "internal.website.com",
|
||||
"recipe": "backup-bot-two",
|
||||
"appName": "backup.internal.website.com",
|
||||
"domain": "backup.internal.website.com",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "unknown",
|
||||
"version": "2.3.0+2.3.0-beta",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "internal.website.com",
|
||||
"recipe": "baserow",
|
||||
"appName": "base.website.com",
|
||||
"domain": "base.website.com",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "unknown",
|
||||
"version": "1.1.0+1.31.1",
|
||||
"upgrade": "1.2.1+1.35.3\n1.2.0+1.35.3"
|
||||
},
|
||||
{
|
||||
"server": "internal.website.com",
|
||||
"recipe": "traefik",
|
||||
"appName": "traefik.internal.website.com",
|
||||
"domain": "traefik.internal.website.com",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "unknown",
|
||||
"version": "3.6.2+v3.4.5",
|
||||
"upgrade": "latest"
|
||||
}
|
||||
],
|
||||
"appCount": 3,
|
||||
"versionCount": 3,
|
||||
"unversionedCount": 0,
|
||||
"latestCount": 2,
|
||||
"upgradeCount": 1
|
||||
},
|
||||
"internal.server.net": {
|
||||
"apps": [
|
||||
{
|
||||
"server": "internal.server.net",
|
||||
"recipe": "authentik",
|
||||
"appName": "accounts.server.net",
|
||||
"domain": "accounts.server.net",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "6.11.0+2024.10.5",
|
||||
"version": "6.11.0+2024.10.5",
|
||||
"upgrade": "9.0.1+2025.8.1\n9.0.0+2025.8.1\n8.0.0+2025.8.1\n7.4.1+2025.6.4\n7.4.0+2025.6.3\n7.3.2+2025.6.2\n7.3.1+2025.6.1\n7.3.0+2025.6.0\n7.2.0+2025.4.1\n7.1.0+2025.2.4\n7.0.3+2025.2.3\n7.0.2+2025.2.2\n7.0.1+2025.2.0\n7.0.0+2025.2.0\n6.12.0+2024.12.3\n6.11.1+2024.10.5"
|
||||
},
|
||||
{
|
||||
"server": "internal.server.net",
|
||||
"recipe": "authentik",
|
||||
"appName": "accounts.test.server.net",
|
||||
"domain": "accounts.test.server.net",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "7.1.0+2025.2.4",
|
||||
"version": "7.1.0+2025.2.4",
|
||||
"upgrade": "9.0.1+2025.8.1\n9.0.0+2025.8.1\n8.0.0+2025.8.1\n7.4.1+2025.6.4\n7.4.0+2025.6.3\n7.3.2+2025.6.2\n7.3.1+2025.6.1\n7.3.0+2025.6.0\n7.2.0+2025.4.1"
|
||||
},
|
||||
{
|
||||
"server": "internal.server.net",
|
||||
"recipe": "backup-bot-two",
|
||||
"appName": "backup.server.net",
|
||||
"domain": "",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "2.3.0+2.3.0-beta",
|
||||
"version": "2.3.0+2.3.0-beta",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "internal.server.net",
|
||||
"recipe": "custom-html",
|
||||
"appName": "counterspy.zip",
|
||||
"domain": "counterspy.zip",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "unknown",
|
||||
"version": "1.11.0+1.29.0",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "internal.server.net",
|
||||
"recipe": "garage",
|
||||
"appName": "garage.server.net",
|
||||
"domain": "garage.server.net",
|
||||
"status": "deployed",
|
||||
"chaos": "true",
|
||||
"chaosVersion": "20bfd2c6+U",
|
||||
"version": "20bfd2c6+U",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "internal.server.net",
|
||||
"recipe": "gitlab",
|
||||
"appName": "git.server.net",
|
||||
"domain": "git.server.net",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "unknown",
|
||||
"version": "0.2.2+18.3.0-ce.0",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "internal.server.net",
|
||||
"recipe": "jitsi",
|
||||
"appName": "jitsi.server.net",
|
||||
"domain": "jitsi.server.net",
|
||||
"status": "deployed",
|
||||
"chaos": "true",
|
||||
"chaosVersion": "d3dd84ab + unstaged changes",
|
||||
"version": "0.0.1+8719",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "internal.server.net",
|
||||
"recipe": "liberaforms",
|
||||
"appName": "forms.test.server.net",
|
||||
"domain": "forms.test.server.net",
|
||||
"status": "deployed",
|
||||
"chaos": "true",
|
||||
"chaosVersion": "b3e74fa7+U",
|
||||
"version": "b3e74fa7+U",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "internal.server.net",
|
||||
"recipe": "mattermost",
|
||||
"appName": "mattermost-demo.server.net",
|
||||
"domain": "mattermost-demo.server.net",
|
||||
"status": "deployed",
|
||||
"chaos": "true",
|
||||
"chaosVersion": "52580808",
|
||||
"version": "52580808",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "internal.server.net",
|
||||
"recipe": "openemr",
|
||||
"appName": "openemr.server.net",
|
||||
"domain": "openemr.server.net",
|
||||
"status": "deployed",
|
||||
"chaos": "true",
|
||||
"chaosVersion": "6b62e331",
|
||||
"version": "6b62e331",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "internal.server.net",
|
||||
"recipe": "outline",
|
||||
"appName": "wiki.server.net",
|
||||
"domain": "wiki.server.net",
|
||||
"status": "deployed",
|
||||
"chaos": "true",
|
||||
"chaosVersion": "ab70b3c4+U",
|
||||
"version": "ab70b3c4+U",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "internal.server.net",
|
||||
"recipe": "statping",
|
||||
"appName": "status.server.net",
|
||||
"domain": "status.server.net",
|
||||
"status": "deployed",
|
||||
"chaos": "true",
|
||||
"chaosVersion": "bd875e08",
|
||||
"version": "0.1.1+v0.90.78",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "internal.server.net",
|
||||
"recipe": "traefik",
|
||||
"appName": "traefik.server.net",
|
||||
"domain": "traefik.server.net",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "unknown",
|
||||
"version": "3.6.2+v3.4.5",
|
||||
"upgrade": "latest"
|
||||
}
|
||||
],
|
||||
"appCount": 13,
|
||||
"versionCount": 13,
|
||||
"unversionedCount": 0,
|
||||
"latestCount": 11,
|
||||
"upgradeCount": 2
|
||||
},
|
||||
"internal.intranet.site.com": {
|
||||
"apps": [
|
||||
{
|
||||
"server": "internal.intranet.site.com",
|
||||
"recipe": "backup-bot-two",
|
||||
"appName": "backup.intranet.site.com",
|
||||
"domain": "",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "2.3.0+2.3.0-beta",
|
||||
"version": "2.3.0+2.3.0-beta",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "internal.intranet.site.com",
|
||||
"recipe": "baserow",
|
||||
"appName": "base.intranet.site.com",
|
||||
"domain": "base.intranet.site.com",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "unknown",
|
||||
"version": "1.1.0+1.31.1",
|
||||
"upgrade": "1.2.1+1.35.3\n1.2.0+1.35.3"
|
||||
},
|
||||
{
|
||||
"server": "internal.intranet.site.com",
|
||||
"recipe": "traefik",
|
||||
"appName": "traefik.intranet.site.com",
|
||||
"domain": "traefik.intranet.site.com",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "unknown",
|
||||
"version": "3.4.2+v3.4.5",
|
||||
"upgrade": "3.6.2+v3.4.5\n3.6.1+v3.4.5\n3.6.0+v3.4.5\n3.5.0+v3.4.5"
|
||||
},
|
||||
{
|
||||
"server": "internal.intranet.site.com",
|
||||
"recipe": "vaultwarden",
|
||||
"appName": "pass.intranet.site.com",
|
||||
"domain": "pass.intranet.site.com",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "unknown",
|
||||
"version": "2.0.0+1.33.2",
|
||||
"upgrade": "2.1.0+1.34.1"
|
||||
}
|
||||
],
|
||||
"appCount": 4,
|
||||
"versionCount": 4,
|
||||
"unversionedCount": 0,
|
||||
"latestCount": 1,
|
||||
"upgradeCount": 3
|
||||
},
|
||||
"internal.test.org": {
|
||||
"apps": [
|
||||
{
|
||||
"server": "internal.test.org",
|
||||
"recipe": "authentik",
|
||||
"appName": "accounts.test.org",
|
||||
"domain": "accounts.test.org",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "unknown",
|
||||
"version": "9.0.0+2025.8.1",
|
||||
"upgrade": "9.0.1+2025.8.1"
|
||||
},
|
||||
{
|
||||
"server": "internal.test.org",
|
||||
"recipe": "backup-bot-two",
|
||||
"appName": "backup.test.org",
|
||||
"domain": "",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "unknown",
|
||||
"version": "2.3.0+2.3.0-beta",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "internal.test.org",
|
||||
"recipe": "collabora",
|
||||
"appName": "docs.test.org",
|
||||
"domain": "docs.test.org",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "3.1.0+24.04.10.2.1",
|
||||
"version": "3.1.0+24.04.10.2.1",
|
||||
"upgrade": "4.0.0+25.04.4.1.1\n3.3.0+24.04.13.3.1\n3.2.0+24.04.12.3.1"
|
||||
},
|
||||
{
|
||||
"server": "internal.test.org",
|
||||
"recipe": "custom-html",
|
||||
"appName": "test.org",
|
||||
"domain": "test.org",
|
||||
"status": "deployed",
|
||||
"chaos": "true",
|
||||
"chaosVersion": "ba1c2269",
|
||||
"version": "1.7.1+1.27.2",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "internal.test.org",
|
||||
"recipe": "garage",
|
||||
"appName": "garage.test.org",
|
||||
"domain": "garage.test.org",
|
||||
"status": "deployed",
|
||||
"chaos": "true",
|
||||
"chaosVersion": "9ca66f0f",
|
||||
"version": "9ca66f0f",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "internal.test.org",
|
||||
"recipe": "nextcloud",
|
||||
"appName": "nc.test.org",
|
||||
"domain": "nc.test.org",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "11.0.0+30.0.4-fpm",
|
||||
"version": "11.0.0+30.0.4-fpm",
|
||||
"upgrade": "12.0.1+31.0.6-fpm\n12.0.0+31.0.6-fpm\n11.4.0+30.0.10-fpm\n11.4.0+30.0.6-fpm\n11.3.0+30.0.6-fpm\n11.2.0+30.0.6-fpm\n11.1.0+30.0.6-fpm\n11.0.1+30.0.4-fpm"
|
||||
},
|
||||
{
|
||||
"server": "internal.test.org",
|
||||
"recipe": "outline",
|
||||
"appName": "wiki.test.org",
|
||||
"domain": "wiki.test.org",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "2.8.1+0.81.1",
|
||||
"version": "2.8.1+0.81.1",
|
||||
"upgrade": "2.12.1+0.87.4\n2.12.0+0.87.3\n2.11.0+0.86.1\n2.10.0+0.84.0\n2.9.1+0.82.0\n2.9.0+0.82.0\n2.8.2+0.81.1"
|
||||
},
|
||||
{
|
||||
"server": "internal.test.org",
|
||||
"recipe": "traefik",
|
||||
"appName": "traefik.test.org",
|
||||
"domain": "traefik.test.org",
|
||||
"status": "deployed",
|
||||
"chaos": "true",
|
||||
"chaosVersion": "993ed9cf",
|
||||
"version": "993ed9cf",
|
||||
"upgrade": "latest"
|
||||
}
|
||||
],
|
||||
"appCount": 8,
|
||||
"versionCount": 8,
|
||||
"unversionedCount": 0,
|
||||
"latestCount": 4,
|
||||
"upgradeCount": 4
|
||||
},
|
||||
"orgsite.org": {
|
||||
"apps": [
|
||||
{
|
||||
"server": "orgsite.org",
|
||||
"recipe": "authentik",
|
||||
"appName": "accounts.orgsite.org",
|
||||
"domain": "accounts.orgsite.org",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "6.11.0+2024.10.5",
|
||||
"version": "6.11.0+2024.10.5",
|
||||
"upgrade": "9.0.1+2025.8.1\n9.0.0+2025.8.1\n8.0.0+2025.8.1\n7.4.1+2025.6.4\n7.4.0+2025.6.3\n7.3.2+2025.6.2\n7.3.1+2025.6.1\n7.3.0+2025.6.0\n7.2.0+2025.4.1\n7.1.0+2025.2.4\n7.0.3+2025.2.3\n7.0.2+2025.2.2\n7.0.1+2025.2.0\n7.0.0+2025.2.0\n6.12.0+2024.12.3\n6.11.1+2024.10.5"
|
||||
},
|
||||
{
|
||||
"server": "orgsite.org",
|
||||
"recipe": "backup-bot-two",
|
||||
"appName": "backup.orgsite.org",
|
||||
"domain": "",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "2.3.0+2.3.0-beta",
|
||||
"version": "2.3.0+2.3.0-beta",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "orgsite.org",
|
||||
"recipe": "baserow",
|
||||
"appName": "base.orgsite.org",
|
||||
"domain": "base.orgsite.org",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "unknown",
|
||||
"version": "1.1.0+1.31.1",
|
||||
"upgrade": "1.2.1+1.35.3\n1.2.0+1.35.3"
|
||||
},
|
||||
{
|
||||
"server": "orgsite.org",
|
||||
"recipe": "collabora",
|
||||
"appName": "docs.orgsite.org",
|
||||
"domain": "docs.orgsite.org",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "3.1.0+24.04.10.2.1",
|
||||
"version": "3.1.0+24.04.10.2.1",
|
||||
"upgrade": "4.0.0+25.04.4.1.1\n3.3.0+24.04.13.3.1\n3.2.0+24.04.12.3.1"
|
||||
},
|
||||
{
|
||||
"server": "orgsite.org",
|
||||
"recipe": "jitsi",
|
||||
"appName": "meet.orgsite.org",
|
||||
"domain": "meet.orgsite.org",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "d3dd84ab",
|
||||
"version": "0.0.1+8719",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "orgsite.org",
|
||||
"recipe": "mattermost",
|
||||
"appName": "chat.orgsite.org",
|
||||
"domain": "chat.orgsite.org",
|
||||
"status": "deployed",
|
||||
"chaos": "true",
|
||||
"chaosVersion": "5f0295b9",
|
||||
"version": "2.1.0+10.5.0",
|
||||
"upgrade": "latest"
|
||||
},
|
||||
{
|
||||
"server": "orgsite.org",
|
||||
"recipe": "nextcloud",
|
||||
"appName": "nc.orgsite.org",
|
||||
"domain": "nc.orgsite.org",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "11.1.0+30.0.6-fpm",
|
||||
"version": "11.1.0+30.0.6-fpm",
|
||||
"upgrade": "12.0.1+31.0.6-fpm\n12.0.0+31.0.6-fpm\n11.4.0+30.0.10-fpm\n11.4.0+30.0.6-fpm\n11.3.0+30.0.6-fpm\n11.2.0+30.0.6-fpm"
|
||||
},
|
||||
{
|
||||
"server": "orgsite.org",
|
||||
"recipe": "traefik",
|
||||
"appName": "traefik.orgsite.org",
|
||||
"domain": "traefik.orgsite.org",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "unknown",
|
||||
"version": "3.4.2+v3.4.5",
|
||||
"upgrade": "3.6.2+v3.4.5\n3.6.1+v3.4.5\n3.6.0+v3.4.5\n3.5.0+v3.4.5"
|
||||
},
|
||||
{
|
||||
"server": "orgsite.org",
|
||||
"recipe": "vaultwarden",
|
||||
"appName": "pass.orgsite.org",
|
||||
"domain": "pass.orgsite.org",
|
||||
"status": "deployed",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "unknown",
|
||||
"version": "2.0.0+1.33.2",
|
||||
"upgrade": "2.1.0+1.34.1"
|
||||
}
|
||||
],
|
||||
"appCount": 9,
|
||||
"versionCount": 9,
|
||||
"unversionedCount": 0,
|
||||
"latestCount": 3,
|
||||
"upgradeCount": 6
|
||||
}
|
||||
}
|
||||
30
src/services/example abra server ls.json
Normal file
30
src/services/example abra server ls.json
Normal file
@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"host": "mydomain.com",
|
||||
"name": "mydomain.com"
|
||||
},
|
||||
{
|
||||
"host": "coop.test.org",
|
||||
"name": "coop.test.org"
|
||||
},
|
||||
{
|
||||
"host": "internal.website.com",
|
||||
"name": "internal.website.com"
|
||||
},
|
||||
{
|
||||
"host": "internal.server.net",
|
||||
"name": "internal.server.net"
|
||||
},
|
||||
{
|
||||
"host": "internal.intranet.site.com",
|
||||
"name": "internal.intranet.site.com"
|
||||
},
|
||||
{
|
||||
"host": "internal.test.org",
|
||||
"name": "internal.test.org"
|
||||
},
|
||||
{
|
||||
"host": "orgsite.org",
|
||||
"name": "orgsite.org"
|
||||
}
|
||||
]
|
||||
@ -1,62 +1,91 @@
|
||||
import type { AbraApp, AbraServer } from '../types';
|
||||
|
||||
// Mock dev data
|
||||
export const mockApps: AbraApp[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'nextcloud',
|
||||
domain: 'cloud.example.coop',
|
||||
status: 'running',
|
||||
recipe: 'nextcloud',
|
||||
import type { AbraServer, ServerAppsResponse } from '../types';
|
||||
|
||||
// Mock data matching real API structure
|
||||
export const mockAppsData: ServerAppsResponse = {
|
||||
"mydomain.com": {
|
||||
apps: [
|
||||
{
|
||||
server: "mydomain.com",
|
||||
recipe: "nextcloud",
|
||||
appName: "nc.mydomain.com",
|
||||
domain: "nc.mydomain.com",
|
||||
status: "deployed",
|
||||
chaos: "false",
|
||||
chaosVersion: "unknown",
|
||||
version: "12.0.1+31.0.6-fpm",
|
||||
upgrade: "latest"
|
||||
},
|
||||
{
|
||||
server: "mydomain.com",
|
||||
recipe: "traefik",
|
||||
appName: "traefik.mydomain.com",
|
||||
domain: "traefik.mydomain.com",
|
||||
status: "deployed",
|
||||
chaos: "false",
|
||||
chaosVersion: "unknown",
|
||||
version: "3.4.2+v3.4.5",
|
||||
upgrade: "3.6.2+v3.4.5\n3.6.1+v3.4.5"
|
||||
},
|
||||
{
|
||||
server: "mydomain.com",
|
||||
recipe: "authentik",
|
||||
appName: "accounts.mydomain.com",
|
||||
domain: "accounts.mydomain.com",
|
||||
status: "deployed",
|
||||
chaos: "false",
|
||||
chaosVersion: "unknown",
|
||||
version: "7.4.0+2025.6.3",
|
||||
upgrade: "9.0.1+2025.8.1\n9.0.0+2025.8.1"
|
||||
}
|
||||
],
|
||||
appCount: 3,
|
||||
versionCount: 3,
|
||||
unversionedCount: 0,
|
||||
latestCount: 1,
|
||||
upgradeCount: 2
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'wordpress',
|
||||
domain: 'blog.example.coop',
|
||||
status: 'running',
|
||||
recipe: 'wordpress',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'gitea',
|
||||
domain: 'git.example.coop',
|
||||
status: 'stopped',
|
||||
recipe: 'gitea',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'discourse',
|
||||
domain: 'forum.example.coop',
|
||||
status: 'running',
|
||||
recipe: 'discourse',
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
name: 'peertube',
|
||||
domain: 'video.example.coop',
|
||||
status: 'error',
|
||||
recipe: 'peertube',
|
||||
},
|
||||
];
|
||||
"test.coop": {
|
||||
apps: [
|
||||
{
|
||||
server: "test.coop",
|
||||
recipe: "cryptpad",
|
||||
appName: "cryptpad.test.coop",
|
||||
domain: "cryptpad.test.coop",
|
||||
status: "deployed",
|
||||
chaos: "true",
|
||||
chaosVersion: "cb2a47fb",
|
||||
version: "0.4.0+version-2024.3.0",
|
||||
upgrade: "latest"
|
||||
},
|
||||
{
|
||||
server: "test.coop",
|
||||
recipe: "mobilizon",
|
||||
appName: "events.test.coop",
|
||||
domain: "events.test.coop",
|
||||
status: "deployed",
|
||||
chaos: "true",
|
||||
chaosVersion: "f8f874a5",
|
||||
version: "0.2.1+5.1.2",
|
||||
upgrade: "latest"
|
||||
}
|
||||
],
|
||||
appCount: 2,
|
||||
versionCount: 2,
|
||||
unversionedCount: 0,
|
||||
latestCount: 2,
|
||||
upgradeCount: 0
|
||||
}
|
||||
};
|
||||
|
||||
export const mockServers: AbraServer[] = [
|
||||
{
|
||||
name: 'prod-server-1',
|
||||
host: '192.168.1.10',
|
||||
user: 'root',
|
||||
connected: true,
|
||||
name: 'mydomain.com',
|
||||
host: 'mydomain.com',
|
||||
},
|
||||
{
|
||||
name: 'prod-server-2',
|
||||
host: '192.168.1.11',
|
||||
user: 'root',
|
||||
connected: true,
|
||||
},
|
||||
{
|
||||
name: 'staging-server',
|
||||
host: '192.168.1.20',
|
||||
user: 'root',
|
||||
connected: false,
|
||||
name: 'test.coop',
|
||||
host: 'test.coop',
|
||||
},
|
||||
];
|
||||
|
||||
@ -64,9 +93,9 @@ export const mockServers: AbraServer[] = [
|
||||
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
export const mockApiService = {
|
||||
async getApps(): Promise<AbraApp[]> {
|
||||
await delay(300);
|
||||
return mockApps;
|
||||
async getAppsGrouped(): Promise<ServerAppsResponse> {
|
||||
await delay(500);
|
||||
return mockAppsData;
|
||||
},
|
||||
|
||||
async getServers(): Promise<AbraServer[]> {
|
||||
|
||||
@ -19,18 +19,37 @@ export interface ApiError {
|
||||
status: number;
|
||||
}
|
||||
|
||||
// // Abra CLI related types
|
||||
// export interface AbraApp {
|
||||
// id: string;
|
||||
// name: string;
|
||||
// domain: string;
|
||||
// status: 'running' | 'stopped' | 'error';
|
||||
// recipe: string;
|
||||
// }
|
||||
export interface AbraApp {
|
||||
server: string;
|
||||
recipe: string;
|
||||
appName: string;
|
||||
domain: string;
|
||||
status: string;
|
||||
chaos: string;
|
||||
chaosVersion: string;
|
||||
version: string;
|
||||
upgrade: string;
|
||||
}
|
||||
|
||||
// export interface AbraServer {
|
||||
// name: string;
|
||||
// host: string;
|
||||
// user: string;
|
||||
// connected: boolean;
|
||||
// }
|
||||
export interface AbraServer {
|
||||
name: string;
|
||||
host: string;
|
||||
}
|
||||
|
||||
export interface ServerAppsResponse {
|
||||
[serverName: string]: {
|
||||
apps: AbraApp[];
|
||||
appCount: number;
|
||||
versionCount: number;
|
||||
unversionedCount: number;
|
||||
latestCount: number;
|
||||
upgradeCount: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AppWithServer extends AbraApp {
|
||||
serverStats: {
|
||||
appCount: number;
|
||||
upgradeCount: number;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user