feat(frontend): add Helm chart browser, monitoring, chart-references and values templates pages

Add new frontend pages for the multi-tenant OCDP platform:

- Charts page (/charts): Browse Harbor OCI registries to list Helm chart repositories
  and versions, with deploy modal to launch charts on selected clusters
- Monitoring page (/monitoring): Display cluster metrics (CPU/Memory/GPU usage)
  and per-node details with resource utilization bars
- Chart References page (/chart-references): CRUD for chart metadata references
- Values Templates page (/templates): CRUD for Helm values templates with version
  history and rollback support
- Sidebar: Add Charts navigation, update Storage and Templates links
- api.ts: Add all API client functions (clusterApi, registryApi, instanceApi,
  monitoringApi, storageApi, chartReferenceApi, valuesTemplateApi,
  workspaceApi, userApi) with full TypeScript types

Note: deploy flow and values template rollback not yet end-to-end tested.
This commit is contained in:
Ivan087
2026-04-15 16:59:31 +08:00
parent c5e51ed069
commit 29d0310f03
283 changed files with 24658 additions and 36038 deletions

View File

@ -6,7 +6,11 @@ CREATE TABLE IF NOT EXISTS users (
id VARCHAR(36) PRIMARY KEY,
username VARCHAR(255) NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
email VARCHAR(255) NOT NULL,
email VARCHAR(255),
role VARCHAR(20) NOT NULL DEFAULT 'user',
workspace_id VARCHAR(36),
is_active BOOLEAN DEFAULT TRUE,
must_change_password BOOLEAN DEFAULT FALSE,
revoked_after TIMESTAMP NOT NULL DEFAULT '1970-01-01 00:00:00',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
@ -24,15 +28,21 @@ COMMENT ON COLUMN users.email IS '邮箱';
-- ===== Clusters 表 =====
CREATE TABLE IF NOT EXISTS clusters (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
workspace_id VARCHAR(36),
owner_id VARCHAR(36),
name VARCHAR(255) NOT NULL,
host TEXT NOT NULL,
ca_data TEXT,
cert_data TEXT,
key_data TEXT,
token TEXT,
description TEXT,
isolation_mode VARCHAR(20) DEFAULT 'namespace',
default_namespace VARCHAR(255),
is_shared BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(workspace_id, name)
);
CREATE INDEX IF NOT EXISTS idx_clusters_name ON clusters(name);
@ -116,6 +126,69 @@ COMMENT ON COLUMN instances.last_operation IS '最后一次操作类型';
COMMENT ON COLUMN instances.last_error IS '最近一次错误信息';
COMMENT ON COLUMN instances.revision IS 'Helm Release Revision';
-- ===== Workspaces 表 =====
CREATE TABLE IF NOT EXISTS workspaces (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
description TEXT,
created_by VARCHAR(36),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_workspaces_name ON workspaces(name);
-- ===== Storage Backends 表 =====
CREATE TABLE IF NOT EXISTS storage_backends (
id VARCHAR(36) PRIMARY KEY,
workspace_id VARCHAR(36),
name VARCHAR(255) NOT NULL,
type VARCHAR(50) NOT NULL,
config JSONB NOT NULL,
description TEXT,
is_default BOOLEAN DEFAULT FALSE,
is_shared BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(workspace_id, name)
);
CREATE INDEX IF NOT EXISTS idx_storage_workspace ON storage_backends(workspace_id);
-- ===== Chart References 表 =====
CREATE TABLE IF NOT EXISTS chart_references (
id VARCHAR(36) PRIMARY KEY,
workspace_id VARCHAR(36),
registry_id VARCHAR(36),
repository VARCHAR(500) NOT NULL,
chart_name VARCHAR(255) NOT NULL,
description TEXT,
is_enabled BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_chart_workspace ON chart_references(workspace_id);
CREATE INDEX IF NOT EXISTS idx_chart_registry ON chart_references(registry_id);
-- ===== Values Templates 表 =====
CREATE TABLE IF NOT EXISTS values_templates (
id VARCHAR(36) PRIMARY KEY,
workspace_id VARCHAR(36),
chart_reference_id VARCHAR(36),
name VARCHAR(255) NOT NULL,
description TEXT,
values_yaml TEXT NOT NULL,
version INTEGER NOT NULL DEFAULT 1,
is_default BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(workspace_id, chart_reference_id, name)
);
CREATE INDEX IF NOT EXISTS idx_values_template_chart ON values_templates(chart_reference_id);
CREATE INDEX IF NOT EXISTS idx_values_template_workspace ON values_templates(workspace_id);
-- ===== 数据库版本表 =====
CREATE TABLE IF NOT EXISTS schema_migrations (
version VARCHAR(50) PRIMARY KEY,

View File

@ -0,0 +1,190 @@
-- OCDP Multi-Tenant Migration Script
-- Adds multi-tenant fields and new tables for workspace isolation
-- ===== Phase 1: Add new columns to existing tables =====
-- Add multi-tenant fields to users table
ALTER TABLE users ADD COLUMN IF NOT EXISTS role VARCHAR(20) NOT NULL DEFAULT 'user';
ALTER TABLE users ADD COLUMN IF NOT EXISTS workspace_id VARCHAR(36);
ALTER TABLE users ADD COLUMN IF NOT EXISTS is_active BOOLEAN NOT NULL DEFAULT TRUE;
ALTER TABLE users ADD COLUMN IF NOT EXISTS must_change_password BOOLEAN NOT NULL DEFAULT FALSE;
-- Add indexes for new user fields
CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
CREATE INDEX IF NOT EXISTS idx_users_workspace_id ON users(workspace_id);
CREATE INDEX IF NOT EXISTS idx_users_is_active ON users(is_active);
-- Add multi-tenant fields to clusters table
ALTER TABLE clusters ADD COLUMN IF NOT EXISTS workspace_id VARCHAR(36);
ALTER TABLE clusters ADD COLUMN IF NOT EXISTS owner_id VARCHAR(36);
ALTER TABLE clusters ADD COLUMN IF NOT EXISTS isolation_mode VARCHAR(20) NOT NULL DEFAULT 'namespace';
ALTER TABLE clusters ADD COLUMN IF NOT EXISTS default_namespace VARCHAR(255);
ALTER TABLE clusters ADD COLUMN IF NOT EXISTS is_shared BOOLEAN NOT NULL DEFAULT FALSE;
-- Add index for cluster workspace
CREATE INDEX IF NOT EXISTS idx_clusters_workspace_id ON clusters(workspace_id);
-- Add multi-tenant fields to registries table
ALTER TABLE registries ADD COLUMN IF NOT EXISTS workspace_id VARCHAR(36);
ALTER TABLE registries ADD COLUMN IF NOT EXISTS owner_id VARCHAR(36);
ALTER TABLE registries ADD COLUMN IF NOT EXISTS is_shared BOOLEAN NOT NULL DEFAULT FALSE;
-- Add index for registry workspace
CREATE INDEX IF NOT EXISTS idx_registries_workspace_id ON registries(workspace_id);
-- Add multi-tenant fields to instances table
ALTER TABLE instances ADD COLUMN IF NOT EXISTS workspace_id VARCHAR(36);
ALTER TABLE instances ADD COLUMN IF NOT EXISTS owner_id VARCHAR(36);
ALTER TABLE instances ADD COLUMN IF NOT EXISTS values_template_id VARCHAR(36);
ALTER TABLE instances ADD COLUMN IF NOT EXISTS user_override_yaml TEXT;
ALTER TABLE instances ADD COLUMN IF NOT EXISTS cpu_requested DECIMAL(10,2) NOT NULL DEFAULT 0;
ALTER TABLE instances ADD COLUMN IF NOT EXISTS memory_requested VARCHAR(50) NOT NULL DEFAULT '0Mi';
ALTER TABLE instances ADD COLUMN IF NOT EXISTS gpu_requested DECIMAL(10,2) NOT NULL DEFAULT 0;
ALTER TABLE instances ADD COLUMN IF NOT EXISTS gpu_memory_requested VARCHAR(50) NOT NULL DEFAULT '0Mi';
-- Add index for instance workspace
CREATE INDEX IF NOT EXISTS idx_instances_workspace_id ON instances(workspace_id);
-- ===== Phase 2: Create new tables =====
-- Create workspaces table
CREATE TABLE IF NOT EXISTS workspaces (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
description TEXT,
created_by VARCHAR(36),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_workspaces_name ON workspaces(name);
-- Create workspace_quotas table
CREATE TABLE IF NOT EXISTS workspace_quotas (
id VARCHAR(36) PRIMARY KEY,
workspace_id VARCHAR(36) NOT NULL,
resource_type VARCHAR(50) NOT NULL,
hard_limit DECIMAL(10,2) NOT NULL,
soft_limit DECIMAL(10,2) NOT NULL,
used DECIMAL(10,2) NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(workspace_id, resource_type),
CONSTRAINT fk_workspace_quotas_workspace FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_workspace_quotas_workspace_id ON workspace_quotas(workspace_id);
-- Create storage_backends table
CREATE TABLE IF NOT EXISTS storage_backends (
id VARCHAR(36) PRIMARY KEY,
workspace_id VARCHAR(36),
owner_id VARCHAR(36),
name VARCHAR(255) NOT NULL,
type VARCHAR(50) NOT NULL,
config JSONB NOT NULL,
description TEXT,
is_default BOOLEAN NOT NULL DEFAULT FALSE,
is_shared BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(workspace_id, name)
);
CREATE INDEX IF NOT EXISTS idx_storage_backends_workspace_id ON storage_backends(workspace_id);
-- Create chart_references table
CREATE TABLE IF NOT EXISTS chart_references (
id VARCHAR(36) PRIMARY KEY,
workspace_id VARCHAR(36),
registry_id VARCHAR(36),
repository VARCHAR(500) NOT NULL,
chart_name VARCHAR(255) NOT NULL,
description TEXT,
is_enabled BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(workspace_id, registry_id, repository)
);
CREATE INDEX IF NOT EXISTS idx_chart_references_workspace_id ON chart_references(workspace_id);
CREATE INDEX IF NOT EXISTS idx_chart_references_registry_id ON chart_references(registry_id);
-- Create values_templates table
CREATE TABLE IF NOT EXISTS values_templates (
id VARCHAR(36) PRIMARY KEY,
workspace_id VARCHAR(36),
owner_id VARCHAR(36),
chart_reference_id VARCHAR(36),
name VARCHAR(255) NOT NULL,
description TEXT,
values_yaml TEXT NOT NULL,
version INTEGER NOT NULL DEFAULT 1,
is_default BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(workspace_id, chart_reference_id, name)
);
CREATE INDEX IF NOT EXISTS idx_values_templates_workspace_id ON values_templates(workspace_id);
CREATE INDEX IF NOT EXISTS idx_values_templates_chart_reference_id ON values_templates(chart_reference_id);
-- Create user_config_overrides table
CREATE TABLE IF NOT EXISTS user_config_overrides (
id VARCHAR(36) PRIMARY KEY,
workspace_id VARCHAR(36),
user_id VARCHAR(36),
target_type VARCHAR(50) NOT NULL,
target_id VARCHAR(36),
config JSONB NOT NULL,
priority INTEGER NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_user_config_overrides_workspace_id ON user_config_overrides(workspace_id);
CREATE INDEX IF NOT EXISTS idx_user_config_overrides_user_id ON user_config_overrides(user_id);
-- Create audit_logs table
CREATE TABLE IF NOT EXISTS audit_logs (
id VARCHAR(36) PRIMARY KEY,
workspace_id VARCHAR(36),
user_id VARCHAR(36),
action VARCHAR(100) NOT NULL,
resource_type VARCHAR(50) NOT NULL,
resource_id VARCHAR(36),
resource_name VARCHAR(255),
details JSONB,
ip_address VARCHAR(50),
user_agent TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_audit_logs_workspace_id ON audit_logs(workspace_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_user_id ON audit_logs(user_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at ON audit_logs(created_at);
-- ===== Phase 3: Create admin user =====
-- Note: Default password is 'admin123' (bcrypt hash will be set by application)
-- The admin user will have NULL workspace_id to indicate global access
INSERT INTO users (id, username, password_hash, email, role, workspace_id, is_active, must_change_password)
VALUES (
'00000000-0000-0000-0000-000000000001',
'admin',
'$2a$10$placeholder', -- Replace with actual bcrypt hash in production
'admin@ocdp.local',
'admin',
NULL,
TRUE,
TRUE
) ON CONFLICT (username) DO NOTHING;
-- Update schema version
INSERT INTO schema_migrations (version) VALUES ('v2.0.0-multi-tenant')
ON CONFLICT (version) DO NOTHING;
-- Grant permissions (adjust as needed for your setup)
-- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ocdp_user;
-- GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ocdp_user;

View File

@ -0,0 +1,257 @@
-- OCDP 多租户权限系统迁移脚本
-- 版本: v2.0.0
-- 日期: 2026-04-09
-- ===== 1. 修改 users 表 =====
ALTER TABLE users ADD COLUMN IF NOT EXISTS role VARCHAR(20) NOT NULL DEFAULT 'user';
ALTER TABLE users ADD COLUMN IF NOT EXISTS workspace_id VARCHAR(36);
ALTER TABLE users ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT TRUE;
ALTER TABLE users ADD COLUMN IF NOT EXISTS must_change_password BOOLEAN DEFAULT FALSE;
-- 添加索引
CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
CREATE INDEX IF NOT EXISTS idx_users_workspace ON users(workspace_id);
CREATE INDEX IF NOT EXISTS idx_users_is_active ON users(is_active);
COMMENT ON COLUMN users.role IS '用户角色: admin, user';
COMMENT ON COLUMN users.workspace_id IS '所属工作空间 ID';
COMMENT ON COLUMN users.is_active IS '账户是否激活';
COMMENT ON COLUMN users.must_change_password IS '首次登录必须修改密码';
-- ===== 2. 创建 workspaces 表 =====
CREATE TABLE IF NOT EXISTS workspaces (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
description TEXT,
created_by VARCHAR(36) REFERENCES users(id),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_workspaces_name ON workspaces(name);
CREATE INDEX IF NOT EXISTS idx_workspaces_created_by ON workspaces(created_by);
COMMENT ON TABLE workspaces IS '工作空间/租户表';
-- ===== 3. 创建 workspace_quotas 表 =====
CREATE TABLE IF NOT EXISTS workspace_quotas (
id VARCHAR(36) PRIMARY KEY,
workspace_id VARCHAR(36) NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
resource_type VARCHAR(50) NOT NULL, -- 'cpu', 'gpu', 'gpu_memory'
hard_limit DECIMAL(10,2) NOT NULL DEFAULT 0, -- 硬限制0表示无限制
soft_limit DECIMAL(10,2) NOT NULL DEFAULT 0, -- 软限制(警告阈值)
used DECIMAL(10,2) DEFAULT 0, -- 当前使用量
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(workspace_id, resource_type)
);
CREATE INDEX IF NOT EXISTS idx_workspace_quotas_workspace ON workspace_quotas(workspace_id);
CREATE INDEX IF NOT EXISTS idx_workspace_quotas_type ON workspace_quotas(resource_type);
COMMENT ON TABLE workspace_quotas IS '工作空间资源配额表';
COMMENT ON COLUMN workspace_quotas.resource_type IS '资源类型: cpu, gpu, gpu_memory';
COMMENT ON COLUMN workspace_quotas.hard_limit IS '硬限制0表示无限制';
COMMENT ON COLUMN workspace_quotas.soft_limit IS '软限制(警告阈值)';
COMMENT ON COLUMN workspace_quotas.used IS '当前使用量';
-- ===== 4. 修改 clusters 表 =====
ALTER TABLE clusters ADD COLUMN IF NOT EXISTS workspace_id VARCHAR(36) REFERENCES workspaces(id) ON DELETE SET NULL;
ALTER TABLE clusters ADD COLUMN IF NOT EXISTS owner_id VARCHAR(36) REFERENCES users(id);
ALTER TABLE clusters ADD COLUMN IF NOT EXISTS isolation_mode VARCHAR(20) DEFAULT 'namespace';
ALTER TABLE clusters ADD COLUMN IF NOT EXISTS default_namespace VARCHAR(255);
ALTER TABLE clusters ADD COLUMN IF NOT EXISTS is_shared BOOLEAN DEFAULT FALSE;
CREATE INDEX IF NOT EXISTS idx_clusters_workspace ON clusters(workspace_id);
CREATE INDEX IF NOT EXISTS idx_clusters_owner ON clusters(owner_id);
CREATE INDEX IF NOT EXISTS idx_clusters_is_shared ON clusters(is_shared);
-- 删除旧的唯一约束添加新的允许同一workspace内名称唯一
ALTER TABLE clusters DROP CONSTRAINT IF EXISTS clusters_name_key;
COMMENT ON COLUMN clusters.workspace_id IS '所属工作空间 ID';
COMMENT ON COLUMN clusters.owner_id IS '创建者用户 ID';
COMMENT ON COLUMN clusters.isolation_mode IS '隔离模式: namespace(共享集群) 或 cluster(独立集群)';
COMMENT ON COLUMN clusters.default_namespace IS '默认命名空间前缀';
COMMENT ON COLUMN clusters.is_shared IS '是否为共享集群';
-- ===== 5. 修改 registries 表 =====
ALTER TABLE registries ADD COLUMN IF NOT EXISTS workspace_id VARCHAR(36) REFERENCES workspaces(id) ON DELETE SET NULL;
ALTER TABLE registries ADD COLUMN IF NOT EXISTS owner_id VARCHAR(36) REFERENCES users(id);
ALTER TABLE registries ADD COLUMN IF NOT EXISTS is_shared BOOLEAN DEFAULT FALSE;
CREATE INDEX IF NOT EXISTS idx_registries_workspace ON registries(workspace_id);
CREATE INDEX IF NOT EXISTS idx_registries_owner ON registries(owner_id);
CREATE INDEX IF NOT EXISTS idx_registries_is_shared ON registries(is_shared);
-- 删除旧的唯一约束
ALTER TABLE registries DROP CONSTRAINT IF EXISTS registries_name_key;
COMMENT ON COLUMN registries.workspace_id IS '所属工作空间 ID';
COMMENT ON COLUMN registries.owner_id IS '创建者用户 ID';
COMMENT ON COLUMN registries.is_shared IS '是否为共享注册表';
-- ===== 6. 创建 storage_backends 表 =====
CREATE TABLE IF NOT EXISTS storage_backends (
id VARCHAR(36) PRIMARY KEY,
workspace_id VARCHAR(36) REFERENCES workspaces(id) ON DELETE CASCADE,
owner_id VARCHAR(36) REFERENCES users(id),
name VARCHAR(255) NOT NULL,
type VARCHAR(50) NOT NULL, -- 'nfs', 'pv', 'hostPath'
config JSONB NOT NULL, -- 存储配置
description TEXT,
is_default BOOLEAN DEFAULT FALSE,
is_shared BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(workspace_id, name)
);
CREATE INDEX IF NOT EXISTS idx_storage_backends_workspace ON storage_backends(workspace_id);
CREATE INDEX IF NOT EXISTS idx_storage_backends_owner ON storage_backends(owner_id);
CREATE INDEX IF NOT EXISTS idx_storage_backends_type ON storage_backends(type);
COMMENT ON TABLE storage_backends IS '存储后端表 (NFS/PV/HostPath)';
COMMENT ON COLUMN storage_backends.type IS '存储类型: nfs, pv, hostPath';
COMMENT ON COLUMN storage_backends.config IS '存储配置 (JSON)';
-- ===== 7. 创建 chart_references 表 =====
CREATE TABLE IF NOT EXISTS chart_references (
id VARCHAR(36) PRIMARY KEY,
workspace_id VARCHAR(36) REFERENCES workspaces(id) ON DELETE CASCADE,
registry_id VARCHAR(36) REFERENCES registries(id) ON DELETE CASCADE,
repository VARCHAR(500) NOT NULL, -- OCI repository path
chart_name VARCHAR(255) NOT NULL,
description TEXT,
is_enabled BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(workspace_id, registry_id, repository)
);
CREATE INDEX IF NOT EXISTS idx_chart_references_workspace ON chart_references(workspace_id);
CREATE INDEX IF NOT EXISTS idx_chart_references_registry ON chart_references(registry_id);
COMMENT ON TABLE chart_references IS 'Helm Chart 引用表';
-- ===== 8. 创建 values_templates 表 =====
CREATE TABLE IF NOT EXISTS values_templates (
id VARCHAR(36) PRIMARY KEY,
workspace_id VARCHAR(36) REFERENCES workspaces(id) ON DELETE CASCADE,
owner_id VARCHAR(36) REFERENCES users(id),
chart_reference_id VARCHAR(36) REFERENCES chart_references(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
values_yaml TEXT NOT NULL,
version INTEGER NOT NULL DEFAULT 1,
is_default BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(workspace_id, chart_reference_id, name, version)
);
CREATE INDEX IF NOT EXISTS idx_values_templates_workspace ON values_templates(workspace_id);
CREATE INDEX IF NOT EXISTS idx_values_templates_chart ON values_templates(chart_reference_id);
CREATE INDEX IF NOT EXISTS idx_values_templates_name ON values_templates(name);
COMMENT ON TABLE values_templates IS 'Values 模板表(带版本管理)';
COMMENT ON COLUMN values_templates.version IS '模板版本号';
-- ===== 9. 创建 user_config_overrides 表 =====
CREATE TABLE IF NOT EXISTS user_config_overrides (
id VARCHAR(36) PRIMARY KEY,
workspace_id VARCHAR(36) REFERENCES workspaces(id) ON DELETE CASCADE,
user_id VARCHAR(36) REFERENCES users(id),
target_type VARCHAR(50) NOT NULL, -- 'storage', 'template', 'global'
target_id VARCHAR(36),
config JSONB NOT NULL, -- 覆盖配置
priority INTEGER DEFAULT 0, -- 优先级
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_user_config_overrides_workspace ON user_config_overrides(workspace_id);
CREATE INDEX IF NOT EXISTS idx_user_config_overrides_user ON user_config_overrides(user_id);
CREATE INDEX IF NOT EXISTS idx_user_config_overrides_target ON user_config_overrides(target_type, target_id);
COMMENT ON TABLE user_config_overrides IS '用户配置覆盖表';
COMMENT ON COLUMN user_config_overrides.target_type IS '目标类型: storage, template, global';
COMMENT ON COLUMN user_config_overrides.priority IS '优先级(越高越优先)';
-- ===== 10. 修改 instances 表 =====
ALTER TABLE instances ADD COLUMN IF NOT EXISTS workspace_id VARCHAR(36) REFERENCES workspaces(id) ON DELETE SET NULL;
ALTER TABLE instances ADD COLUMN IF NOT EXISTS owner_id VARCHAR(36) REFERENCES users(id);
ALTER TABLE instances ADD COLUMN IF NOT EXISTS chart_reference_id VARCHAR(36) REFERENCES chart_references(id) ON DELETE SET NULL;
ALTER TABLE instances ADD COLUMN IF NOT EXISTS values_template_id VARCHAR(36) REFERENCES values_templates(id) ON DELETE SET NULL;
ALTER TABLE instances ADD COLUMN IF NOT EXISTS user_override_yaml TEXT;
ALTER TABLE instances ADD COLUMN IF NOT EXISTS cpu_requested DECIMAL(10,2) DEFAULT 0;
ALTER TABLE instances ADD COLUMN IF NOT EXISTS memory_requested VARCHAR(50) DEFAULT '0Mi';
ALTER TABLE instances ADD COLUMN IF NOT EXISTS gpu_requested DECIMAL(10,2) DEFAULT 0;
ALTER TABLE instances ADD COLUMN IF NOT EXISTS gpu_memory_requested VARCHAR(50) DEFAULT '0Mi';
CREATE INDEX IF NOT EXISTS idx_instances_workspace ON instances(workspace_id);
CREATE INDEX IF NOT EXISTS idx_instances_owner ON instances(owner_id);
COMMENT ON COLUMN instances.workspace_id IS '所属工作空间 ID';
COMMENT ON COLUMN instances.owner_id IS '创建者用户 ID';
COMMENT ON COLUMN instances.chart_reference_id IS 'Chart 引用 ID';
COMMENT ON COLUMN instances.values_template_id IS 'Values 模板 ID';
COMMENT ON COLUMN instances.user_override_yaml IS '用户覆盖配置';
COMMENT ON COLUMN instances.cpu_requested IS '请求的 CPU 核数';
COMMENT ON COLUMN instances.memory_requested IS '请求的内存';
COMMENT ON COLUMN instances.gpu_requested IS '请求的 GPU 卡数';
COMMENT ON COLUMN instances.gpu_memory_requested IS '请求的 GPU 内存';
-- ===== 11. 创建 audit_logs 表 =====
CREATE TABLE IF NOT EXISTS audit_logs (
id VARCHAR(36) PRIMARY KEY,
workspace_id VARCHAR(36),
user_id VARCHAR(36) REFERENCES users(id),
action VARCHAR(100) NOT NULL, -- 'create', 'update', 'delete', 'deploy', 'scale'
resource_type VARCHAR(50) NOT NULL, -- 'cluster', 'registry', 'instance', 'quota', 'user', 'workspace'
resource_id VARCHAR(36),
resource_name VARCHAR(255),
details JSONB,
ip_address VARCHAR(50),
user_agent TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_audit_logs_workspace ON audit_logs(workspace_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_user ON audit_logs(user_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_action ON audit_logs(action);
CREATE INDEX IF NOT EXISTS idx_audit_logs_resource ON audit_logs(resource_type, resource_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_created ON audit_logs(created_at);
COMMENT ON TABLE audit_logs IS '审计日志表';
COMMENT ON COLUMN audit_logs.action IS '操作类型: create, update, delete, deploy, scale';
COMMENT ON COLUMN audit_logs.resource_type IS '资源类型: cluster, registry, instance, quota, user, workspace';
-- ===== 12. 插入迁移版本 =====
INSERT INTO schema_migrations (version) VALUES ('v2.0.0')
ON CONFLICT (version) DO NOTHING;
-- ===== 13. 更新现有 admin 用户为 admin 角色 =====
UPDATE users SET role = 'admin', workspace_id = NULL WHERE username = 'admin' AND role = 'user';
-- ===== 14. 创建默认 workspace可选用于旧数据兼容=====
-- 如果需要将现有数据迁移到默认 workspace取消下面注释
-- INSERT INTO workspaces (id, name, description)
-- VALUES (gen_random_uuid(), 'default', '默认工作空间')
-- ON CONFLICT (name) DO NOTHING;
--
-- UPDATE clusters SET workspace_id = (SELECT id FROM workspaces WHERE name = 'default')
-- WHERE workspace_id IS NULL;
--
-- UPDATE registries SET workspace_id = (SELECT id FROM workspaces WHERE name = 'default')
-- WHERE workspace_id IS NULL;
--
-- UPDATE instances SET workspace_id = (SELECT id FROM workspaces WHERE name = 'default')
-- WHERE workspace_id IS NULL;
-- ===== 迁移完成 =====
DO $$
BEGIN
RAISE NOTICE 'Migration v2.0.0 completed successfully!';
END $$;