Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a1cd85b
feat(cluster): Manage deployments across connected servers
nfebe Aug 22, 2026
9fba11f
ui(notifications): Add incidents and delivery rules
nfebe Aug 22, 2026
fd56647
test(cluster): Cover local deployment loading
nfebe Aug 22, 2026
4190e59
ui(cluster): Add guided Fleet setup
nfebe Aug 22, 2026
ed335d6
ui(cluster): Manage peer access policies
nfebe Aug 22, 2026
7a01797
ui(cluster): Configure Fleet providers
nfebe Aug 22, 2026
2314d3f
ui(capacity): Configure deployment autoscaling
nfebe Aug 22, 2026
6db0b5c
ui: Add focused docs and local Fleet reviews
nfebe Aug 22, 2026
ef8826d
ui(services): Promote Fleet and notifications
nfebe Aug 22, 2026
6f169f5
ui(navigation): Standardize service headers
nfebe Aug 22, 2026
468d87c
ui(notifications): Add target and incident details
nfebe Aug 22, 2026
e1b8190
ui(navigation): Strengthen shared service headers
nfebe Aug 22, 2026
21aebd6
ui(navigation): Replace duplicate page headers
nfebe Aug 22, 2026
e7549f7
ui(navigation): Merge page context and actions
nfebe Aug 22, 2026
c8ea3fe
ui(navigation): Adapt page context layout
nfebe Aug 22, 2026
9a8a17e
ui(navigation): Align page context with controls
nfebe Aug 22, 2026
5c786cd
ui(navigation): Refine page header rhythm
nfebe Aug 22, 2026
878e616
ui(cluster): Configure K3s provider
nfebe Aug 22, 2026
8efe1be
ui(autoscale): Configure scale-ready workloads
nfebe Aug 22, 2026
f9a9f49
ui(autoscale): Add managed scaling activation
nfebe Aug 22, 2026
9688bf8
ui(cluster): Pair runtime providers
nfebe Aug 22, 2026
b7fd50b
ui(autoscale): Clarify Fleet runtime requirement
nfebe Aug 22, 2026
1915203
ui(fleet): Expose peer deployment inventories
nfebe Aug 22, 2026
ca25e73
ui(deployments): Separate scaling and service configuration
nfebe Aug 22, 2026
67cb0bd
ui(deployments): Keep service image edits in overview
nfebe Aug 22, 2026
adf7bf6
ui(deployments): Simplify configuration navigation
nfebe Aug 22, 2026
464b1d3
ui(fleet): Scope deployments to the selected server
nfebe Aug 22, 2026
bdc25cb
refactor(ui): Remove review-only preview data
nfebe Aug 22, 2026
fb19ea9
feat: Prepare 0.4.0-beta.5
nfebe Aug 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## [0.4.0-beta.5] - 2026-08-22

### Added
- Guided Fleet setup, peer access controls, runtime provider selection, and remote deployment inventories
- Deployment autoscaling configuration with workload compatibility and activation guidance
- Grouped notification incidents with editable targets and delivery rules

### Changed
- Deployments show the selected server in the navigation and remain local by default
- Deployment configuration keeps scaling beside settings while service image changes remain in the overview

## [0.4.0-beta.4] - 2026-08-21

### Added
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@flatrun/ui",
"version": "0.4.0-beta.4",
"version": "0.4.0-beta.5",
"description": "Web interface for FlatRun container orchestration",
"author": "FlatRun",
"license": "MIT",
Expand Down
14 changes: 7 additions & 7 deletions src/assets/design-system.css
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@
--color-info-600: #2563eb;
--color-info-700: #1e40af;

/* Border Radius (scale: 2, 4, 6, 8, 12) */
/* Border Radius */
--radius-xs: 2px;
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 8px;
--radius-xl: 12px;
--radius-sm: 3px;
--radius-md: 4px;
--radius-lg: 6px;
--radius-xl: 8px;
--radius-modal: 14px;
--radius-full: 9999px;
--radius-full: 4px;

/* Spacing */
--space-1: 0.25rem;
Expand Down Expand Up @@ -332,7 +332,7 @@
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
border-radius: var(--radius-xs);
}

.status-indicator.running {
Expand Down
124 changes: 124 additions & 0 deletions src/components/DeploymentAutoscaleCard.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { flushPromises, mount } from "@vue/test-utils";
import { beforeEach, describe, expect, it, vi } from "vitest";
import DeploymentAutoscaleCard from "./DeploymentAutoscaleCard.vue";

vi.mock("@/stores/notifications", () => ({
useNotificationsStore: () => ({ success: vi.fn(), error: vi.fn() }),
}));

vi.mock("@/services/api", () => ({
autoscaleApi: {
getPolicy: vi.fn(),
updatePolicy: vi.fn(),
getCompatibility: vi.fn(),
updateWorkload: vi.fn(),
activate: vi.fn(),
},
}));

describe("DeploymentAutoscaleCard", () => {
beforeEach(async () => {
window.history.replaceState({}, "", "/");
vi.clearAllMocks();
const { autoscaleApi } = await import("@/services/api");
vi.mocked(autoscaleApi.getPolicy).mockResolvedValue({
data: {
enabled: true,
min_replicas: 1,
max_replicas: 3,
scale_up_percent: 80,
scale_down_percent: 30,
scale_up_windows: 3,
scale_down_windows: 10,
cooldown_seconds: 300,
allow_fleet_capacity: false,
state: { high_windows: 0, low_windows: 0 },
},
} as any);
vi.mocked(autoscaleApi.updatePolicy).mockImplementation(
async (_deployment, policy) =>
({
data: { ...policy, state: { high_windows: 0, low_windows: 0 } },
}) as any,
);
vi.mocked(autoscaleApi.getCompatibility).mockResolvedValue({
data: {
compatible: true,
service: "app",
image: "nginx:alpine",
services: ["app"],
blockers: [],
warnings: [],
workload: { service: "app", stateless: true, storage: { mode: "none", class: "" } },
},
} as any);
vi.mocked(autoscaleApi.updateWorkload).mockResolvedValue({
data: {
compatible: true,
service: "app",
image: "nginx:alpine",
services: ["app"],
blockers: [],
warnings: [],
workload: { service: "app", stateless: true, storage: { mode: "none", class: "" } },
},
} as any);
vi.mocked(autoscaleApi.activate).mockResolvedValue({
data: { workload: { workload: "shop", desired: 1, available: 1 }, route: { id: "shop" } },
} as any);
});

it("activates managed scaling through a confirmation modal", async () => {
const { autoscaleApi } = await import("@/services/api");
const wrapper = mount(DeploymentAutoscaleCard, {
props: { deployment: "shop", canWrite: true },
global: {
stubs: {
BaseModal: {
props: ["visible"],
template: '<div v-if="visible"><slot /><slot name="footer" /></div>',
},
},
},
});
await flushPromises();
await wrapper.find(".activate-button").trigger("click");
await wrapper
.findAll("button")
.find((button) => button.text() === "Activate scaling")
?.trigger("click");
await flushPromises();

expect(autoscaleApi.activate).toHaveBeenCalledWith("shop");
});

it("updates the policy through the deployment UI", async () => {
const { autoscaleApi } = await import("@/services/api");
const wrapper = mount(DeploymentAutoscaleCard, {
props: { deployment: "shop", canWrite: true },
global: {
stubs: {
BaseModal: {
props: ["visible"],
template: '<div v-if="visible"><slot /><slot name="footer" /></div>',
},
},
},
});
await flushPromises();
await wrapper
.findAll(".autoscale-header button")
.find((button) => button.text() === "Configure")
?.trigger("click");
await wrapper.find('input[type="number"]').setValue(2);
await wrapper.find("#autoscale-policy-form").trigger("submit");
await flushPromises();

expect(autoscaleApi.updatePolicy).toHaveBeenCalledWith("shop", expect.objectContaining({ min_replicas: 2 }));
expect(autoscaleApi.updateWorkload).toHaveBeenCalledWith("shop", {
service: "app",
stateless: true,
storage: { mode: "none", class: "" },
});
});
});
Loading
Loading