Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions apps/dashboard/src/lib/remote/admin-projects.remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ import {
import { sendRenderedEmail } from '$lib/server/email';
import { softDeleteOrganizationResources } from '$lib/server/project-deletion';
import { provisionVm } from '$lib/server/vm-provisioning';
import {
accessibilityFixtureEnabled,
accessibilityFixtureAdminProjects
} from '$lib/server/accessibility-fixtures';

export type AdminProjectBillingStatus = 'configured' | 'past_due' | 'suspended' | 'none';

Expand Down Expand Up @@ -69,6 +73,7 @@ function makeCountMap(rows: { key: string | null; count: number }[]) {
}

export const listAdminProjects = query(async (): Promise<AdminProject[]> => {
if (accessibilityFixtureEnabled) return accessibilityFixtureAdminProjects;
const db = await requireCurrentAdmin();

const [orgs, owners, memberCounts, vmCounts, volumeCounts, billingCustomers] = await Promise.all([
Expand Down
5 changes: 5 additions & 0 deletions apps/dashboard/src/lib/remote/admin-users.remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import {
import { updateProjectCustomer } from '$lib/server/billing/autumn';
import { sendRenderedEmail } from '$lib/server/email';
import { softDeleteOrganizationResources } from '$lib/server/project-deletion';
import {
accessibilityFixtureEnabled,
accessibilityFixtureAdminUsers
} from '$lib/server/accessibility-fixtures';

export type UserSession = {
id: string;
Expand Down Expand Up @@ -156,6 +160,7 @@ function makeCountMap(rows: { userId: string | null; count: number }[]) {
}

export const listAdminUsers = query(async (): Promise<AdminUser[]> => {
if (accessibilityFixtureEnabled) return accessibilityFixtureAdminUsers;
const { db } = await requireCurrentAdmin();

const users = await db
Expand Down
5 changes: 5 additions & 0 deletions apps/dashboard/src/lib/remote/admin-vms.remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import { initDrizzle } from '$lib/server/db';
import { member, organization, user, vms, vmTypes } from '$lib/server/db/schema';
import { getBackend, type VmInfo } from '$lib/server/backends';
import { queueVmDeletion } from '$lib/server/vm-deletion';
import {
accessibilityFixtureEnabled,
accessibilityFixtureAdminVms
} from '$lib/server/accessibility-fixtures';

export type AdminVm = {
id: string;
Expand Down Expand Up @@ -50,6 +54,7 @@ async function requireCurrentAdmin() {
}

export const listAllAdminVms = query(async (): Promise<AdminVm[]> => {
if (accessibilityFixtureEnabled) return accessibilityFixtureAdminVms;
const db = await requireCurrentAdmin();

const [rows, owners] = await Promise.all([
Expand Down
6 changes: 6 additions & 0 deletions apps/dashboard/src/lib/remote/billing.remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import {
import { getProjectBillingOverview, refreshProjectBilling } from '$lib/server/billing/overview';
import { runInBackground } from '$lib/server/background';
import { initDrizzle } from '$lib/server/db';
import {
accessibilityFixtureEnabled,
accessibilityFixtureBillingOverview
} from '$lib/server/accessibility-fixtures';

const projectParams = type({ projectId: 'string' });
const setupParams = type({ projectId: 'string', returnTo: 'string?', discountCode: 'string?' });
Expand All @@ -24,6 +28,8 @@ export const getProjectBilling = query(projectParams, async (params) => {
const event = getRequestEvent();
if (!event?.locals.user) error(401, 'Authentication required');

if (accessibilityFixtureEnabled) return accessibilityFixtureBillingOverview;

const db = initDrizzle();
await requireProjectAccess(db, event.locals.user.id, params.projectId, 'admin');
runInBackground(refreshProjectBilling(params.projectId), 'refreshProjectBilling');
Expand Down
6 changes: 6 additions & 0 deletions apps/dashboard/src/lib/remote/images.remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import { initDrizzle } from '$lib/server/db';
import { baseImages } from '$lib/server/db/schema';
import { getBackend } from '$lib/server/backends';
import { requireAdmin } from '$lib/server/auth-context';
import {
accessibilityFixtureEnabled,
accessibilityFixtureImages
} from '$lib/server/accessibility-fixtures';

type ImageRow = {
id: string;
Expand Down Expand Up @@ -53,6 +57,8 @@ export const listImages = query(async () => {
const event = getRequestEvent();
if (!event?.locals.user) error(401, 'Authentication required');

if (accessibilityFixtureEnabled) return accessibilityFixtureImages;

const db = initDrizzle();
const rows = await db.query.baseImages.findMany({
orderBy: [asc(baseImages.sortOrder), asc(baseImages.id)]
Expand Down
5 changes: 5 additions & 0 deletions apps/dashboard/src/lib/remote/ipam.remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import { initDrizzle } from '$lib/server/db';
import { ipamAllocations, ipamPrefixes } from '$lib/server/db/schema';
import { requireAdmin } from '$lib/server/auth-context';
import { listIpamPrefixesWithStats, normalizeIpamPrefixInput } from '$lib/server/ipam';
import {
accessibilityFixtureEnabled,
accessibilityFixtureIpamPrefixes
} from '$lib/server/accessibility-fixtures';

async function requireCurrentAdmin() {
const event = getRequestEvent();
Expand All @@ -18,6 +22,7 @@ async function requireCurrentAdmin() {
}

export const listIpamPrefixes = query(async () => {
if (accessibilityFixtureEnabled) return accessibilityFixtureIpamPrefixes;
const db = await requireCurrentAdmin();
return listIpamPrefixesWithStats(db);
});
Expand Down
6 changes: 6 additions & 0 deletions apps/dashboard/src/lib/remote/vm-types.remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { asc, desc, eq } from 'drizzle-orm';
import { initDrizzle } from '$lib/server/db';
import { vmTypes } from '$lib/server/db/schema';
import { requireAdmin } from '$lib/server/auth-context';
import {
accessibilityFixtureEnabled,
accessibilityFixtureVmTypes
} from '$lib/server/accessibility-fixtures';

type VmTypeRow = {
id: string;
Expand All @@ -22,6 +26,8 @@ export const listVmTypes = query(async () => {
const event = getRequestEvent();
if (!event?.locals.user) error(401, 'Authentication required');

if (accessibilityFixtureEnabled) return accessibilityFixtureVmTypes;

const db = initDrizzle();
const rows = await db.query.vmTypes.findMany({
orderBy: [asc(vmTypes.sortOrder), asc(vmTypes.id)]
Expand Down
146 changes: 146 additions & 0 deletions apps/dashboard/src/lib/server/accessibility-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,149 @@ export const accessibilityFixtureServers = [
}
}
];

export const accessibilityFixtureBillingOverview = {
customer: null,
status: 'active' as const,
statusLabel: 'Ready',
planLabel: 'Project billing',
setupRequired: false,
syncError: null,
lastUpdatedAt: now.getTime(),
activeResourceCount: 1,
activeResources: [
{
id: 'starter',
label: 'Starter',
resourceType: 'vm' as const,
count: 1,
hours: 720,
cost: 36.0
}
]
};

export const accessibilityFixtureAdminUsers = [
{
id: accessibilityFixtureUser.id,
name: accessibilityFixtureUser.name,
email: accessibilityFixtureUser.email,
image: accessibilityFixtureUser.image,
emailVerified: accessibilityFixtureUser.emailVerified,
role: accessibilityFixtureUser.role,
isAdmin: accessibilityFixtureUser.isAdmin,
disabled: false,
billingExempt: false,
twoFactorEnabled: false,
createdAt: now,
updatedAt: now,
sessionCount: 1,
accountCount: 1,
orgCount: 1,
sshKeyCount: 0,
apiTokenCount: 0,
passkeyCount: 0
}
];

export const accessibilityFixtureAdminProjects = [
{
id: accessibilityFixtureProject.id,
name: accessibilityFixtureProject.projectName,
slug: 'accessibility-project',
createdAt: now.getTime(),
ownerId: accessibilityFixtureUser.id,
ownerName: accessibilityFixtureUser.name,
ownerEmail: accessibilityFixtureUser.email,
ownerBillingExempt: false,
memberCount: 2,
vmCount: 1,
volumeCount: 0,
billingStatus: 'configured' as const,
billingExempt: false,
disabled: false
}
];

export const accessibilityFixtureAdminVms = [
{
id: accessibilityFixtureServers[0].id,
name: accessibilityFixtureServers[0].name,
proxmoxId: accessibilityFixtureServers[0].proxmoxId,
proxmoxNode: accessibilityFixtureServers[0].live.proxmoxNode,
active: accessibilityFixtureServers[0].active,
status: accessibilityFixtureServers[0].status,
statusError: null,
liveStatus: accessibilityFixtureServers[0].live.status,
uptime: accessibilityFixtureServers[0].live.uptime,
cpuUsage: accessibilityFixtureServers[0].live.metrics.cpu,
memoryUsageBytes: accessibilityFixtureServers[0].live.memory,
memoryTotalBytes: accessibilityFixtureServers[0].live.memory,
createdAt: now.getTime(),
deletedAt: null,
lastKnownAt: now.getTime(),
lastKnownIpv4: accessibilityFixtureServers[0].live.networkInterfaces.eth0.ipAddresses[0],
lastKnownIpv6: accessibilityFixtureServers[0].live.networkInterfaces.eth0.ipAddresses[1],
projectId: accessibilityFixtureProject.id,
projectName: accessibilityFixtureProject.projectName,
projectBillingExempt: false,
ownerName: accessibilityFixtureUser.name,
ownerEmail: accessibilityFixtureUser.email,
ownerBillingExempt: false,
vmTypeName: accessibilityFixtureServers[0].vmType.name,
vmTypeCores: accessibilityFixtureServers[0].vmType.cores,
vmTypeRamCapacity: accessibilityFixtureServers[0].vmType.ramCapacity,
vmTypeStorageAmount: accessibilityFixtureServers[0].vmType.storageAmount,
vmTypeRate: '0.05'
}
];

export const accessibilityFixtureIpamPrefixes = [
{
id: 'accessibility-prefix-v4',
name: 'Test IPv4',
cidr: '192.0.2.0/24',
family: 'ipv4' as const,
disabled: false,
ipv6UseTransitAddress: false,
whitelistStart: null,
whitelistEnd: null,
gatewayAddress: '192.0.2.1',
allocated: 2,
capacity: '254',
available: '252',
hasCapacity: true
}
];

export const accessibilityFixtureVmTypes = [
{
id: 'starter',
name: 'Starter',
isa: 'x86',
cores: 2,
ramCapacity: 4,
storageAmount: 50,
rate: '0.05',
cap: '50.00',
autumnFeatureId: 'starter'
}
];

export const accessibilityFixtureImages = [
{
id: 'ubuntu-2404',
name: 'Ubuntu',
version: '24.04 LTS',
description: 'Ubuntu 24.04 LTS',
icon: 'ubuntu',
color: 'bg-orange-500',
isOfficial: true,
logoSvg: null,
accentColor: '#E95420',
imageType: 'qcow2',
secureBoot: false,
filePath: 'local:iso/ubuntu-24.04.qcow2',
isa: 'x86'
}
];
15 changes: 15 additions & 0 deletions apps/dashboard/src/lib/server/auth-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { and, eq } from 'drizzle-orm';
import { getRequestEvent } from '$app/server';
import { hasProjectRole, type PermissionLevel } from '$lib/auth/organization-permissions';
import { member, organization, user } from '$lib/server/db/schema';
import {
accessibilityFixtureEnabled,
accessibilityFixtureProject,
accessibilityFixtureUser
} from '$lib/server/accessibility-fixtures';

export function hasAdminRole(role: string | null | undefined): boolean {
return role?.split(',').includes('admin') ?? false;
Expand Down Expand Up @@ -45,6 +50,8 @@ function loadProjectAccess(db: any, userId: string, projectId: string): Promise<
}

export async function requireAdmin(db: any, userId: string): Promise<void> {
if (accessibilityFixtureEnabled && userId === accessibilityFixtureUser.id) return;

const isAdmin = await cachedLookup(`is-admin:${userId}`, async () => {
const currentUser = await db.query.user.findFirst({
where: eq(user.id, userId)
Expand All @@ -69,6 +76,10 @@ export async function requireProjectAccess(
projectId: string,
minLevel: PermissionLevel | 'owner' = 'read'
): Promise<void> {
if (accessibilityFixtureEnabled && userId === accessibilityFixtureUser.id) {
if (projectId === accessibilityFixtureProject.id) return;
}

const projectAccess = await loadProjectAccess(db, userId, projectId);

if (!projectAccess || projectAccess.deletedAt != null) {
Expand All @@ -89,6 +100,10 @@ export async function getProjectMemberRole(
userId: string,
projectId: string
): Promise<string | null> {
if (accessibilityFixtureEnabled && userId === accessibilityFixtureUser.id) {
if (projectId === accessibilityFixtureProject.id) return 'owner';
}

const projectAccess = await loadProjectAccess(db, userId, projectId);
return projectAccess?.role ?? null;
}
6 changes: 6 additions & 0 deletions apps/dashboard/src/lib/server/billing/overview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import {
} from './autumn';
import { initDrizzle } from '$lib/server/db';
import { billingMeters, billingUsageEvents, vmTypes } from '$lib/server/db/schema';
import {
accessibilityFixtureEnabled,
accessibilityFixtureBillingOverview
} from '$lib/server/accessibility-fixtures';

function statusLabel(status: Awaited<ReturnType<typeof getProjectBillingState>>['status']) {
if (status === 'active') return 'Ready';
Expand All @@ -24,6 +28,8 @@ export async function refreshProjectBilling(projectId: string) {
}

export async function getProjectBillingOverview(projectId: string) {
if (accessibilityFixtureEnabled) return accessibilityFixtureBillingOverview;

const db = initDrizzle();
const now = Date.now();
const billingState = await getProjectBillingState(projectId);
Expand Down
6 changes: 5 additions & 1 deletion apps/dashboard/src/lib/server/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Fetcher, KVNamespace, SendEmail } from '@cloudflare/workers-types'
import { getRequestEvent } from '$app/server';
import { dev } from '$app/environment';
import { env as privateEnv } from '$env/dynamic/private';
import { accessibilityFixtureEnabled } from '$lib/server/accessibility-fixtures';

export type RuntimeEnv = {
ORIGIN: string;
Expand Down Expand Up @@ -34,7 +35,10 @@ export type RuntimeEnv = {
};

function required(name: keyof RuntimeEnv, value: string | undefined): string {
if (!value) throw new Error(`${name} is not set`);
if (!value) {
if (accessibilityFixtureEnabled) return `fixture-${name}`;
throw new Error(`${name} is not set`);
}

return value;
}
Expand Down
Loading