dev #5

Merged
BornDeleuze merged 5 commits from dev into main 2026-03-04 22:44:54 +00:00
17 changed files with 396 additions and 658 deletions

View File

@ -4,5 +4,4 @@ This is the frontend of a web wrapper for Coop Clouds abra CLI, letting users se
## Still a work in progess!
## This is built with react, typescript, scss, and vite
## This is built with react, typescript, scss, and vite

View File

@ -1,3 +1,6 @@
@use './variables' as *;
@use './mixins' as *;
body {
margin: 0;
padding: 0;
@ -9,3 +12,262 @@ body {
width: 100%;
min-height: 100vh;
}
// global page layout styles
.page-wrapper {
min-height: 100vh;
background-color: $bg-secondary;
}
.page-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;
}
}
// global state styles
.loading,
.error {
text-align: center;
padding: $spacing-3xl;
font-size: $font-size-lg;
}
.loading {
color: $text-secondary;
}
.error {
color: $error;
}
// Stats grid and cards
.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;
}
// Modifier classes for colored borders
&.upgrade {
border-left: 4px solid $primary-light;
}
&.chaos {
border-left: 4px solid $primary-dark;
}
&.primary {
border-left: 4px solid $primary;
}
.stat-icon {
font-size: 2rem;
}
.stat-info {
flex: 1;
.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 component
.filters {
@include card;
display: flex;
gap: $spacing-md;
flex-wrap: wrap;
margin-bottom: $spacing-xl;
@media (max-width: 768px) {
flex-direction: column;
}
.search-input {
background-color: $bg-primary;
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;
color: $text-primary;
&::placeholder {
color: $text-muted;
}
&: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: $bg-primary;
color: $text-primary;
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;
}
}
}
// Status badges
.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: $success;
}
&.status-stopped {
background-color: rgba($text-muted, 0.1);
color: $text-muted;
}
&.status-error {
background-color: rgba($error, 0.1);
color: $error;
}
}
// global badge styles
.recipe-badge,
.server-badge {
display: inline-block;
padding: $spacing-xs $spacing-sm;
border-radius: $radius-sm;
font-size: $font-size-sm;
font-weight: $font-weight-medium;
}
.recipe-badge {
background-color: rgba($info, 0.1);
color: $info;
}
.server-badge {
background-color: rgba($text-secondary, 0.1);
color: $text-secondary;
}
// Results count
.results-count {
text-align: center;
color: $text-secondary;
font-size: $font-size-sm;
padding: $spacing-md;
}
// No results message
.no-results {
@include card;
text-align: center;
padding: $spacing-3xl;
color: $text-secondary;
grid-column: 1 / -1;
}
// Navigation link button (for clickable cards)
.nav-link {
all: unset;
cursor: pointer;
width: 100%;
display: block;
&:focus-visible {
outline: 2px solid $primary;
outline-offset: 2px;
border-radius: $radius-md;
}
}
.bland-button {
background: none;
border: none;
padding: 0;
margin: 0;
}

View File

@ -45,7 +45,7 @@
// Gradient background
@mixin gradient-primary {
background: linear-gradient(135deg, $primary 0%, $primary-dark 100%);
background: linear-gradient(135deg, $primary 0%, $primary-light 100%);
}
// Truncate text

View File

@ -1,6 +1,7 @@
// Colors
$primary: #EFEFEF;
$primary-dark: #ff4f88;
$primary-dark: #6A9CFF;
$primary-light: #ff4f88;
$secondary: #363636;
$success: #10b981;

View File

@ -2,7 +2,7 @@
@use '../../assets/scss/mixins' as *;
.layout-header {
background-color: $primary-dark;
background-color: $primary-light;
color: $text-primary;
padding: $spacing-lg 0;
box-shadow: $shadow-lg;

View File

@ -1,15 +1,18 @@
@use './assets/scss/variables' as *;
@use './assets/scss/mixins' as *;
@use './assets/scss/global' as *;
// Global root styles
:root {
line-height: 1.5;
font-weight: 400;
font-family: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI',
'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
'Helvetica Neue', sans-serif;
'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
'Helvetica Neue', sans-serif;
color-scheme: light dark;
color: $text-primary;
background-color: $primary;
background-color: $bg-primary;
font-synthesis: none;
text-rendering: optimizeLegibility;
@ -17,13 +20,9 @@
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
// Global element resets
* {
box-sizing: border-box;
}
body {
@ -32,51 +31,63 @@ body {
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
font-family: 'Lora', serif;
// Global link styles
a {
font-weight: 500;
color: $primary;
text-decoration: none;
transition: color $transition-base;
&:hover {
color: $primary-light;
}
}
// Global heading styles
h1, h2, h3, h4, h5, h6 {
color: $text-primary;
margin: 0;
}
h1 {
font-size: $font-size-3xl;
line-height: 1.1;
}
h2 {
font-size: $font-size-2xl;
line-height: 1.2;
}
h3 {
font-size: $font-size-xl;
line-height: 1.3;
}
// Global button styles
button {
border-radius: 8px;
border-radius: $radius-md;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
padding: $spacing-sm $spacing-lg;
font-size: $font-size-base;
font-weight: $font-weight-medium;
font-family: inherit;
background-color: $primary-dark;
background-color: $primary;
color: white;
cursor: pointer;
transition: border-color 0.25s;
}
.bland-button{
border-radius: 8px;
border: 0px;
padding: 0em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
transition: all $transition-base;
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: $primary;
&:hover {
background-color: $primary-light;
}
a:hover {
color: #747bff;
&:focus-visible {
outline: 2px solid $primary;
outline-offset: 2px;
}
button {
background-color: $primary;
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}

View File

@ -253,7 +253,7 @@
}
.domain-link {
color: #0066cc;
color: $primary-dark;
text-decoration: none;
font-size: 14px;

View File

@ -1,149 +1,17 @@
@use '../../../assets/scss/variables' as *;
@use '../../../assets/scss/mixins' as *;
@use '../../../assets/scss/global' as *;
// Extend global page wrapper
.apps-page {
min-height: 100vh;
background-color: $bg-secondary;
@extend .page-wrapper;
}
.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;
}
}
@extend .page-content;
}
// Apps table specific styles
.apps-table-container {
@include card;
overflow-x: auto;
@ -197,6 +65,7 @@
}
}
// App table cell styles
.app-name-cell {
.app-name {
font-weight: $font-weight-medium;
@ -204,39 +73,20 @@
}
}
.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;
color: $primary-light;
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;
font-style: italic;
}
.version-cell {
@ -247,6 +97,7 @@
.version {
font-family: monospace;
font-size: $font-size-sm;
color: $text-primary;
}
.chaos-badge,
@ -255,40 +106,19 @@
}
}
.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%);
}
}
// Action buttons
.actions {
display: flex;
gap: $spacing-sm;
.action-btn {
background: none;
border: 1px solid $border-color;
padding: $spacing-xs $spacing-sm;
border-radius: $radius-sm;
cursor: pointer;
font-size: $font-size-base;
color: $text-primary;
transition: all $transition-base;
&:hover {
@ -298,28 +128,7 @@
&.upgrade {
border-color: $warning;
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;
}

View File

@ -51,6 +51,10 @@ export const Dashboard: React.FC = () => {
fetchData();
}, [isMockMode]);
// Calculate stats
const deployedAppsCount = apps.filter(a => a.status === 'deployed').length;
const serversWithAppsCount = new Set(apps.map(a => a.server)).size;
if (loading) {
return (
<div className="dashboard-page">
@ -77,7 +81,9 @@ export const Dashboard: React.FC = () => {
<div className="dashboard-page">
<Header />
<main className="dashboard-content">
<h2>Dashboard</h2>
<div className="page-header">
<h1>Dashboard</h1>
</div>
<div className="stats-grid">
<button onClick={() => navigate('/apps')} className="nav-link bland-button">
@ -85,7 +91,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 === 'deployed').length} deployed
{deployedAppsCount} deployed
</p>
</div>
</button>
@ -95,7 +101,7 @@ export const Dashboard: React.FC = () => {
<h3>Servers</h3>
<p className="stat-number">{servers.length}</p>
<p className="stat-label">
{servers.length} connected
{serversWithAppsCount} connected
</p>
</div>
</button>
@ -105,7 +111,11 @@ export const Dashboard: React.FC = () => {
<h3>Recent Applications</h3>
<div className="apps-list">
{apps.slice(0, 5).map((app, index) => (
<div key={`${app.server}-${app.appName}-${index}`} className="app-item" onClick={() => navigate(`/apps/${app.server}/${app.appName}`)}>
<div
key={`${app.server}-${app.appName}-${index}`}
className="app-item"
onClick={() => navigate(`/apps/${app.server}/${app.appName}`)}
>
<div className="app-info">
<h4>{app.appName}</h4>
<p className="app-domain">{app.domain || 'No domain'}</p>
@ -121,4 +131,4 @@ export const Dashboard: React.FC = () => {
</main>
</div>
);
};
};

View File

@ -1,83 +1,23 @@
@use '../../../assets/scss/variables' as *;
@use '../../../assets/scss/mixins' as *;
@use '../../../assets/scss/global' as *;
// Extend global page wrapper
.dashboard-page {
min-height: 100vh;
background-color: $bg-secondary;
@extend .page-wrapper;
}
.dashboard-content {
max-width: 1400px;
margin: 0 auto;
padding: $spacing-2xl $spacing-xl;
@media (max-width: 768px) {
padding: $spacing-xl $spacing-md;
}
h2 {
margin: 0 0 $spacing-2xl;
font-size: $font-size-3xl;
color: $text-primary;
}
}
.loading, .error {
text-align: center;
padding: $spacing-3xl;
font-size: $font-size-lg;
color: $text-secondary;
}
.error {
color: $error;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: $spacing-lg;
margin-bottom: $spacing-2xl;
}
.stat-card {
@include card;
border-left: 4px solid $primary;
transition: transform $transition-base, box-shadow $transition-base;
&:hover {
transform: translateY(-2px);
box-shadow: $shadow-lg;
}
h3 {
margin: 0 0 $spacing-md;
color: $text-secondary;
font-size: $font-size-sm;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: $font-weight-semibold;
}
.stat-number {
margin: 0 0 $spacing-sm;
font-size: $font-size-3xl;
font-weight: $font-weight-bold;
color: $text-primary;
line-height: 1;
}
.stat-label {
margin: 0;
color: $text-muted;
font-size: $font-size-sm;
}
@extend .page-content;
}
// Dashboard-specific styles
.recent-apps {
margin-bottom: $spacing-2xl;
h3 {
margin: 0 0 $spacing-lg;
font-size: $font-size-2xl;
margin: 0 0 $spacing-lg;
color: $text-primary;
}
}
@ -90,19 +30,20 @@
.app-item {
@include card;
padding: $spacing-lg;
display: flex;
justify-content: space-between;
align-items: center;
transition: transform $transition-base, box-shadow $transition-base;
cursor: pointer;
&:hover {
transform: translateY(-2px);
box-shadow: $shadow-lg;
cursor: pointer;
}
.app-info {
flex: 1;
h4 {
margin: 0 0 $spacing-xs;
font-size: $font-size-lg;
@ -111,26 +52,16 @@
}
.app-domain {
margin: 0;
margin: 0 0 $spacing-xs;
color: $text-muted;
font-size: $font-size-sm;
}
}
.status-badge {
@include status-badge($success);
text-transform: capitalize;
&.status-running {
@include status-badge($success);
}
&.status-stopped {
@include status-badge($text-muted);
}
&.status-error {
@include status-badge($error);
.app-server {
margin: 0;
font-size: $font-size-sm;
color: $text-secondary;
font-family: monospace;
}
}
}
}

View File

@ -50,7 +50,7 @@
transition: color $transition-base;
&:hover {
color: $primary-dark;
color: $primary-light;
text-decoration: underline;
}
}
@ -128,8 +128,8 @@
border-color: $primary;
&:hover:not(:disabled) {
background: $primary-dark;
border-color: $primary-dark;
background: $primary-light;
border-color: $primary-light;
}
}
@ -189,7 +189,7 @@
display: inline-block;
padding: $spacing-xs $spacing-sm;
background: rgba($primary, 0.1);
color: $primary-dark;
color: $primary-light;
border-radius: $radius-sm;
font-size: $font-size-xs;
font-weight: $font-weight-medium;

View File

@ -1,130 +1,17 @@
@use '../../../assets/scss/variables' as *;
@use '../../../assets/scss/mixins' as *;
@use '../../../assets/scss/global' as *;
// Extend global page wrapper
.servers-page {
min-height: 100vh;
background-color: $bg-secondary;
@extend .page-wrapper;
}
.servers-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;
}
}
@extend .page-content;
}
// Servers grid
.servers-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
@ -136,6 +23,7 @@
}
}
// Server card
.server-card {
@include card;
display: flex;
@ -202,6 +90,7 @@
border-bottom: none;
}
// Highlighted rows
&.highlight {
background-color: rgba($warning, 0.05);
padding: $spacing-sm $spacing-md;
@ -211,12 +100,10 @@
border-bottom: none;
.stat-label {
// color: darken($warning, 10%);
font-weight: $font-weight-semibold;
}
.stat-value {
// color: darken($warning, 10%);
font-weight: $font-weight-bold;
}
}
@ -229,12 +116,10 @@
padding-right: calc($spacing-xl + $spacing-md);
.stat-label {
// color: darken($info, 10%);
font-weight: $font-weight-semibold;
}
.stat-value {
// color: darken($info, 10%);
font-weight: $font-weight-bold;
}
}
@ -269,6 +154,7 @@
flex: 1;
padding: $spacing-sm $spacing-md;
border: 2px solid $border-color;
background: none;
color: $text-primary;
border-radius: $radius-md;
font-size: $font-size-sm;
@ -277,17 +163,19 @@
transition: all $transition-base;
&:hover {
background-color: $primary-dark;
background-color: rgba($primary, 0.1);
border-color: $primary;
transform: translateY(-1px);
}
&.primary {
background-color: $primary;
color: white;
border-color: $primary;
&:hover {
background-color: $primary-dark;
border-color: $primary-dark;
background-color: $primary-light;
border-color: $primary-light;
}
}
}
@ -304,42 +192,13 @@
.alert-icon {
font-size: $font-size-lg;
color: $warning;
}
.alert-text {
font-size: $font-size-sm;
// color: darken($warning, 20%);
color: $text-primary;
font-weight: $font-weight-medium;
}
}
}
.no-results {
@include card;
text-align: center;
padding: $spacing-3xl;
color: $text-secondary;
grid-column: 1 / -1;
}
.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;
}

View File

@ -6,7 +6,7 @@
min-height: 100vh;
width: 100%;
// @include gradient-primary;
background-color: $primary-dark;
background-color: $primary-light;
position: fixed;
top: 0;
left: 0;

View File

@ -1,150 +1,6 @@
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\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: "myotherdomain.com",
recipe: "traefik",
appName: "traefik.myotherdomain.com",
domain: "traefik.myotherdomain.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: 3,
versionCount: 3,
unversionedCount: 0,
latestCount: 1,
upgradeCount: 2
},
"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: 'test.coop',
host: 'test.coop',
},
{
"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"
}
];
import appsData from './mock-apps.json';
import serversData from './mock-servers.json';
// Simulate API delay
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
@ -152,12 +8,12 @@ const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
export const mockApiService = {
async getAppsGrouped(): Promise<ServerAppsResponse> {
await delay(500);
return mockAppsData;
return appsData as ServerAppsResponse;
},
async getServers(): Promise<AbraServer[]> {
await delay(300);
return mockServers;
return serversData as AbraServer[];
},
async deployApp(appName: string): Promise<void> {

View File

@ -24,7 +24,7 @@ export interface AbraApp {
recipe: string;
appName: string;
domain: string;
status: string;
status: 'deployed' | 'stopped' | 'unknown';
chaos: string;
chaosVersion: string;
version: string;