diff --git a/biome.json b/biome.json index 35457fa41..649f42251 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.4.4/schema.json", + "$schema": "https://biomejs.dev/schemas/2.4.12/schema.json", "vcs": { "enabled": true, "clientKind": "git", diff --git a/drizzle/0091_daily_carnage.sql b/drizzle/0091_daily_carnage.sql new file mode 100644 index 000000000..101d7ac82 --- /dev/null +++ b/drizzle/0091_daily_carnage.sql @@ -0,0 +1,2 @@ +ALTER TABLE "system_settings" ADD COLUMN "public_status_window_hours" integer DEFAULT 24 NOT NULL;--> statement-breakpoint +ALTER TABLE "system_settings" ADD COLUMN "public_status_aggregation_interval_minutes" integer DEFAULT 5 NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0091_snapshot.json b/drizzle/meta/0091_snapshot.json new file mode 100644 index 000000000..411967c75 --- /dev/null +++ b/drizzle/meta/0091_snapshot.json @@ -0,0 +1,4409 @@ +{ + "id": "e913c888-7269-4522-8c99-b6580bef6b01", + "prevId": "f5f98cc5-1a95-4e2e-bf30-59b2ed1465f2", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "action_category": { + "name": "action_category", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "target_name": { + "name": "target_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "before_value": { + "name": "before_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "after_value": { + "name": "after_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "operator_user_id": { + "name": "operator_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_user_name": { + "name": "operator_user_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_key_id": { + "name": "operator_key_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_key_name": { + "name": "operator_key_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_ip": { + "name": "operator_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_audit_log_category_created_at": { + "name": "idx_audit_log_category_created_at", + "columns": [ + { + "expression": "action_category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_user_created_at": { + "name": "idx_audit_log_operator_user_created_at", + "columns": [ + { + "expression": "operator_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_ip_created_at": { + "name": "idx_audit_log_operator_ip_created_at", + "columns": [ + { + "expression": "operator_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_target": { + "name": "idx_audit_log_target", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"target_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_created_at_id": { + "name": "idx_audit_log_created_at_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.error_rules": { + "name": "error_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'regex'" + }, + "category": { + "name": "category", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "override_response": { + "name": "override_response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "override_status_code": { + "name": "override_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_error_rules_enabled": { + "name": "idx_error_rules_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_pattern": { + "name": "unique_pattern", + "columns": [ + { + "expression": "pattern", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category": { + "name": "idx_category", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_match_type": { + "name": "idx_match_type", + "columns": [ + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.keys": { + "name": "keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "can_login_web_ui": { + "name": "can_login_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_keys_user_id": { + "name": "idx_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_key": { + "name": "idx_keys_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_created_at": { + "name": "idx_keys_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_deleted_at": { + "name": "idx_keys_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_request": { + "name": "message_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "cost_breakdown": { + "name": "cost_breakdown", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "request_sequence": { + "name": "request_sequence", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "provider_chain": { + "name": "provider_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "special_settings": { + "name": "special_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_stack": { + "name": "error_stack", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_cause": { + "name": "error_cause", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "messages_count": { + "name": "messages_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_message_request_user_date_cost": { + "name": "idx_message_request_user_date_cost", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_created_at_cost_stats": { + "name": "idx_message_request_user_created_at_cost_stats", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_query": { + "name": "idx_message_request_user_query", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_active": { + "name": "idx_message_request_provider_created_at_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_finalized_active": { + "name": "idx_message_request_provider_created_at_finalized_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id": { + "name": "idx_message_request_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix": { + "name": "idx_message_request_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_seq": { + "name": "idx_message_request_session_seq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_endpoint": { + "name": "idx_message_request_endpoint", + "columns": [ + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_blocked_by": { + "name": "idx_message_request_blocked_by", + "columns": [ + { + "expression": "blocked_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_id": { + "name": "idx_message_request_provider_id", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_id": { + "name": "idx_message_request_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key": { + "name": "idx_message_request_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_created_at_id": { + "name": "idx_message_request_key_created_at_id", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_model_active": { + "name": "idx_message_request_key_model_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_endpoint_active": { + "name": "idx_message_request_key_endpoint_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"endpoint\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at_id_active": { + "name": "idx_message_request_created_at_id_active", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_model_active": { + "name": "idx_message_request_model_active", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_status_code_active": { + "name": "idx_message_request_status_code_active", + "columns": [ + { + "expression": "status_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at": { + "name": "idx_message_request_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_deleted_at": { + "name": "idx_message_request_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_last_active": { + "name": "idx_message_request_key_last_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_cost_active": { + "name": "idx_message_request_key_cost_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_user_info": { + "name": "idx_message_request_session_user_info", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_client_ip_created_at": { + "name": "idx_message_request_client_ip_created_at", + "columns": [ + { + "expression": "client_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"client_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_prices": { + "name": "model_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "model_name": { + "name": "model_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "price_data": { + "name": "price_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'litellm'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_model_prices_latest": { + "name": "idx_model_prices_latest", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_model_name": { + "name": "idx_model_prices_model_name", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_created_at": { + "name": "idx_model_prices_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_source": { + "name": "idx_model_prices_source", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_settings": { + "name": "notification_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "use_legacy_mode": { + "name": "use_legacy_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_enabled": { + "name": "circuit_breaker_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_webhook": { + "name": "circuit_breaker_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_enabled": { + "name": "daily_leaderboard_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "daily_leaderboard_webhook": { + "name": "daily_leaderboard_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_time": { + "name": "daily_leaderboard_time", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'09:00'" + }, + "daily_leaderboard_top_n": { + "name": "daily_leaderboard_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cost_alert_enabled": { + "name": "cost_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cost_alert_webhook": { + "name": "cost_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cost_alert_threshold": { + "name": "cost_alert_threshold", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": false, + "default": "'0.80'" + }, + "cost_alert_check_interval": { + "name": "cost_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 60 + }, + "cache_hit_rate_alert_enabled": { + "name": "cache_hit_rate_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cache_hit_rate_alert_webhook": { + "name": "cache_hit_rate_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cache_hit_rate_alert_window_mode": { + "name": "cache_hit_rate_alert_window_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "cache_hit_rate_alert_check_interval": { + "name": "cache_hit_rate_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cache_hit_rate_alert_historical_lookback_days": { + "name": "cache_hit_rate_alert_historical_lookback_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 7 + }, + "cache_hit_rate_alert_min_eligible_requests": { + "name": "cache_hit_rate_alert_min_eligible_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 20 + }, + "cache_hit_rate_alert_min_eligible_tokens": { + "name": "cache_hit_rate_alert_min_eligible_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cache_hit_rate_alert_abs_min": { + "name": "cache_hit_rate_alert_abs_min", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "cache_hit_rate_alert_drop_rel": { + "name": "cache_hit_rate_alert_drop_rel", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.3'" + }, + "cache_hit_rate_alert_drop_abs": { + "name": "cache_hit_rate_alert_drop_abs", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.1'" + }, + "cache_hit_rate_alert_cooldown_minutes": { + "name": "cache_hit_rate_alert_cooldown_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cache_hit_rate_alert_top_n": { + "name": "cache_hit_rate_alert_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_target_bindings": { + "name": "notification_target_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "schedule_cron": { + "name": "schedule_cron", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "schedule_timezone": { + "name": "schedule_timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "template_override": { + "name": "template_override", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "unique_notification_target_binding": { + "name": "unique_notification_target_binding", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_type": { + "name": "idx_notification_bindings_type", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_target": { + "name": "idx_notification_bindings_target", + "columns": [ + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notification_target_bindings_target_id_webhook_targets_id_fk": { + "name": "notification_target_bindings_target_id_webhook_targets_id_fk", + "tableFrom": "notification_target_bindings", + "tableTo": "webhook_targets", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoint_probe_logs": { + "name": "provider_endpoint_probe_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "ok": { + "name": "ok", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_type": { + "name": "error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_endpoint_probe_logs_endpoint_created_at": { + "name": "idx_provider_endpoint_probe_logs_endpoint_created_at", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoint_probe_logs_created_at": { + "name": "idx_provider_endpoint_probe_logs_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk": { + "name": "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk", + "tableFrom": "provider_endpoint_probe_logs", + "tableTo": "provider_endpoints", + "columnsFrom": [ + "endpoint_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoints": { + "name": "provider_endpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "vendor_id": { + "name": "vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_probed_at": { + "name": "last_probed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_probe_ok": { + "name": "last_probe_ok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "last_probe_status_code": { + "name": "last_probe_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_latency_ms": { + "name": "last_probe_latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_type": { + "name": "last_probe_error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_message": { + "name": "last_probe_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "uniq_provider_endpoints_vendor_type_url": { + "name": "uniq_provider_endpoints_vendor_type_url", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_vendor_type": { + "name": "idx_provider_endpoints_vendor_type", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_enabled": { + "name": "idx_provider_endpoints_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_pick_enabled": { + "name": "idx_provider_endpoints_pick_enabled", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_created_at": { + "name": "idx_provider_endpoints_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_deleted_at": { + "name": "idx_provider_endpoints_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoints_vendor_id_provider_vendors_id_fk": { + "name": "provider_endpoints_vendor_id_provider_vendors_id_fk", + "tableFrom": "provider_endpoints", + "tableTo": "provider_vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_groups": { + "name": "provider_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'1.0'" + }, + "description": { + "name": "description", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "provider_groups_name_unique": { + "name": "provider_groups_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_vendors": { + "name": "provider_vendors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "website_domain": { + "name": "website_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "uniq_provider_vendors_website_domain": { + "name": "uniq_provider_vendors_website_domain", + "columns": [ + { + "expression": "website_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_vendors_created_at": { + "name": "idx_provider_vendors_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.providers": { + "name": "providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_vendor_id": { + "name": "provider_vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "group_priorities": { + "name": "group_priorities", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false, + "default": "'1.0'" + }, + "group_tag": { + "name": "group_tag", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "preserve_client_ip": { + "name": "preserve_client_ip", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "disable_session_reuse": { + "name": "disable_session_reuse", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "model_redirects": { + "name": "model_redirects", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "active_time_start": { + "name": "active_time_start", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "active_time_end": { + "name": "active_time_end", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "codex_instructions_strategy": { + "name": "codex_instructions_strategy", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "mcp_passthrough_type": { + "name": "mcp_passthrough_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "mcp_passthrough_url": { + "name": "mcp_passthrough_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_cost_reset_at": { + "name": "total_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "max_retry_attempts": { + "name": "max_retry_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "circuit_breaker_failure_threshold": { + "name": "circuit_breaker_failure_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "circuit_breaker_open_duration": { + "name": "circuit_breaker_open_duration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1800000 + }, + "circuit_breaker_half_open_success_threshold": { + "name": "circuit_breaker_half_open_success_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 2 + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "first_byte_timeout_streaming_ms": { + "name": "first_byte_timeout_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "streaming_idle_timeout_ms": { + "name": "streaming_idle_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_timeout_non_streaming_ms": { + "name": "request_timeout_non_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "swap_cache_ttl_billing": { + "name": "swap_cache_ttl_billing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "context_1m_preference": { + "name": "context_1m_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_effort_preference": { + "name": "codex_reasoning_effort_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_summary_preference": { + "name": "codex_reasoning_summary_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_text_verbosity_preference": { + "name": "codex_text_verbosity_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_parallel_tool_calls_preference": { + "name": "codex_parallel_tool_calls_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_service_tier_preference": { + "name": "codex_service_tier_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_max_tokens_preference": { + "name": "anthropic_max_tokens_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_thinking_budget_preference": { + "name": "anthropic_thinking_budget_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_adaptive_thinking": { + "name": "anthropic_adaptive_thinking", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "gemini_google_search_preference": { + "name": "gemini_google_search_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "tpm": { + "name": "tpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpm": { + "name": "rpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpd": { + "name": "rpd", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cc": { + "name": "cc", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_providers_enabled_priority": { + "name": "idx_providers_enabled_priority", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "weight", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_group": { + "name": "idx_providers_group", + "columns": [ + { + "expression": "group_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type_url_active": { + "name": "idx_providers_vendor_type_url_active", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_created_at": { + "name": "idx_providers_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_deleted_at": { + "name": "idx_providers_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type": { + "name": "idx_providers_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_enabled_vendor_type": { + "name": "idx_providers_enabled_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL AND \"providers\".\"is_enabled\" = true AND \"providers\".\"provider_vendor_id\" IS NOT NULL AND \"providers\".\"provider_vendor_id\" > 0", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "providers_provider_vendor_id_provider_vendors_id_fk": { + "name": "providers_provider_vendor_id_provider_vendors_id_fk", + "tableFrom": "providers", + "tableTo": "provider_vendors", + "columnsFrom": [ + "provider_vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.request_filters": { + "name": "request_filters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "binding_type": { + "name": "binding_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'global'" + }, + "provider_ids": { + "name": "provider_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "group_tags": { + "name": "group_tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rule_mode": { + "name": "rule_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'simple'" + }, + "execution_phase": { + "name": "execution_phase", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'guard'" + }, + "operations": { + "name": "operations", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_request_filters_enabled": { + "name": "idx_request_filters_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_scope": { + "name": "idx_request_filters_scope", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_action": { + "name": "idx_request_filters_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_binding": { + "name": "idx_request_filters_binding", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_phase": { + "name": "idx_request_filters_phase", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_phase", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sensitive_words": { + "name": "sensitive_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "word": { + "name": "word", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'contains'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_sensitive_words_enabled": { + "name": "idx_sensitive_words_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sensitive_words_created_at": { + "name": "idx_sensitive_words_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "site_title": { + "name": "site_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "default": "'Claude Code Hub'" + }, + "allow_global_usage_view": { + "name": "allow_global_usage_view", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_display": { + "name": "currency_display", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "billing_model_source": { + "name": "billing_model_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'original'" + }, + "codex_priority_billing_source": { + "name": "codex_priority_billing_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "timezone": { + "name": "timezone", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "enable_auto_cleanup": { + "name": "enable_auto_cleanup", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cleanup_retention_days": { + "name": "cleanup_retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cleanup_schedule": { + "name": "cleanup_schedule", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false, + "default": "'0 2 * * *'" + }, + "cleanup_batch_size": { + "name": "cleanup_batch_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10000 + }, + "enable_client_version_check": { + "name": "enable_client_version_check", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verbose_provider_error": { + "name": "verbose_provider_error", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_http2": { + "name": "enable_http2", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_high_concurrency_mode": { + "name": "enable_high_concurrency_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "intercept_anthropic_warmup_requests": { + "name": "intercept_anthropic_warmup_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_thinking_signature_rectifier": { + "name": "enable_thinking_signature_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_thinking_budget_rectifier": { + "name": "enable_thinking_budget_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_billing_header_rectifier": { + "name": "enable_billing_header_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_input_rectifier": { + "name": "enable_response_input_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_codex_session_id_completion": { + "name": "enable_codex_session_id_completion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_claude_metadata_user_id_injection": { + "name": "enable_claude_metadata_user_id_injection", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_fixer": { + "name": "enable_response_fixer", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "response_fixer_config": { + "name": "response_fixer_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"fixTruncatedJson\":true,\"fixSseFormat\":true,\"fixEncoding\":true,\"maxJsonDepth\":200,\"maxFixSize\":1048576}'::jsonb" + }, + "quota_db_refresh_interval_seconds": { + "name": "quota_db_refresh_interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "quota_lease_percent_5h": { + "name": "quota_lease_percent_5h", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_daily": { + "name": "quota_lease_percent_daily", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_weekly": { + "name": "quota_lease_percent_weekly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_monthly": { + "name": "quota_lease_percent_monthly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_cap_usd": { + "name": "quota_lease_cap_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "ip_extraction_config": { + "name": "ip_extraction_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ip_geo_lookup_enabled": { + "name": "ip_geo_lookup_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "public_status_window_hours": { + "name": "public_status_window_hours", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 24 + }, + "public_status_aggregation_interval_minutes": { + "name": "public_status_aggregation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_ledger": { + "name": "usage_ledger", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "final_provider_id": { + "name": "final_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_success": { + "name": "is_success", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_usage_ledger_request_id": { + "name": "idx_usage_ledger_request_id", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_created_at": { + "name": "idx_usage_ledger_user_created_at", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at": { + "name": "idx_usage_ledger_key_created_at", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_created_at": { + "name": "idx_usage_ledger_provider_created_at", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_minute": { + "name": "idx_usage_ledger_created_at_minute", + "columns": [ + { + "expression": "date_trunc('minute', \"created_at\" AT TIME ZONE 'UTC')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_desc_id": { + "name": "idx_usage_ledger_created_at_desc_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_id": { + "name": "idx_usage_ledger_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"session_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_model": { + "name": "idx_usage_ledger_model", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_cost": { + "name": "idx_usage_ledger_key_cost", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_cost_cover": { + "name": "idx_usage_ledger_user_cost_cover", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_cost_cover": { + "name": "idx_usage_ledger_provider_cost_cover", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at_desc_cover": { + "name": "idx_usage_ledger_key_created_at_desc_cover", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "rpm_limit": { + "name": "rpm_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_limit_usd": { + "name": "daily_limit_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_users_active_role_sort": { + "name": "idx_users_active_role_sort", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_enabled_expires_at": { + "name": "idx_users_enabled_expires_at", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_tags_gin": { + "name": "idx_users_tags_gin", + "columns": [ + { + "expression": "tags", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_users_created_at": { + "name": "idx_users_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook_targets": { + "name": "webhook_targets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "webhook_provider_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "webhook_url": { + "name": "webhook_url", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false + }, + "telegram_bot_token": { + "name": "telegram_bot_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "telegram_chat_id": { + "name": "telegram_chat_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "dingtalk_secret": { + "name": "dingtalk_secret", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "custom_template": { + "name": "custom_template", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_test_at": { + "name": "last_test_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_test_result": { + "name": "last_test_result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.daily_reset_mode": { + "name": "daily_reset_mode", + "schema": "public", + "values": [ + "fixed", + "rolling" + ] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": [ + "circuit_breaker", + "daily_leaderboard", + "cost_alert", + "cache_hit_rate_alert" + ] + }, + "public.webhook_provider_type": { + "name": "webhook_provider_type", + "schema": "public", + "values": [ + "wechat", + "feishu", + "dingtalk", + "telegram", + "custom" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 7ef74d094..8be162aba 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -638,6 +638,13 @@ "when": 1776421578713, "tag": "0090_demonic_captain_universe", "breakpoints": true + }, + { + "idx": 91, + "version": "7", + "when": 1776767493777, + "tag": "0091_daily_carnage", + "breakpoints": true } ] } \ No newline at end of file diff --git a/messages/en/settings/config.json b/messages/en/settings/config.json index 6284e2608..eb8c9b633 100644 --- a/messages/en/settings/config.json +++ b/messages/en/settings/config.json @@ -36,6 +36,8 @@ "cleanupSchedulePlaceholder": "0 2 * * *", "cleanupScheduleRequired": "Execution Time (Cron) *", "configUpdated": "System settings updated. The page will refresh to apply currency display changes.", + "publicStatusProjectionWarning": "System settings were saved, but the public status Redis projection was not refreshed.", + "publicStatusBackgroundRefreshPending": "System settings were saved, but the public status page may stay stale until the background refresh succeeds.", "currencies": { "CNY": "¥ Chinese Yuan (CNY)", "EUR": "€ Euro (EUR)", diff --git a/messages/en/settings/index.ts b/messages/en/settings/index.ts index b89bfb600..56357e8ad 100644 --- a/messages/en/settings/index.ts +++ b/messages/en/settings/index.ts @@ -10,6 +10,7 @@ import notifications from "./notifications.json"; import prices from "./prices.json"; import requestFilters from "./requestFilters.json"; import sensitiveWords from "./sensitiveWords.json"; +import statusPage from "./statusPage.json"; import strings from "./strings.json"; import providersAutoSort from "./providers/autoSort.json"; @@ -110,6 +111,7 @@ export default { data, clientVersions, notifications, + statusPage, errors, errorRules, ...strings, diff --git a/messages/en/settings/nav.json b/messages/en/settings/nav.json index 5a3646860..f3b77757f 100644 --- a/messages/en/settings/nav.json +++ b/messages/en/settings/nav.json @@ -2,6 +2,7 @@ "apiDocs": "API Docs", "clientVersions": "Updates", "config": "Config", + "statusPage": "Status Page", "data": "Data", "docs": "Documentation", "errorRules": "Errors", diff --git a/messages/en/settings/statusPage.json b/messages/en/settings/statusPage.json new file mode 100644 index 000000000..0e1444f4a --- /dev/null +++ b/messages/en/settings/statusPage.json @@ -0,0 +1,58 @@ +{ + "title": "Public Status Page", + "description": "Configure the public status window, aggregation interval, and which groups/models should be exposed.", + "form": { + "windowHours": "Display Window (hours)", + "windowHoursDesc": "Controls how much history the public page aggregates. Default: 24 hours.", + "aggregationIntervalMinutes": "Aggregation Interval (minutes)", + "aggregationIntervalMinutesDesc": "How often the background job refreshes the public snapshot. It only runs after at least one group declares public models.", + "aggregationIntervalMinutesInvalid": "Public status aggregation interval must be one of 5, 15, 30, or 60 minutes.", + "intervalOption": "{minutes} min", + "slug": "Slug", + "copy": "Copy", + "sortOrder": "Sort Order", + "descriptionTooLong": "Public status configuration exceeds the 500 character limit of provider_groups.description.", + "projectionPublishFailed": "Public status Redis projection publish failed.", + "groupsTitle": "Public Groups and Models", + "groupsDesc": "Only groups with the toggle enabled and a non-empty model list will activate public status aggregation.", + "groupName": "Group", + "enabled": "Expose this group", + "displayName": "Public display name", + "displayNamePlaceholder": "Defaults to the group name", + "modelIds": "Public model IDs", + "modelIdsPlaceholder": "One model ID per line, e.g. gpt-4.1", + "modelIdsDesc": "Only enter model IDs to expose. The public page resolves display name and icon from the local price table.", + "helper": "The feature is off by default. Background aggregation starts only when at least one group has a non-empty public model list.", + "empty": "No provider groups available", + "save": "Save public status settings", + "saveSuccess": "Public status settings saved", + "saveFailed": "Failed to save public status settings", + "backgroundRefreshPending": "Settings saved, but the public status page may stay stale until the background refresh succeeds." + }, + "public": { + "systemStatus": "System Status", + "heroPrimary": "AI SERVICES", + "heroSecondary": "INTELLIGENCE MONITOR", + "generatedAt": "Updated", + "ttfb": "TTFB", + "tps": "TPS", + "history": "History", + "freshnessWindow": "Snapshot freshness", + "availability": "Availability", + "noData": "No data", + "operational": "Operational", + "failed": "Failed", + "fresh": "Fresh", + "stale": "Stale", + "rebuilding": "Rebuilding", + "emptyDescription": "We are preparing the first public snapshot for this page.", + "past": "Past", + "now": "Now", + "requestTypes": { + "openaiCompatible": "OpenAI Compatible", + "codex": "Codex", + "anthropic": "Anthropic", + "gemini": "Gemini" + } + } +} diff --git a/messages/ja/settings/config.json b/messages/ja/settings/config.json index e69f07e33..8628881ea 100644 --- a/messages/ja/settings/config.json +++ b/messages/ja/settings/config.json @@ -36,6 +36,8 @@ "cleanupSchedulePlaceholder": "0 2 * * *", "cleanupScheduleRequired": "実行時間(Cron)*", "configUpdated": "システム設定が更新されました。ページが更新され、通貨表示の変更が適用されます。", + "publicStatusProjectionWarning": "システム設定は保存されましたが、public status の Redis 投影は更新されませんでした。", + "publicStatusBackgroundRefreshPending": "システム設定は保存されましたが、バックグラウンド更新が成功するまで公開ステータスページに古いデータが表示される場合があります。", "currencies": { "CNY": "¥ 人民元 (CNY)", "EUR": "€ ユーロ (EUR)", diff --git a/messages/ja/settings/index.ts b/messages/ja/settings/index.ts index b89bfb600..56357e8ad 100644 --- a/messages/ja/settings/index.ts +++ b/messages/ja/settings/index.ts @@ -10,6 +10,7 @@ import notifications from "./notifications.json"; import prices from "./prices.json"; import requestFilters from "./requestFilters.json"; import sensitiveWords from "./sensitiveWords.json"; +import statusPage from "./statusPage.json"; import strings from "./strings.json"; import providersAutoSort from "./providers/autoSort.json"; @@ -110,6 +111,7 @@ export default { data, clientVersions, notifications, + statusPage, errors, errorRules, ...strings, diff --git a/messages/ja/settings/nav.json b/messages/ja/settings/nav.json index 52e634fc2..2506d5997 100644 --- a/messages/ja/settings/nav.json +++ b/messages/ja/settings/nav.json @@ -2,6 +2,7 @@ "apiDocs": "API文書", "clientVersions": "更新通知", "config": "設定", + "statusPage": "ステータスページ", "data": "データ", "docs": "ドキュメント", "errorRules": "エラー", diff --git a/messages/ja/settings/statusPage.json b/messages/ja/settings/statusPage.json new file mode 100644 index 000000000..df9663ea0 --- /dev/null +++ b/messages/ja/settings/statusPage.json @@ -0,0 +1,58 @@ +{ + "title": "公開ステータスページ", + "description": "公開ステータスページの集計期間、集計間隔、および公開するグループ/モデルを設定します。", + "form": { + "windowHours": "表示期間(時間)", + "windowHoursDesc": "公開ページが集計する履歴期間を指定します。既定値は 24 時間です。", + "aggregationIntervalMinutes": "集計間隔(分)", + "aggregationIntervalMinutesDesc": "バックグラウンド集計がスナップショットを更新する周期です。公開グループとモデルが設定された場合のみ有効になります。", + "aggregationIntervalMinutesInvalid": "公開ステータスの集計間隔は 5、15、30、60 分のいずれかである必要があります。", + "intervalOption": "{minutes} 分", + "slug": "Slug", + "copy": "説明文", + "sortOrder": "並び順", + "descriptionTooLong": "公開ステータス設定が provider_groups.description の 500 文字制限を超えています。", + "projectionPublishFailed": "公開ステータスの Redis 投影の公開に失敗しました。", + "groupsTitle": "公開グループとモデル", + "groupsDesc": "公開を有効にし、かつモデル ID が入力されたグループだけが公開ステータス集計を有効化します。", + "groupName": "グループ", + "enabled": "このグループを公開", + "displayName": "公開表示名", + "displayNamePlaceholder": "未入力時はグループ名を使用", + "modelIds": "公開モデル ID", + "modelIdsPlaceholder": "1 行に 1 つのモデル ID(例: gpt-4.1)", + "modelIdsDesc": "公開するモデル ID のみ入力してください。表示名とアイコンはローカル価格表から解決されます。", + "helper": "機能は既定で無効です。少なくとも 1 つのグループに空でないモデル一覧が設定された場合のみ、定期集計が開始されます。", + "empty": "設定可能なグループがありません", + "save": "公開ステータス設定を保存", + "saveSuccess": "公開ステータス設定を保存しました", + "saveFailed": "公開ステータス設定の保存に失敗しました", + "backgroundRefreshPending": "設定は保存されましたが、バックグラウンド更新が成功するまで公開ステータスページに古いデータが表示される場合があります。" + }, + "public": { + "systemStatus": "システム状態", + "heroPrimary": "AI SERVICES", + "heroSecondary": "INTELLIGENCE MONITOR", + "generatedAt": "更新", + "ttfb": "TTFB", + "tps": "TPS", + "history": "履歴", + "freshnessWindow": "スナップショット有効期限", + "availability": "可用率", + "noData": "データなし", + "operational": "正常", + "failed": "失敗", + "fresh": "最新", + "stale": "古い", + "rebuilding": "再構築中", + "emptyDescription": "このページの最初の公開スナップショットを準備しています。", + "past": "過去", + "now": "現在", + "requestTypes": { + "openaiCompatible": "OpenAI 互換", + "codex": "Codex", + "anthropic": "Anthropic", + "gemini": "Gemini" + } + } +} diff --git a/messages/ru/settings/config.json b/messages/ru/settings/config.json index 86e4b8c48..c8165d266 100644 --- a/messages/ru/settings/config.json +++ b/messages/ru/settings/config.json @@ -36,6 +36,8 @@ "cleanupSchedulePlaceholder": "0 2 * * *", "cleanupScheduleRequired": "Время выполнения (Cron) *", "configUpdated": "Параметры системы обновлены. Страница обновится для применения изменений валюты.", + "publicStatusProjectionWarning": "Системные настройки сохранены, но Redis-проекция public status не была обновлена.", + "publicStatusBackgroundRefreshPending": "Системные настройки сохранены, но публичная статус-страница может временно показывать устаревшие данные, пока фоновое обновление не завершится.", "currencies": { "CNY": "¥ Китайский юань (CNY)", "EUR": "€ Евро (EUR)", diff --git a/messages/ru/settings/index.ts b/messages/ru/settings/index.ts index b89bfb600..56357e8ad 100644 --- a/messages/ru/settings/index.ts +++ b/messages/ru/settings/index.ts @@ -10,6 +10,7 @@ import notifications from "./notifications.json"; import prices from "./prices.json"; import requestFilters from "./requestFilters.json"; import sensitiveWords from "./sensitiveWords.json"; +import statusPage from "./statusPage.json"; import strings from "./strings.json"; import providersAutoSort from "./providers/autoSort.json"; @@ -110,6 +111,7 @@ export default { data, clientVersions, notifications, + statusPage, errors, errorRules, ...strings, diff --git a/messages/ru/settings/nav.json b/messages/ru/settings/nav.json index 0e57e3b28..d90e990e9 100644 --- a/messages/ru/settings/nav.json +++ b/messages/ru/settings/nav.json @@ -2,6 +2,7 @@ "apiDocs": "API док.", "clientVersions": "Обновления", "config": "Конфиг", + "statusPage": "Статус", "data": "Данные", "docs": "Документация", "errorRules": "Ошибки", diff --git a/messages/ru/settings/statusPage.json b/messages/ru/settings/statusPage.json new file mode 100644 index 000000000..291c5f9c7 --- /dev/null +++ b/messages/ru/settings/statusPage.json @@ -0,0 +1,58 @@ +{ + "title": "Публичная страница статуса", + "description": "Настройте окно агрегации, интервал агрегации и группы/модели, которые будут показаны публично.", + "form": { + "windowHours": "Окно отображения (часы)", + "windowHoursDesc": "Определяет глубину истории для публичной страницы. По умолчанию: 24 часа.", + "aggregationIntervalMinutes": "Интервал агрегации (минуты)", + "aggregationIntervalMinutesDesc": "Как часто фоновая задача обновляет публичный снимок. Запускается только после настройки хотя бы одной группы с моделями.", + "aggregationIntervalMinutesInvalid": "Интервал агрегации публичного статуса должен быть одним из 5, 15, 30 или 60 минут.", + "intervalOption": "{minutes} мин", + "slug": "Slug", + "copy": "Пояснение", + "sortOrder": "Порядок", + "descriptionTooLong": "Конфигурация публичного статуса превышает лимит 500 символов для provider_groups.description.", + "projectionPublishFailed": "Не удалось опубликовать Redis-проекцию публичного статуса.", + "groupsTitle": "Публичные группы и модели", + "groupsDesc": "Публичная агрегация включается только для групп с включенным переключателем и непустым списком моделей.", + "groupName": "Группа", + "enabled": "Показывать эту группу", + "displayName": "Публичное имя", + "displayNamePlaceholder": "По умолчанию используется имя группы", + "modelIds": "Публичные ID моделей", + "modelIdsPlaceholder": "Один ID модели на строку, например: gpt-4.1", + "modelIdsDesc": "Укажите только те ID моделей, которые нужно показать. Имя и иконка будут определены из локальной таблицы цен.", + "helper": "По умолчанию функция отключена. Фоновая агрегация запускается только если хотя бы у одной группы есть непустой список моделей.", + "empty": "Нет доступных групп", + "save": "Сохранить настройки публичного статуса", + "saveSuccess": "Настройки публичного статуса сохранены", + "saveFailed": "Не удалось сохранить настройки публичного статуса", + "backgroundRefreshPending": "Настройки сохранены, но публичная статус-страница может временно показывать устаревшие данные, пока фоновое обновление не завершится." + }, + "public": { + "systemStatus": "Статус системы", + "heroPrimary": "AI SERVICES", + "heroSecondary": "INTELLIGENCE MONITOR", + "generatedAt": "Обновлено", + "ttfb": "TTFB", + "tps": "TPS", + "history": "История", + "freshnessWindow": "Свежесть снимка", + "availability": "Доступность", + "noData": "Нет данных", + "operational": "Доступно", + "failed": "Сбой", + "fresh": "Актуально", + "stale": "Устарело", + "rebuilding": "Перестраивается", + "emptyDescription": "Мы готовим первый публичный снимок для этой страницы.", + "past": "Прошлое", + "now": "Сейчас", + "requestTypes": { + "openaiCompatible": "Совместимый с OpenAI", + "codex": "Codex", + "anthropic": "Anthropic", + "gemini": "Gemini" + } + } +} diff --git a/messages/zh-CN/settings/config.json b/messages/zh-CN/settings/config.json index 79aacfe41..257d0bc6b 100644 --- a/messages/zh-CN/settings/config.json +++ b/messages/zh-CN/settings/config.json @@ -78,6 +78,8 @@ "saveFailed": "保存失败", "saveError": "保存失败", "configUpdated": "系统设置已更新,页面将刷新以应用货币显示变更", + "publicStatusProjectionWarning": "系统设置已保存,但 public status Redis 投影未刷新。", + "publicStatusBackgroundRefreshPending": "系统设置已保存,但公开状态页可能会在后台刷新成功前暂时保持旧数据。", "enableAutoCleanup": "启用自动清理", "enableAutoCleanupDesc": "定时自动清理历史日志数据", "cleanupRetentionDays": "保留天数", diff --git a/messages/zh-CN/settings/index.ts b/messages/zh-CN/settings/index.ts index b89bfb600..56357e8ad 100644 --- a/messages/zh-CN/settings/index.ts +++ b/messages/zh-CN/settings/index.ts @@ -10,6 +10,7 @@ import notifications from "./notifications.json"; import prices from "./prices.json"; import requestFilters from "./requestFilters.json"; import sensitiveWords from "./sensitiveWords.json"; +import statusPage from "./statusPage.json"; import strings from "./strings.json"; import providersAutoSort from "./providers/autoSort.json"; @@ -110,6 +111,7 @@ export default { data, clientVersions, notifications, + statusPage, errors, errorRules, ...strings, diff --git a/messages/zh-CN/settings/nav.json b/messages/zh-CN/settings/nav.json index e3294525f..74dd37815 100644 --- a/messages/zh-CN/settings/nav.json +++ b/messages/zh-CN/settings/nav.json @@ -1,5 +1,6 @@ { "config": "配置", + "statusPage": "状态页", "prices": "价格表", "providers": "供应商", "sensitiveWords": "敏感词", diff --git a/messages/zh-CN/settings/statusPage.json b/messages/zh-CN/settings/statusPage.json new file mode 100644 index 000000000..b17e56217 --- /dev/null +++ b/messages/zh-CN/settings/statusPage.json @@ -0,0 +1,58 @@ +{ + "title": "公开状态页面", + "description": "配置公开状态页面的聚合窗口、聚合间隔,以及需要对外展示的分组和模型。", + "form": { + "windowHours": "展示时间长度(小时)", + "windowHoursDesc": "控制公开页面聚合覆盖的时间窗口。默认 24 小时。", + "aggregationIntervalMinutes": "聚合间隔(分钟)", + "aggregationIntervalMinutesDesc": "定时聚合写快照的周期。只有配置了公开分组和模型后才会生效。", + "aggregationIntervalMinutesInvalid": "公开状态聚合间隔只能是 5、15、30、60 分钟之一。", + "intervalOption": "{minutes} 分钟", + "slug": "Slug", + "copy": "说明文案", + "sortOrder": "排序", + "descriptionTooLong": "公开状态配置超过 provider_groups.description 的 500 字符限制。", + "projectionPublishFailed": "公开状态 Redis 投影发布失败。", + "groupsTitle": "公开分组与模型", + "groupsDesc": "只有勾选并填写模型 ID 的分组,才会启用公开状态聚合。", + "groupName": "分组", + "enabled": "公开此分组", + "displayName": "对外显示名", + "displayNamePlaceholder": "默认使用分组名", + "modelIds": "公开模型 ID", + "modelIdsPlaceholder": "每行一个模型 ID,例如:gpt-4.1", + "modelIdsDesc": "只需填写需要公开展示的模型 ID;公开页展示名和图标将从本地价格表解析。", + "helper": "功能默认关闭;只有至少一个分组配置了非空模型列表时,后台才会启动定时聚合。", + "empty": "暂无可配置分组", + "save": "保存公开状态配置", + "saveSuccess": "公开状态配置已保存", + "saveFailed": "保存公开状态配置失败", + "backgroundRefreshPending": "配置已保存,但公开状态页可能会在后台刷新成功前暂时保持旧数据。" + }, + "public": { + "systemStatus": "系统状态", + "heroPrimary": "AI 服务", + "heroSecondary": "智能状态面板", + "generatedAt": "更新于", + "ttfb": "TTFB", + "tps": "TPS", + "history": "历史", + "freshnessWindow": "快照新鲜期剩余", + "availability": "在线率", + "noData": "暂无数据", + "operational": "可用", + "failed": "失败", + "fresh": "最新", + "stale": "陈旧", + "rebuilding": "重建中", + "emptyDescription": "我们正在为此页面准备第一份公开快照。", + "past": "过去", + "now": "现在", + "requestTypes": { + "openaiCompatible": "OpenAI 兼容", + "codex": "Codex", + "anthropic": "Anthropic", + "gemini": "Gemini" + } + } +} diff --git a/messages/zh-TW/settings/config.json b/messages/zh-TW/settings/config.json index b3b8882c9..a061562a8 100644 --- a/messages/zh-TW/settings/config.json +++ b/messages/zh-TW/settings/config.json @@ -36,6 +36,8 @@ "cleanupSchedulePlaceholder": "0 2 * * *", "cleanupScheduleRequired": "執行時間(Cron) *", "configUpdated": "系統設定已更新,頁面將重新整理以應用貨幣顯示變更。", + "publicStatusProjectionWarning": "系統設定已儲存,但 public status Redis 投影尚未刷新。", + "publicStatusBackgroundRefreshPending": "系統設定已儲存,但公開狀態頁可能會在背景刷新成功前暫時維持舊資料。", "currencies": { "CNY": "¥ 人民幣(CNY)", "EUR": "€ 歐元(EUR)", diff --git a/messages/zh-TW/settings/index.ts b/messages/zh-TW/settings/index.ts index b89bfb600..56357e8ad 100644 --- a/messages/zh-TW/settings/index.ts +++ b/messages/zh-TW/settings/index.ts @@ -10,6 +10,7 @@ import notifications from "./notifications.json"; import prices from "./prices.json"; import requestFilters from "./requestFilters.json"; import sensitiveWords from "./sensitiveWords.json"; +import statusPage from "./statusPage.json"; import strings from "./strings.json"; import providersAutoSort from "./providers/autoSort.json"; @@ -110,6 +111,7 @@ export default { data, clientVersions, notifications, + statusPage, errors, errorRules, ...strings, diff --git a/messages/zh-TW/settings/nav.json b/messages/zh-TW/settings/nav.json index 36d33bde4..9bf38b42a 100644 --- a/messages/zh-TW/settings/nav.json +++ b/messages/zh-TW/settings/nav.json @@ -2,6 +2,7 @@ "apiDocs": "API 文檔", "clientVersions": "用戶端升級提醒", "config": "設定", + "statusPage": "狀態頁", "data": "資料管理", "docs": "使用文檔", "errorRules": "錯誤規則", diff --git a/messages/zh-TW/settings/statusPage.json b/messages/zh-TW/settings/statusPage.json new file mode 100644 index 000000000..72ab12f05 --- /dev/null +++ b/messages/zh-TW/settings/statusPage.json @@ -0,0 +1,58 @@ +{ + "title": "公開狀態頁面", + "description": "設定公開狀態頁面的聚合視窗、聚合間隔,以及需要對外展示的分組和模型。", + "form": { + "windowHours": "展示時間長度(小時)", + "windowHoursDesc": "控制公開頁面聚合覆蓋的時間視窗。預設 24 小時。", + "aggregationIntervalMinutes": "聚合間隔(分鐘)", + "aggregationIntervalMinutesDesc": "定時聚合寫入快照的週期。只有在設定了公開分組和模型後才會生效。", + "aggregationIntervalMinutesInvalid": "公開狀態聚合間隔只能是 5、15、30、60 分鐘之一。", + "intervalOption": "{minutes} 分鐘", + "slug": "Slug", + "copy": "說明文案", + "sortOrder": "排序", + "descriptionTooLong": "公開狀態設定超過 provider_groups.description 的 500 字元限制。", + "projectionPublishFailed": "公開狀態 Redis 投影發布失敗。", + "groupsTitle": "公開分組與模型", + "groupsDesc": "只有勾選並填寫模型 ID 的分組,才會啟用公開狀態聚合。", + "groupName": "分組", + "enabled": "公開此分組", + "displayName": "對外顯示名稱", + "displayNamePlaceholder": "預設使用分組名", + "modelIds": "公開模型 ID", + "modelIdsPlaceholder": "每行一個模型 ID,例如:gpt-4.1", + "modelIdsDesc": "只需填寫需要公開展示的模型 ID;公開頁的展示名與圖示會從本地價格表解析。", + "helper": "功能預設關閉;只有至少一個分組設定了非空模型列表時,後台才會啟動定時聚合。", + "empty": "暫無可設定分組", + "save": "儲存公開狀態設定", + "saveSuccess": "公開狀態設定已儲存", + "saveFailed": "儲存公開狀態設定失敗", + "backgroundRefreshPending": "設定已儲存,但公開狀態頁可能會在背景刷新成功前暫時維持舊資料。" + }, + "public": { + "systemStatus": "系統狀態", + "heroPrimary": "AI 服務", + "heroSecondary": "智慧狀態面板", + "generatedAt": "更新於", + "ttfb": "TTFB", + "tps": "TPS", + "history": "歷史", + "freshnessWindow": "快照新鮮期剩餘", + "availability": "在線率", + "noData": "暫無資料", + "operational": "可用", + "failed": "失敗", + "fresh": "最新", + "stale": "陳舊", + "rebuilding": "重建中", + "emptyDescription": "我們正在為此頁面準備第一份公開快照。", + "past": "過去", + "now": "現在", + "requestTypes": { + "openaiCompatible": "OpenAI 相容", + "codex": "Codex", + "anthropic": "Anthropic", + "gemini": "Gemini" + } + } +} diff --git a/src/actions/notifications.ts b/src/actions/notifications.ts index 82f5ab601..b066d2997 100644 --- a/src/actions/notifications.ts +++ b/src/actions/notifications.ts @@ -91,7 +91,7 @@ export async function testWebhookAction( return { success: false, error: "无权限执行此操作" }; } - if (!webhookUrl || !webhookUrl.trim()) { + if (!webhookUrl?.trim()) { return { success: false, error: "Webhook URL 不能为空" }; } diff --git a/src/actions/provider-groups.ts b/src/actions/provider-groups.ts index a7906bc6c..9407e4a92 100644 --- a/src/actions/provider-groups.ts +++ b/src/actions/provider-groups.ts @@ -5,6 +5,10 @@ import { emitActionAudit } from "@/lib/audit/emit"; import { getSession } from "@/lib/auth"; import { PROVIDER_GROUP } from "@/lib/constants/provider.constants"; import { logger } from "@/lib/logger"; +import { + parsePublicStatusDescription, + serializePublicStatusDescription, +} from "@/lib/public-status/config"; import { ERROR_CODES } from "@/lib/utils/error-messages"; import { parseProviderGroups } from "@/lib/utils/provider-group"; import { findAllProvidersFresh } from "@/repository/provider"; @@ -189,7 +193,7 @@ export async function createProviderGroup(input: { */ export async function updateProviderGroup( id: number, - input: { costMultiplier?: number; description?: string | null } + input: { costMultiplier?: number; description?: string | null; descriptionNote?: string | null } ): Promise> { const t = await getTranslations("settings.providers.providerGroups"); const tError = await getTranslations("errors"); @@ -211,10 +215,24 @@ export async function updateProviderGroup( } const beforeGroup = await findProviderGroupById(id); + const nextDescription = + input.descriptionNote !== undefined + ? serializePublicStatusDescription({ + note: input.descriptionNote, + publicStatus: parsePublicStatusDescription(beforeGroup?.description).publicStatus, + }) + : input.description; + if (nextDescription && nextDescription.length > 500) { + return { + ok: false, + error: t("descriptionTooLong"), + errorCode: "DESCRIPTION_TOO_LONG", + }; + } const updated = await repoUpdateProviderGroup(id, { costMultiplier: input.costMultiplier, - description: input.description, + description: nextDescription, }); if (!updated) { diff --git a/src/actions/public-status.ts b/src/actions/public-status.ts new file mode 100644 index 000000000..21b3dac97 --- /dev/null +++ b/src/actions/public-status.ts @@ -0,0 +1,220 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { getTranslations } from "next-intl/server"; +import { db } from "@/drizzle/db"; +import { locales } from "@/i18n/config"; +import { getSession } from "@/lib/auth"; +import { invalidateSystemSettingsCache } from "@/lib/config"; +import { logger } from "@/lib/logger"; +import { + collectEnabledPublicStatusGroups, + type EnabledPublicStatusGroup, + invalidateConfiguredPublicStatusGroupsCache, + parsePublicStatusDescription, + serializePublicStatusDescription, +} from "@/lib/public-status/config"; +import { publishCurrentPublicStatusConfigProjection } from "@/lib/public-status/config-publisher"; +import { PUBLIC_STATUS_INTERVAL_SET } from "@/lib/public-status/constants"; +import { schedulePublicStatusRebuild } from "@/lib/public-status/rebuild-hints"; +import { UpdateSystemSettingsSchema } from "@/lib/validation/schemas"; +import { + findAllProviderGroups, + findProviderGroupById, + updateProviderGroup, +} from "@/repository/provider-groups"; +import { updateSystemSettings } from "@/repository/system-config"; +import type { ActionResult } from "./types"; + +export interface SavePublicStatusSettingsInput { + publicStatusWindowHours: number; + publicStatusAggregationIntervalMinutes: number; + groups: Array<{ + groupName: string; + displayName?: string; + publicGroupSlug?: string; + explanatoryCopy?: string | null; + sortOrder?: number; + publicModelKeys: string[]; + }>; +} + +function normalizeEnabledGroups( + groups: SavePublicStatusSettingsInput["groups"] +): EnabledPublicStatusGroup[] { + return collectEnabledPublicStatusGroups( + groups.map((group) => ({ + groupName: group.groupName, + note: null, + publicStatus: { + displayName: group.displayName, + publicGroupSlug: group.publicGroupSlug, + explanatoryCopy: group.explanatoryCopy, + sortOrder: group.sortOrder, + publicModelKeys: group.publicModelKeys, + }, + })) + ); +} + +export async function savePublicStatusSettings(input: SavePublicStatusSettingsInput): Promise< + ActionResult<{ + updatedGroupCount: number; + configVersion: string; + publicStatusProjectionWarningCode: string | null; + }> +> { + try { + const t = await getTranslations("settings"); + const tError = await getTranslations("errors"); + const session = await getSession(); + if (!session || session.user.role !== "admin") { + return { ok: false, error: tError("UNAUTHORIZED") }; + } + if (!PUBLIC_STATUS_INTERVAL_SET.has(input.publicStatusAggregationIntervalMinutes)) { + return { + ok: false, + error: t("statusPage.form.aggregationIntervalMinutesInvalid"), + }; + } + + const validatedSettings = UpdateSystemSettingsSchema.parse({ + publicStatusWindowHours: input.publicStatusWindowHours, + publicStatusAggregationIntervalMinutes: input.publicStatusAggregationIntervalMinutes, + }); + const normalizedEnabledGroups = normalizeEnabledGroups(input.groups); + + const allGroups = await findAllProviderGroups(); + const enabledByName = new Map( + normalizedEnabledGroups.map((group) => [group.groupName, group] as const) + ); + const groupUpdates: Array<{ + id: number; + publicStatus: ReturnType["publicStatus"]; + }> = []; + + for (const group of allGroups) { + const existing = parsePublicStatusDescription(group.description); + const configured = enabledByName.get(group.name); + const nextPublicStatus = configured + ? { + displayName: configured.displayName, + publicGroupSlug: configured.publicGroupSlug, + explanatoryCopy: configured.explanatoryCopy, + sortOrder: configured.sortOrder, + publicModelKeys: configured.publicModelKeys, + } + : null; + const nextDescription = serializePublicStatusDescription({ + note: existing.note, + publicStatus: nextPublicStatus, + }); + + if (nextDescription && nextDescription.length > 500) { + return { + ok: false, + error: t("statusPage.form.descriptionTooLong"), + }; + } + + if ((group.description ?? null) !== nextDescription) { + groupUpdates.push({ + id: group.id, + publicStatus: nextPublicStatus, + }); + } + } + + const settings = await db.transaction(async (tx) => { + const updatedSettings = await updateSystemSettings( + { + publicStatusWindowHours: validatedSettings.publicStatusWindowHours, + publicStatusAggregationIntervalMinutes: + validatedSettings.publicStatusAggregationIntervalMinutes, + }, + tx + ); + + for (const groupUpdate of groupUpdates) { + const currentGroup = await findProviderGroupById(groupUpdate.id, tx); + const currentNote = parsePublicStatusDescription(currentGroup?.description).note; + await updateProviderGroup( + groupUpdate.id, + { + description: serializePublicStatusDescription({ + note: currentNote, + publicStatus: groupUpdate.publicStatus, + }), + }, + tx + ); + } + + return updatedSettings; + }); + + const configVersion = `cfg-${Date.now()}`; + let publishResult: { + configVersion: string; + key: string; + written: boolean; + groupCount: number; + } = { + configVersion, + key: "", + written: false, + groupCount: normalizedEnabledGroups.length, + }; + let publicStatusProjectionWarningCode: string | null = null; + try { + publishResult = await publishCurrentPublicStatusConfigProjection({ + reason: "save-public-status-settings", + configVersion, + }); + } catch (error) { + logger.warn("[PublicStatus] DB truth saved but failed to publish Redis projection", error); + publicStatusProjectionWarningCode = "PUBLIC_STATUS_PROJECTION_PUBLISH_FAILED"; + } + + if (!publishResult.written) { + publicStatusProjectionWarningCode = "PUBLIC_STATUS_PROJECTION_PUBLISH_FAILED"; + } else { + try { + await schedulePublicStatusRebuild({ + intervalMinutes: settings.publicStatusAggregationIntervalMinutes, + rangeHours: settings.publicStatusWindowHours, + reason: "config-updated", + }); + } catch (error) { + logger.warn("[PublicStatus] DB truth saved but failed to schedule rebuild hint", error); + publicStatusProjectionWarningCode = "PUBLIC_STATUS_BACKGROUND_REFRESH_PENDING"; + } + } + + invalidateSystemSettingsCache(); + invalidateConfiguredPublicStatusGroupsCache(); + + for (const locale of locales) { + revalidatePath(`/${locale}/settings/config`); + revalidatePath(`/${locale}/settings/providers`); + revalidatePath(`/${locale}/status`); + } + revalidatePath("/", "layout"); + + return { + ok: true, + data: { + updatedGroupCount: groupUpdates.length, + configVersion: publishResult.configVersion, + publicStatusProjectionWarningCode, + }, + }; + } catch (error) { + logger.error("[PublicStatus] savePublicStatusSettings failed", error); + const t = await getTranslations("settings"); + return { + ok: false, + error: t("statusPage.form.saveFailed"), + }; + } +} diff --git a/src/actions/system-config.ts b/src/actions/system-config.ts index a4117249d..b3bd5ed94 100644 --- a/src/actions/system-config.ts +++ b/src/actions/system-config.ts @@ -6,6 +6,8 @@ import { emitActionAudit } from "@/lib/audit/emit"; import { getSession } from "@/lib/auth"; import { invalidateSystemSettingsCache } from "@/lib/config"; import { logger } from "@/lib/logger"; +import { publishCurrentPublicStatusConfigProjection } from "@/lib/public-status/config-publisher"; +import { schedulePublicStatusRebuild } from "@/lib/public-status/rebuild-hints"; import { resolveSystemTimezone } from "@/lib/utils/timezone"; import { UpdateSystemSettingsSchema } from "@/lib/validation/schemas"; import { getSystemSettings, updateSystemSettings } from "@/repository/system-config"; @@ -79,10 +81,12 @@ export async function saveSystemSettings(formData: { quotaLeasePercentWeekly?: number; quotaLeasePercentMonthly?: number; quotaLeaseCapUsd?: number | null; + publicStatusWindowHours?: number; + publicStatusAggregationIntervalMinutes?: number; // IP 提取 / 归属地查询 ipExtractionConfig?: IpExtractionConfig | null; ipGeoLookupEnabled?: boolean; -}): Promise> { +}): Promise> { let before: SystemSettings | null = null; try { const session = await getSession(); @@ -122,6 +126,8 @@ export async function saveSystemSettings(formData: { quotaLeasePercentWeekly: validated.quotaLeasePercentWeekly, quotaLeasePercentMonthly: validated.quotaLeasePercentMonthly, quotaLeaseCapUsd: validated.quotaLeaseCapUsd, + publicStatusWindowHours: validated.publicStatusWindowHours, + publicStatusAggregationIntervalMinutes: validated.publicStatusAggregationIntervalMinutes, ipExtractionConfig: validated.ipExtractionConfig, ipGeoLookupEnabled: validated.ipGeoLookupEnabled, }); @@ -129,6 +135,50 @@ export async function saveSystemSettings(formData: { // Invalidate the system settings cache so proxy requests get fresh settings invalidateSystemSettingsCache(); + const shouldRepublishPublicStatusProjection = + validated.siteTitle !== undefined || + validated.timezone !== undefined || + validated.publicStatusWindowHours !== undefined || + validated.publicStatusAggregationIntervalMinutes !== undefined; + + let publicStatusProjectionWarningCode: string | null = null; + if (shouldRepublishPublicStatusProjection) { + try { + const publishResult = await publishCurrentPublicStatusConfigProjection({ + reason: "save-system-settings", + }); + + if (!publishResult.written) { + logger.warn( + "[SystemSettings] Saved DB truth but failed to publish public-status Redis projection" + ); + publicStatusProjectionWarningCode = "PUBLIC_STATUS_PROJECTION_PUBLISH_FAILED"; + } else { + try { + await schedulePublicStatusRebuild({ + intervalMinutes: + validated.publicStatusAggregationIntervalMinutes ?? + updated.publicStatusAggregationIntervalMinutes, + rangeHours: validated.publicStatusWindowHours ?? updated.publicStatusWindowHours, + reason: "system-settings-updated", + }); + } catch (error) { + logger.warn( + "[SystemSettings] Saved DB truth but failed to schedule public-status rebuild", + error + ); + publicStatusProjectionWarningCode = "PUBLIC_STATUS_BACKGROUND_REFRESH_PENDING"; + } + } + } catch (error) { + logger.warn( + "[SystemSettings] Saved DB truth but failed to publish public-status Redis projection", + error + ); + publicStatusProjectionWarningCode = "PUBLIC_STATUS_PROJECTION_PUBLISH_FAILED"; + } + } + // Revalidate paths for all locales to ensure cache invalidation across i18n routes for (const locale of locales) { revalidatePath(`/${locale}/settings/config`); @@ -147,7 +197,7 @@ export async function saveSystemSettings(formData: { success: true, }); - return { ok: true, data: updated }; + return { ok: true, data: { ...updated, publicStatusProjectionWarningCode } }; } catch (error) { logger.error("更新系统设置失败:", error); const message = error instanceof Error ? error.message : "更新系统设置失败"; diff --git a/src/actions/users.ts b/src/actions/users.ts index 0c9ba6d89..0697795b3 100644 --- a/src/actions/users.ts +++ b/src/actions/users.ts @@ -1116,9 +1116,8 @@ async function safeFindUser(userId: number): Promise { - if (!active || !payload || !payload.length) return
; + if (!active || !payload?.length) return
; const data = payload[0].payload as EventTimeline; diff --git a/src/app/[locale]/dashboard/_components/rate-limit-type-breakdown.tsx b/src/app/[locale]/dashboard/_components/rate-limit-type-breakdown.tsx index 4888cdfaa..4482688d4 100644 --- a/src/app/[locale]/dashboard/_components/rate-limit-type-breakdown.tsx +++ b/src/app/[locale]/dashboard/_components/rate-limit-type-breakdown.tsx @@ -74,7 +74,7 @@ export function RateLimitTypeBreakdown({ data }: RateLimitTypeBreakdownProps) { cursor={false} wrapperStyle={{ zIndex: 1000 }} content={({ active, payload }) => { - if (!active || !payload || !payload.length) return
; + if (!active || !payload?.length) return
; const data = payload[0].payload; const percentage = diff --git a/src/app/[locale]/dashboard/_components/statistics/chart.tsx b/src/app/[locale]/dashboard/_components/statistics/chart.tsx index 491fd0634..3b23a8ea7 100644 --- a/src/app/[locale]/dashboard/_components/statistics/chart.tsx +++ b/src/app/[locale]/dashboard/_components/statistics/chart.tsx @@ -424,7 +424,7 @@ export function UserStatisticsChart({ cursor={false} wrapperStyle={{ transform: "translateY(-100%)", marginTop: "-20px", zIndex: 1000 }} content={({ active, payload, label }) => { - if (!active || !payload || !payload.length) return
; + if (!active || !payload?.length) return
; const filteredPayload = payload.filter((entry) => { const value = diff --git a/src/app/[locale]/dashboard/_components/user/forms/limit-rule-picker.tsx b/src/app/[locale]/dashboard/_components/user/forms/limit-rule-picker.tsx index 3b40c7eee..497f27e6c 100644 --- a/src/app/[locale]/dashboard/_components/user/forms/limit-rule-picker.tsx +++ b/src/app/[locale]/dashboard/_components/user/forms/limit-rule-picker.tsx @@ -112,7 +112,7 @@ export function LimitRulePicker({ setDailyMode("fixed"); setDailyTime("00:00"); setError(null); - }, [open, availableTypes]); + }, [open]); const numericValue = useMemo(() => { const trimmed = rawValue.trim(); diff --git a/src/app/[locale]/dashboard/logs/_hooks/use-lazy-filter-options.ts b/src/app/[locale]/dashboard/logs/_hooks/use-lazy-filter-options.ts index c78e125cc..64dfbc811 100644 --- a/src/app/[locale]/dashboard/logs/_hooks/use-lazy-filter-options.ts +++ b/src/app/[locale]/dashboard/logs/_hooks/use-lazy-filter-options.ts @@ -77,7 +77,7 @@ function createLazyFilterHook( inFlightRef.current = promise; return promise; - }, [fetcher, isLoaded]); + }, [isLoaded]); const onOpenChange = useCallback( (open: boolean) => { diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index a7c6c18d5..008e6ef8a 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import "../globals.css"; +import { headers } from "next/headers"; import { notFound } from "next/navigation"; import { NextIntlClientProvider } from "next-intl"; import { getMessages } from "next-intl/server"; @@ -7,8 +8,10 @@ import { Footer } from "@/components/customs/footer"; import { Toaster } from "@/components/ui/sonner"; import { type Locale, locales } from "@/i18n/config"; import { logger } from "@/lib/logger"; -import { resolveSystemTimezone } from "@/lib/utils/timezone"; -import { getSystemSettings } from "@/repository/system-config"; +import { + resolveLayoutTimeZone, + resolveSiteMetadataSource, +} from "@/lib/public-status/layout-metadata"; import { AppProviders } from "../providers"; const FALLBACK_TITLE = "Claude Code Hub"; @@ -19,10 +22,13 @@ export async function generateMetadata({ params: Promise<{ locale: string }>; }): Promise { const { locale } = await params; + const headersStore = await headers(); + const isPublicStatusRequest = headersStore.get("x-cch-public-status") === "1"; try { - const settings = await getSystemSettings(); - const title = settings.siteTitle?.trim() || FALLBACK_TITLE; + const metadata = await resolveSiteMetadataSource({ isPublicStatusRequest }); + const title = metadata?.siteTitle?.trim() || FALLBACK_TITLE; + const description = metadata?.siteDescription?.trim() || FALLBACK_TITLE; // Generate alternates for all locales const alternates: Record = {}; @@ -34,20 +40,20 @@ export async function generateMetadata({ return { title, - description: title, + description, alternates: { canonical: `${baseUrl}/${locale}`, languages: alternates, }, openGraph: { title, - description: title, + description, locale, alternateLocale: locales.filter((l) => l !== locale), }, }; } catch (error) { - logger.error("Failed to load system settings for metadata", { error }); + logger.error("Failed to load metadata", { error }); return { title: FALLBACK_TITLE, description: FALLBACK_TITLE, @@ -63,6 +69,8 @@ export default async function RootLayout({ params: Promise<{ locale: string }>; }>) { const { locale } = await params; + const headersStore = await headers(); + const isPublicStatusRequest = headersStore.get("x-cch-public-status") === "1"; // Validate locale if (!locales.includes(locale as Locale)) { @@ -71,7 +79,7 @@ export default async function RootLayout({ // Load translation messages const messages = await getMessages(); - const timeZone = await resolveSystemTimezone(); + const timeZone = await resolveLayoutTimeZone({ isPublicStatusRequest }); // Create a stable `now` timestamp to avoid SSR/CSR hydration mismatch for relative time const now = new Date(); diff --git a/src/app/[locale]/settings/_components/settings-nav.tsx b/src/app/[locale]/settings/_components/settings-nav.tsx index 98c38c4c8..6e21ece2a 100644 --- a/src/app/[locale]/settings/_components/settings-nav.tsx +++ b/src/app/[locale]/settings/_components/settings-nav.tsx @@ -2,6 +2,7 @@ import { motion } from "framer-motion"; import { + Activity, AlertTriangle, Bell, BookOpen, @@ -27,6 +28,7 @@ import type { SettingsNavIconName, SettingsNavItem } from "../_lib/nav-items"; // Map icon names to actual icon components (client-side only) const ICON_MAP: Record = { settings: Settings, + activity: Activity, "dollar-sign": DollarSign, server: Server, "shield-alert": ShieldAlert, diff --git a/src/app/[locale]/settings/_components/settings-page-header.tsx b/src/app/[locale]/settings/_components/settings-page-header.tsx index 03fb1e5bd..24e1d86d4 100644 --- a/src/app/[locale]/settings/_components/settings-page-header.tsx +++ b/src/app/[locale]/settings/_components/settings-page-header.tsx @@ -1,4 +1,5 @@ import { + Activity, AlertTriangle, Bell, Database, @@ -15,6 +16,7 @@ import { cn } from "@/lib/utils"; // Icon name type for serialization across server/client boundary export type PageHeaderIconName = | "settings" + | "activity" | "database" | "file-text" | "bell" @@ -27,6 +29,7 @@ export type PageHeaderIconName = // Map icon names to components const HEADER_ICON_MAP: Record = { settings: Settings, + activity: Activity, database: Database, "file-text": FileText, bell: Bell, diff --git a/src/app/[locale]/settings/_lib/nav-items.ts b/src/app/[locale]/settings/_lib/nav-items.ts index 8972ebd31..974bb2b39 100644 --- a/src/app/[locale]/settings/_lib/nav-items.ts +++ b/src/app/[locale]/settings/_lib/nav-items.ts @@ -2,6 +2,7 @@ import { getTranslations } from "next-intl/server"; export type SettingsNavIconName = | "settings" + | "activity" | "dollar-sign" | "server" | "shield-alert" @@ -32,6 +33,12 @@ export const SETTINGS_NAV_ITEMS: SettingsNavItem[] = [ label: "Configuration", iconName: "settings", }, + { + href: "/settings/status-page", + labelKey: "nav.statusPage", + label: "Status Page", + iconName: "activity", + }, { href: "/settings/prices", labelKey: "nav.prices", label: "Prices", iconName: "dollar-sign" }, { href: "/settings/providers", diff --git a/src/app/[locale]/settings/config/_components/system-settings-form.tsx b/src/app/[locale]/settings/config/_components/system-settings-form.tsx index 8b4733489..0c0d459d3 100644 --- a/src/app/[locale]/settings/config/_components/system-settings-form.tsx +++ b/src/app/[locale]/settings/config/_components/system-settings-form.tsx @@ -100,6 +100,7 @@ const DEFAULT_IP_EXTRACTION_CONFIG_TEXT = formatIpExtractionConfig(DEFAULT_IP_EX export function SystemSettingsForm({ initialSettings }: SystemSettingsFormProps) { const router = useRouter(); const t = useTranslations("settings.config.form"); + const tSettings = useTranslations("settings"); const tCommon = useTranslations("settings.common"); const tIpLogging = useTranslations("settings.config.ipLogging"); const [siteTitle, setSiteTitle] = useState(initialSettings.siteTitle); @@ -294,6 +295,16 @@ export function SystemSettingsForm({ initialSettings }: SystemSettingsFormProps) } toast.success(t("configUpdated")); + if ( + result.data?.publicStatusProjectionWarningCode === "PUBLIC_STATUS_PROJECTION_PUBLISH_FAILED" + ) { + toast.warning(tSettings("config.form.publicStatusProjectionWarning")); + } else if ( + result.data?.publicStatusProjectionWarningCode === + "PUBLIC_STATUS_BACKGROUND_REFRESH_PENDING" + ) { + toast.warning(tSettings("config.form.publicStatusBackgroundRefreshPending")); + } router.refresh(); }); }; diff --git a/src/app/[locale]/settings/providers/_components/provider-group-tab.tsx b/src/app/[locale]/settings/providers/_components/provider-group-tab.tsx index 4d52498a6..a9216f9ba 100644 --- a/src/app/[locale]/settings/providers/_components/provider-group-tab.tsx +++ b/src/app/[locale]/settings/providers/_components/provider-group-tab.tsx @@ -49,6 +49,7 @@ import { import { PROVIDER_GROUP } from "@/lib/constants/provider.constants"; import { useMediaQuery } from "@/lib/hooks/use-media-query"; import { getProviderTypeConfig, getProviderTypeTranslationKey } from "@/lib/provider-type-utils"; +import { parsePublicStatusDescription } from "@/lib/public-status/config"; import { cn } from "@/lib/utils"; import { parseProviderGroups } from "@/lib/utils/provider-group"; import type { ProviderDisplay } from "@/types/provider"; @@ -74,6 +75,10 @@ const INITIAL_FORM: GroupFormState = { description: "", }; +function getProviderGroupDescriptionNote(description: string | null | undefined): string { + return parsePublicStatusDescription(description).note ?? ""; +} + export function ProviderGroupTab({ providers, isAdmin, @@ -128,7 +133,7 @@ export function ProviderGroupTab({ setForm({ name: group.name, costMultiplier: String(group.costMultiplier), - description: group.description ?? "", + description: getProviderGroupDescriptionNote(group.description), }); setDialogOpen(true); }, []); @@ -148,6 +153,8 @@ export function ProviderGroupTab({ return t("duplicateName"); case "INVALID_MULTIPLIER": return t("invalidMultiplier"); + case "DESCRIPTION_TOO_LONG": + return t("descriptionTooLong"); default: return fallback; } @@ -161,6 +168,7 @@ export function ProviderGroupTab({ patch: { costMultiplier?: number; description?: string | null; + descriptionNote?: string | null; } ): Promise => { const result = await updateProviderGroup(groupId, patch); @@ -197,7 +205,7 @@ export function ProviderGroupTab({ if (editingGroup) { const ok = await saveGroupPatch(editingGroup.id, { costMultiplier, - description: trimmedDescription || null, + descriptionNote: trimmedDescription || null, }); if (ok) { closeDialog(); @@ -357,17 +365,21 @@ export function ProviderGroupTab({ {isAdmin ? ( - saveGroupPatch(group.id, { description: value || null }) + saveGroupPatch(group.id, { + descriptionNote: value || null, + }) } /> - ) : group.description ? ( - {group.description} + ) : getProviderGroupDescriptionNote(group.description) ? ( + + {getProviderGroupDescriptionNote(group.description)} + ) : ( {t("noDescription")} )} diff --git a/src/app/[locale]/settings/status-page/_components/public-status-settings-form.tsx b/src/app/[locale]/settings/status-page/_components/public-status-settings-form.tsx new file mode 100644 index 000000000..e3da994bb --- /dev/null +++ b/src/app/[locale]/settings/status-page/_components/public-status-settings-form.tsx @@ -0,0 +1,272 @@ +"use client"; + +import { Activity, Save } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { useTranslations } from "next-intl"; +import { useMemo, useState, useTransition } from "react"; +import { toast } from "sonner"; +import { + type SavePublicStatusSettingsInput, + savePublicStatusSettings, +} from "@/actions/public-status"; +import { Section } from "@/components/section"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Textarea } from "@/components/ui/textarea"; +import { PUBLIC_STATUS_INTERVAL_OPTIONS } from "@/lib/public-status/constants"; + +export interface PublicStatusSettingsFormGroup { + groupName: string; + enabled: boolean; + displayName: string; + publicGroupSlug: string; + explanatoryCopy: string; + sortOrder: number; + modelIdsText: string; +} + +interface PublicStatusSettingsFormProps { + initialWindowHours: number; + initialAggregationIntervalMinutes: number; + initialGroups: PublicStatusSettingsFormGroup[]; +} + +function normalizeModelKeys(value: string): string[] { + return Array.from( + new Set( + value + .split(/\r?\n/) + .map((item) => item.trim()) + .filter(Boolean) + ) + ); +} + +export function PublicStatusSettingsForm({ + initialWindowHours, + initialAggregationIntervalMinutes, + initialGroups, +}: PublicStatusSettingsFormProps) { + const router = useRouter(); + const t = useTranslations("settings"); + const [windowHours, setWindowHours] = useState(String(initialWindowHours)); + const [aggregationIntervalMinutes, setAggregationIntervalMinutes] = useState( + String(initialAggregationIntervalMinutes) + ); + const [groups, setGroups] = useState(initialGroups); + const [isPending, startTransition] = useTransition(); + + const enabledGroupCount = useMemo( + () => + groups.filter((group) => group.enabled && normalizeModelKeys(group.modelIdsText).length > 0) + .length, + [groups] + ); + + const updateGroup = (index: number, patch: Partial) => { + setGroups((current) => + current.map((group, groupIndex) => (groupIndex === index ? { ...group, ...patch } : group)) + ); + }; + + const handleSave = () => { + const payload: SavePublicStatusSettingsInput = { + publicStatusWindowHours: Number(windowHours), + publicStatusAggregationIntervalMinutes: Number(aggregationIntervalMinutes), + groups: groups + .filter((group) => group.enabled) + .map((group) => ({ + groupName: group.groupName, + displayName: group.displayName.trim() || undefined, + publicGroupSlug: group.publicGroupSlug.trim() || undefined, + explanatoryCopy: group.explanatoryCopy.trim() || null, + sortOrder: group.sortOrder, + publicModelKeys: normalizeModelKeys(group.modelIdsText), + })), + }; + + startTransition(async () => { + const result = await savePublicStatusSettings(payload); + if (!result.ok) { + toast.error(result.error || t("statusPage.form.saveFailed")); + return; + } + + toast.success(t("statusPage.form.saveSuccess")); + if ( + result.data?.publicStatusProjectionWarningCode === "PUBLIC_STATUS_PROJECTION_PUBLISH_FAILED" + ) { + toast.warning(t("statusPage.form.projectionPublishFailed")); + } else if (result.data?.publicStatusProjectionWarningCode) { + toast.warning(t("statusPage.form.backgroundRefreshPending")); + } + router.refresh(); + }); + }; + + return ( +
+
+
+
+ + setWindowHours(event.target.value)} + disabled={isPending} + /> +

{t("statusPage.form.windowHoursDesc")}

+
+ +
+ + +

+ {t("statusPage.form.aggregationIntervalMinutesDesc")} +

+
+
+ +
+ {enabledGroupCount} + {t("statusPage.form.helper")} +
+
+ +
+
+ {groups.map((group, index) => ( + + +
+ + + {group.groupName} + +
+
+ updateGroup(index, { enabled: checked === true })} + disabled={isPending} + /> + + {t("statusPage.form.enabled")} + +
+
+ +
+ + updateGroup(index, { displayName: event.target.value })} + placeholder={t("statusPage.form.displayNamePlaceholder")} + disabled={isPending} + /> +
+ +
+ + + updateGroup(index, { publicGroupSlug: event.target.value }) + } + placeholder={group.groupName.toLowerCase()} + disabled={isPending} + /> +
+ +
+ +