diff --git a/backend/app/api/routes/execution.py b/backend/app/api/routes/execution.py index ef3e9a4..6efc1f8 100644 --- a/backend/app/api/routes/execution.py +++ b/backend/app/api/routes/execution.py @@ -1,3 +1,4 @@ +from dataclasses import asdict from datetime import datetime, timezone from typing import Annotated from uuid import uuid4 @@ -336,7 +337,7 @@ async def get_k8s_resource_limits( ) -> ResourceLimits: try: limits = await execution_service.get_k8s_resource_limits() - return ResourceLimits(**vars(limits)) + return ResourceLimits(**asdict(limits)) except Exception as e: raise HTTPException(status_code=500, detail="Failed to retrieve resource limits") from e diff --git a/backend/app/domain/execution/__init__.py b/backend/app/domain/execution/__init__.py index 4b66b31..fb72541 100644 --- a/backend/app/domain/execution/__init__.py +++ b/backend/app/domain/execution/__init__.py @@ -7,6 +7,7 @@ from .models import ( DomainExecution, ExecutionResultDomain, + LanguageInfoDomain, ResourceLimitsDomain, ResourceUsageDomain, ) @@ -14,6 +15,7 @@ __all__ = [ "DomainExecution", "ExecutionResultDomain", + "LanguageInfoDomain", "ResourceLimitsDomain", "ResourceUsageDomain", "ExecutionServiceError", diff --git a/backend/app/domain/execution/models.py b/backend/app/domain/execution/models.py index 482e9f3..ab49ff6 100644 --- a/backend/app/domain/execution/models.py +++ b/backend/app/domain/execution/models.py @@ -64,6 +64,14 @@ def from_dict(data: dict[str, Any]) -> "ResourceUsageDomain": ) +@dataclass +class LanguageInfoDomain: + """Language runtime information.""" + + versions: list[str] + file_ext: str + + @dataclass class ResourceLimitsDomain: """K8s resource limits configuration.""" @@ -73,4 +81,4 @@ class ResourceLimitsDomain: cpu_request: str memory_request: str execution_timeout: int - supported_runtimes: dict[str, list[str]] + supported_runtimes: dict[str, LanguageInfoDomain] diff --git a/backend/app/domain/user/settings_models.py b/backend/app/domain/user/settings_models.py index 66f2f71..382551d 100644 --- a/backend/app/domain/user/settings_models.py +++ b/backend/app/domain/user/settings_models.py @@ -20,7 +20,7 @@ class DomainNotificationSettings: @dataclass class DomainEditorSettings: - theme: str = "one-dark" + theme: str = "auto" font_size: int = 14 tab_size: int = 4 use_tabs: bool = False diff --git a/backend/app/runtime_registry.py b/backend/app/runtime_registry.py index cf4dd44..7200d61 100644 --- a/backend/app/runtime_registry.py +++ b/backend/app/runtime_registry.py @@ -1,5 +1,7 @@ from typing import NamedTuple, TypedDict +from app.domain.execution import LanguageInfoDomain + class RuntimeConfig(NamedTuple): image: str # Full Docker image reference @@ -178,4 +180,7 @@ def _make_runtime_configs() -> dict[str, dict[str, RuntimeConfig]]: RUNTIME_REGISTRY: dict[str, dict[str, RuntimeConfig]] = _make_runtime_configs() -SUPPORTED_RUNTIMES: dict[str, list[str]] = {lang: list(versions.keys()) for lang, versions in RUNTIME_REGISTRY.items()} +SUPPORTED_RUNTIMES: dict[str, LanguageInfoDomain] = { + lang: LanguageInfoDomain(versions=spec["versions"], file_ext=spec["file_ext"]) + for lang, spec in LANGUAGE_SPECS.items() +} diff --git a/backend/app/schemas_pydantic/execution.py b/backend/app/schemas_pydantic/execution.py index ad91cf9..c3d45cd 100644 --- a/backend/app/schemas_pydantic/execution.py +++ b/backend/app/schemas_pydantic/execution.py @@ -73,13 +73,12 @@ class ExecutionRequest(BaseModel): @model_validator(mode="after") def validate_runtime_supported(self) -> "ExecutionRequest": # noqa: D401 - settings = get_settings() - runtimes = settings.SUPPORTED_RUNTIMES or {} - if self.lang not in runtimes: + runtimes = get_settings().SUPPORTED_RUNTIMES + if not (lang_info := runtimes.get(self.lang)): raise ValueError(f"Language '{self.lang}' not supported. Supported: {list(runtimes.keys())}") - versions = runtimes.get(self.lang, []) - if self.lang_version not in versions: - raise ValueError(f"Version '{self.lang_version}' not supported for {self.lang}. Supported: {versions}") + if self.lang_version not in lang_info.versions: + raise ValueError(f"Version '{self.lang_version}' not supported for {self.lang}. " + f"Supported: {lang_info.versions}") return self @@ -108,6 +107,13 @@ class ExecutionResult(BaseModel): model_config = ConfigDict(from_attributes=True) +class LanguageInfo(BaseModel): + """Language runtime information.""" + + versions: list[str] + file_ext: str + + class ResourceLimits(BaseModel): """Model for resource limits configuration.""" @@ -116,7 +122,7 @@ class ResourceLimits(BaseModel): cpu_request: str memory_request: str execution_timeout: int - supported_runtimes: dict[str, list[str]] + supported_runtimes: dict[str, LanguageInfo] class ExampleScripts(BaseModel): diff --git a/backend/app/schemas_pydantic/user_settings.py b/backend/app/schemas_pydantic/user_settings.py index 2066ca4..258de96 100644 --- a/backend/app/schemas_pydantic/user_settings.py +++ b/backend/app/schemas_pydantic/user_settings.py @@ -21,18 +21,12 @@ class NotificationSettings(BaseModel): class EditorSettings(BaseModel): """Code editor preferences""" - theme: str = "one-dark" + theme: str = "auto" font_size: int = 14 tab_size: int = 4 use_tabs: bool = False word_wrap: bool = True show_line_numbers: bool = True - # These are always on in the editor, not user-configurable - font_family: str = "Monaco, Consolas, 'Courier New', monospace" - auto_complete: bool = True - bracket_matching: bool = True - highlight_active_line: bool = True - default_language: str = "python" @field_validator("font_size") @classmethod diff --git a/backend/app/services/execution_service.py b/backend/app/services/execution_service.py index cd0204d..9b2cebf 100644 --- a/backend/app/services/execution_service.py +++ b/backend/app/services/execution_service.py @@ -10,7 +10,12 @@ from app.db.repositories.execution_repository import ExecutionRepository from app.domain.enums.events import EventType from app.domain.enums.execution import ExecutionStatus -from app.domain.execution import DomainExecution, ExecutionResultDomain, ResourceLimitsDomain, ResourceUsageDomain +from app.domain.execution import ( + DomainExecution, + ExecutionResultDomain, + ResourceLimitsDomain, + ResourceUsageDomain, +) from app.events.core import UnifiedProducer from app.events.event_store import EventStore from app.infrastructure.kafka.events.base import BaseEvent diff --git a/backend/app/settings.py b/backend/app/settings.py index 34b0383..6e80b55 100644 --- a/backend/app/settings.py +++ b/backend/app/settings.py @@ -3,6 +3,7 @@ from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict +from app.domain.execution import LanguageInfoDomain from app.runtime_registry import EXAMPLE_SCRIPTS as EXEC_EXAMPLE_SCRIPTS from app.runtime_registry import SUPPORTED_RUNTIMES as RUNTIME_MATRIX @@ -37,7 +38,7 @@ class Settings(BaseSettings): K8S_POD_EXECUTION_TIMEOUT: int = 300 # in seconds K8S_POD_PRIORITY_CLASS_NAME: str | None = None - SUPPORTED_RUNTIMES: dict[str, list[str]] = Field(default_factory=lambda: RUNTIME_MATRIX) + SUPPORTED_RUNTIMES: dict[str, LanguageInfoDomain] = Field(default_factory=lambda: RUNTIME_MATRIX) EXAMPLE_SCRIPTS: dict[str, str] = Field(default_factory=lambda: EXEC_EXAMPLE_SCRIPTS) diff --git a/backend/tests/integration/test_user_settings_routes.py b/backend/tests/integration/test_user_settings_routes.py index 20b89dc..c637835 100644 --- a/backend/tests/integration/test_user_settings_routes.py +++ b/backend/tests/integration/test_user_settings_routes.py @@ -129,7 +129,7 @@ async def test_get_user_settings(self, client: AsyncClient, test_user: Dict[str, assert settings.editor is not None assert isinstance(settings.editor.font_size, int) assert 8 <= settings.editor.font_size <= 32 - assert settings.editor.theme in ["one-dark", "monokai", "github", "dracula", "solarized", "vs", "vscode"] + assert settings.editor.theme in ["auto", "one-dark", "monokai", "github", "dracula", "solarized", "vs", "vscode"] assert isinstance(settings.editor.tab_size, int) assert settings.editor.tab_size in [2, 4, 8] assert isinstance(settings.editor.word_wrap, bool) diff --git a/docs/reference/openapi.json b/docs/reference/openapi.json index 2e24358..703a776 100644 --- a/docs/reference/openapi.json +++ b/docs/reference/openapi.json @@ -1160,8 +1160,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "title": "Response Liveness Api V1 Health Live Get" + "$ref": "#/components/schemas/LivenessResponse" } } } @@ -1183,8 +1182,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "title": "Response Readiness Api V1 Health Ready Get" + "$ref": "#/components/schemas/ReadinessResponse" } } } @@ -2359,171 +2357,6 @@ } } }, - "/api/v1/admin/events/{event_id}": { - "get": { - "tags": [ - "admin-events" - ], - "summary": "Get Event Detail", - "operationId": "get_event_detail_api_v1_admin_events__event_id__get", - "parameters": [ - { - "name": "event_id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "title": "Event Id" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EventDetailResponse" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - }, - "delete": { - "tags": [ - "admin-events" - ], - "summary": "Delete Event", - "operationId": "delete_event_api_v1_admin_events__event_id__delete", - "parameters": [ - { - "name": "event_id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "title": "Event Id" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EventDeleteResponse" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/api/v1/admin/events/replay": { - "post": { - "tags": [ - "admin-events" - ], - "summary": "Replay Events", - "operationId": "replay_events_api_v1_admin_events_replay_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EventReplayRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EventReplayResponse" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/api/v1/admin/events/replay/{session_id}/status": { - "get": { - "tags": [ - "admin-events" - ], - "summary": "Get Replay Status", - "operationId": "get_replay_status_api_v1_admin_events_replay__session_id__status_get", - "parameters": [ - { - "name": "session_id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "title": "Session Id" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EventReplayStatusResponse" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, "/api/v1/admin/events/export/csv": { "get": { "tags": [ @@ -2782,7 +2615,172 @@ "description": "Successful Response", "content": { "application/json": { - "schema": {} + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/admin/events/{event_id}": { + "get": { + "tags": [ + "admin-events" + ], + "summary": "Get Event Detail", + "operationId": "get_event_detail_api_v1_admin_events__event_id__get", + "parameters": [ + { + "name": "event_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Event Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EventDetailResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "admin-events" + ], + "summary": "Delete Event", + "operationId": "delete_event_api_v1_admin_events__event_id__delete", + "parameters": [ + { + "name": "event_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Event Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EventDeleteResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/admin/events/replay": { + "post": { + "tags": [ + "admin-events" + ], + "summary": "Replay Events", + "operationId": "replay_events_api_v1_admin_events_replay_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EventReplayRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EventReplayResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/admin/events/replay/{session_id}/status": { + "get": { + "tags": [ + "admin-events" + ], + "summary": "Get Replay Status", + "operationId": "get_replay_status_api_v1_admin_events_replay__session_id__status_get", + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Session Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EventReplayStatusResponse" + } } } }, @@ -3141,8 +3139,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "title": "Response Delete User Api V1 Admin Users User Id Delete" + "$ref": "#/components/schemas/DeleteUserResponse" } } } @@ -3281,8 +3278,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "title": "Response Get User Rate Limits Api V1 Admin Users User Id Rate Limits Get" + "$ref": "#/components/schemas/UserRateLimitsResponse" } } } @@ -3333,8 +3329,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "title": "Response Update User Rate Limits Api V1 Admin Users User Id Rate Limits Put" + "$ref": "#/components/schemas/RateLimitUpdateResponse" } } } @@ -4037,7 +4032,7 @@ "sagas" ], "summary": "Get Saga Status", - "description": "Get saga status by ID.\n\nArgs:\n saga_id: The saga identifier\n request: FastAPI request object\n saga_service: Saga service from DI\n auth_service: Auth service from DI\n \nReturns:\n Saga status response\n \nRaises:\n HTTPException: 404 if saga not found, 403 if access denied", + "description": "Get saga status by ID.\n\nArgs:\n saga_id: The saga identifier\n request: FastAPI request object\n saga_service: Saga service from DI\n auth_service: Auth service from DI\n\nReturns:\n Saga status response\n\nRaises:\n HTTPException: 404 if saga not found, 403 if access denied", "operationId": "get_saga_status_api_v1_sagas__saga_id__get", "parameters": [ { @@ -4080,7 +4075,7 @@ "sagas" ], "summary": "Get Execution Sagas", - "description": "Get all sagas for an execution.\n\nArgs:\n execution_id: The execution identifier\n request: FastAPI request object\n saga_service: Saga service from DI\n auth_service: Auth service from DI\n state: Optional state filter\n \nReturns:\n List of sagas for the execution\n \nRaises:\n HTTPException: 403 if access denied", + "description": "Get all sagas for an execution.\n\nArgs:\n execution_id: The execution identifier\n request: FastAPI request object\n saga_service: Saga service from DI\n auth_service: Auth service from DI\n state: Optional state filter\n\nReturns:\n List of sagas for the execution\n\nRaises:\n HTTPException: 403 if access denied", "operationId": "get_execution_sagas_api_v1_sagas_execution__execution_id__get", "parameters": [ { @@ -4141,7 +4136,7 @@ "sagas" ], "summary": "List Sagas", - "description": "List sagas accessible by the current user.\n\nArgs:\n request: FastAPI request object\n saga_service: Saga service from DI\n auth_service: Auth service from DI\n state: Optional state filter\n limit: Maximum number of results\n offset: Number of results to skip\n \nReturns:\n Paginated list of sagas", + "description": "List sagas accessible by the current user.\n\nArgs:\n request: FastAPI request object\n saga_service: Saga service from DI\n auth_service: Auth service from DI\n state: Optional state filter\n limit: Maximum number of results\n offset: Number of results to skip\n\nReturns:\n Paginated list of sagas", "operationId": "list_sagas_api_v1_sagas__get", "parameters": [ { @@ -4216,7 +4211,7 @@ "sagas" ], "summary": "Cancel Saga", - "description": "Cancel a running saga.\n\nArgs:\n saga_id: The saga identifier\n request: FastAPI request object\n saga_service: Saga service from DI\n auth_service: Auth service from DI\n \nReturns:\n Cancellation response with success status\n \nRaises:\n HTTPException: 404 if not found, 403 if denied, 400 if invalid state", + "description": "Cancel a running saga.\n\nArgs:\n saga_id: The saga identifier\n request: FastAPI request object\n saga_service: Saga service from DI\n auth_service: Auth service from DI\n\nReturns:\n Cancellation response with success status\n\nRaises:\n HTTPException: 404 if not found, 403 if denied, 400 if invalid state", "operationId": "cancel_saga_api_v1_sagas__saga_id__cancel_post", "parameters": [ { @@ -4955,6 +4950,28 @@ "title": "DeleteResponse", "description": "Model for execution deletion response." }, + "DeleteUserResponse": { + "properties": { + "message": { + "type": "string", + "title": "Message" + }, + "deleted_counts": { + "additionalProperties": { + "type": "integer" + }, + "type": "object", + "title": "Deleted Counts" + } + }, + "type": "object", + "required": [ + "message", + "deleted_counts" + ], + "title": "DeleteUserResponse", + "description": "Response model for user deletion." + }, "DerivedCounts": { "properties": { "succeeded": { @@ -4991,7 +5008,7 @@ "theme": { "type": "string", "title": "Theme", - "default": "one-dark" + "default": "auto" }, "font_size": { "type": "integer", @@ -5017,31 +5034,6 @@ "type": "boolean", "title": "Show Line Numbers", "default": true - }, - "font_family": { - "type": "string", - "title": "Font Family", - "default": "Monaco, Consolas, 'Courier New', monospace" - }, - "auto_complete": { - "type": "boolean", - "title": "Auto Complete", - "default": true - }, - "bracket_matching": { - "type": "boolean", - "title": "Bracket Matching", - "default": true - }, - "highlight_active_line": { - "type": "boolean", - "title": "Highlight Active Line", - "default": true - }, - "default_language": { - "type": "string", - "title": "Default Language", - "default": "python" } }, "type": "object", @@ -6388,6 +6380,55 @@ "type": "object", "title": "HTTPValidationError" }, + "LanguageInfo": { + "properties": { + "versions": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Versions" + }, + "file_ext": { + "type": "string", + "title": "File Ext" + } + }, + "type": "object", + "required": [ + "versions", + "file_ext" + ], + "title": "LanguageInfo", + "description": "Language runtime information." + }, + "LivenessResponse": { + "properties": { + "status": { + "type": "string", + "title": "Status", + "description": "Health status" + }, + "uptime_seconds": { + "type": "integer", + "title": "Uptime Seconds", + "description": "Server uptime in seconds" + }, + "timestamp": { + "type": "string", + "title": "Timestamp", + "description": "ISO timestamp of health check" + } + }, + "type": "object", + "required": [ + "status", + "uptime_seconds", + "timestamp" + ], + "title": "LivenessResponse", + "description": "Response model for liveness probe." + }, "LoginResponse": { "properties": { "message": { @@ -6949,6 +6990,55 @@ ], "title": "RateLimitRule" }, + "RateLimitRuleResponse": { + "properties": { + "endpoint_pattern": { + "type": "string", + "title": "Endpoint Pattern" + }, + "group": { + "type": "string", + "title": "Group" + }, + "requests": { + "type": "integer", + "title": "Requests" + }, + "window_seconds": { + "type": "integer", + "title": "Window Seconds" + }, + "algorithm": { + "type": "string", + "title": "Algorithm" + }, + "burst_multiplier": { + "type": "number", + "title": "Burst Multiplier", + "default": 1.5 + }, + "priority": { + "type": "integer", + "title": "Priority", + "default": 0 + }, + "enabled": { + "type": "boolean", + "title": "Enabled", + "default": true + } + }, + "type": "object", + "required": [ + "endpoint_pattern", + "group", + "requests", + "window_seconds", + "algorithm" + ], + "title": "RateLimitRuleResponse", + "description": "Response model for rate limit rule." + }, "RateLimitSummary": { "properties": { "bypass_rate_limit": { @@ -6988,6 +7078,50 @@ "type": "object", "title": "RateLimitSummary" }, + "RateLimitUpdateResponse": { + "properties": { + "user_id": { + "type": "string", + "title": "User Id" + }, + "updated": { + "type": "boolean", + "title": "Updated" + }, + "config": { + "$ref": "#/components/schemas/UserRateLimitConfigResponse" + } + }, + "type": "object", + "required": [ + "user_id", + "updated", + "config" + ], + "title": "RateLimitUpdateResponse", + "description": "Response model for rate limit update." + }, + "ReadinessResponse": { + "properties": { + "status": { + "type": "string", + "title": "Status", + "description": "Readiness status" + }, + "uptime_seconds": { + "type": "integer", + "title": "Uptime Seconds", + "description": "Server uptime in seconds" + } + }, + "type": "object", + "required": [ + "status", + "uptime_seconds" + ], + "title": "ReadinessResponse", + "description": "Response model for readiness probe." + }, "ReplayAggregateResponse": { "properties": { "dry_run": { @@ -7580,10 +7714,7 @@ }, "supported_runtimes": { "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" + "$ref": "#/components/schemas/LanguageInfo" }, "type": "object", "title": "Supported Runtimes" @@ -7791,8 +7922,7 @@ "description": "Maximum connections allowed per user" }, "shutdown": { - "type": "object", - "title": "Shutdown", + "$ref": "#/components/schemas/ShutdownStatusResponse", "description": "Shutdown status information" }, "timestamp": { @@ -8281,6 +8411,57 @@ "title": "SettingsHistoryResponse", "description": "Response model for settings history" }, + "ShutdownStatusResponse": { + "properties": { + "phase": { + "type": "string", + "title": "Phase", + "description": "Current shutdown phase" + }, + "initiated": { + "type": "boolean", + "title": "Initiated", + "description": "Whether shutdown has been initiated" + }, + "complete": { + "type": "boolean", + "title": "Complete", + "description": "Whether shutdown is complete" + }, + "active_connections": { + "type": "integer", + "title": "Active Connections", + "description": "Number of active connections" + }, + "draining_connections": { + "type": "integer", + "title": "Draining Connections", + "description": "Number of connections being drained" + }, + "duration": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Duration", + "description": "Duration of shutdown in seconds" + } + }, + "type": "object", + "required": [ + "phase", + "initiated", + "complete", + "active_connections", + "draining_connections" + ], + "title": "ShutdownStatusResponse", + "description": "Response model for shutdown status." + }, "SortOrder": { "type": "string", "enum": [ @@ -8601,6 +8782,105 @@ ], "title": "UserRateLimit" }, + "UserRateLimitConfigResponse": { + "properties": { + "user_id": { + "type": "string", + "title": "User Id" + }, + "bypass_rate_limit": { + "type": "boolean", + "title": "Bypass Rate Limit" + }, + "global_multiplier": { + "type": "number", + "title": "Global Multiplier" + }, + "rules": { + "items": { + "$ref": "#/components/schemas/RateLimitRuleResponse" + }, + "type": "array", + "title": "Rules" + }, + "created_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Created At" + }, + "updated_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Updated At" + }, + "notes": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Notes" + } + }, + "type": "object", + "required": [ + "user_id", + "bypass_rate_limit", + "global_multiplier", + "rules" + ], + "title": "UserRateLimitConfigResponse", + "description": "Response model for user rate limit config." + }, + "UserRateLimitsResponse": { + "properties": { + "user_id": { + "type": "string", + "title": "User Id" + }, + "rate_limit_config": { + "anyOf": [ + { + "$ref": "#/components/schemas/UserRateLimitConfigResponse" + }, + { + "type": "null" + } + ] + }, + "current_usage": { + "additionalProperties": { + "type": "object" + }, + "type": "object", + "title": "Current Usage" + } + }, + "type": "object", + "required": [ + "user_id", + "current_usage" + ], + "title": "UserRateLimitsResponse", + "description": "Response model for user rate limits with usage stats." + }, "UserResponse": { "properties": { "username": { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 241d03d..9cadc2d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,8 +11,11 @@ "@babel/runtime": "^7.27.6", "@codemirror/autocomplete": "^6.17.0", "@codemirror/commands": "^6.7.0", + "@codemirror/lang-go": "^6.0.1", + "@codemirror/lang-javascript": "^6.2.4", "@codemirror/lang-python": "^6.1.6", "@codemirror/language": "^6.10.2", + "@codemirror/legacy-modes": "^6.5.2", "@codemirror/state": "^6.4.1", "@codemirror/theme-one-dark": "^6.1.2", "@codemirror/view": "^6.34.1", @@ -46,6 +49,7 @@ "@babel/runtime": "^7.24.7", "@hey-api/openapi-ts": "0.89.2", "@playwright/test": "^1.52.0", + "@rollup/plugin-alias": "^6.0.0", "@rollup/plugin-typescript": "^12.1.2", "@sveltejs/vite-plugin-svelte": "^5.0.3", "@tailwindcss/forms": "^0.5.11", @@ -208,6 +212,32 @@ "@lezer/common": "^1.1.0" } }, + "node_modules/@codemirror/lang-go": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-go/-/lang-go-6.0.1.tgz", + "integrity": "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/go": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz", + "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, "node_modules/@codemirror/lang-python": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.2.1.tgz", @@ -233,6 +263,14 @@ "style-mod": "^4.0.0" } }, + "node_modules/@codemirror/legacy-modes": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.2.tgz", + "integrity": "sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==", + "dependencies": { + "@codemirror/language": "^6.0.0" + } + }, "node_modules/@codemirror/lint": { "version": "6.9.2", "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.2.tgz", @@ -969,6 +1007,16 @@ "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.4.0.tgz", "integrity": "sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==" }, + "node_modules/@lezer/go": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@lezer/go/-/go-1.0.1.tgz", + "integrity": "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, "node_modules/@lezer/highlight": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", @@ -977,6 +1025,16 @@ "@lezer/common": "^1.3.0" } }, + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, "node_modules/@lezer/lr": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.5.tgz", @@ -1046,6 +1104,23 @@ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==" }, + "node_modules/@rollup/plugin-alias": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-6.0.0.tgz", + "integrity": "sha512-tPCzJOtS7uuVZd+xPhoy5W4vThe6KWXNmsFCNktaAh5RTqcLiSfT4huPQIXkgJ6YCOjJHvecOAzQxLFhPxKr+g==", + "dev": true, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "rollup": ">=4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/plugin-commonjs": { "version": "29.0.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 906e875..d7f70d7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,8 +18,11 @@ "@babel/runtime": "^7.27.6", "@codemirror/autocomplete": "^6.17.0", "@codemirror/commands": "^6.7.0", + "@codemirror/lang-go": "^6.0.1", + "@codemirror/lang-javascript": "^6.2.4", "@codemirror/lang-python": "^6.1.6", "@codemirror/language": "^6.10.2", + "@codemirror/legacy-modes": "^6.5.2", "@codemirror/state": "^6.4.1", "@codemirror/theme-one-dark": "^6.1.2", "@codemirror/view": "^6.34.1", @@ -53,6 +56,7 @@ "@babel/runtime": "^7.24.7", "@hey-api/openapi-ts": "0.89.2", "@playwright/test": "^1.52.0", + "@rollup/plugin-alias": "^6.0.0", "@rollup/plugin-typescript": "^12.1.2", "@sveltejs/vite-plugin-svelte": "^5.0.3", "@tailwindcss/forms": "^0.5.11", diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js index 8cf1e14..818837e 100644 --- a/frontend/rollup.config.js +++ b/frontend/rollup.config.js @@ -6,12 +6,26 @@ import postcss from 'rollup-plugin-postcss'; import sveltePreprocess from 'svelte-preprocess'; import replace from '@rollup/plugin-replace'; import typescript from '@rollup/plugin-typescript'; +import alias from '@rollup/plugin-alias'; import dotenv from 'dotenv'; import fs from 'fs'; import https from 'https'; import path from 'path'; import json from '@rollup/plugin-json'; +// Path aliases - must match tsconfig.json paths +const projectRoot = path.resolve('.'); +const aliases = alias({ + entries: [ + { find: '$lib', replacement: path.resolve(projectRoot, 'src/lib') }, + { find: '$components', replacement: path.resolve(projectRoot, 'src/components') }, + { find: '$stores', replacement: path.resolve(projectRoot, 'src/stores') }, + { find: '$routes', replacement: path.resolve(projectRoot, 'src/routes') }, + { find: '$utils', replacement: path.resolve(projectRoot, 'src/utils') }, + { find: '$styles', replacement: path.resolve(projectRoot, 'src/styles') } + ] +}); + dotenv.config(); const production = !process.env.ROLLUP_WATCH; @@ -144,6 +158,8 @@ export default { '@codemirror/language', '@codemirror/autocomplete', '@codemirror/lang-python', + '@codemirror/lang-javascript', + '@codemirror/lang-go', '@codemirror/theme-one-dark', '@uiw/codemirror-theme-github' ] @@ -155,6 +171,7 @@ export default { } }, plugins: [ + aliases, replace({ 'process.env.VITE_BACKEND_URL': JSON.stringify(''), preventAssignment: true @@ -182,7 +199,7 @@ export default { // Prefer ES modules mainFields: ['svelte', 'module', 'browser', 'main'], exportConditions: ['svelte'], - extensions: ['.mjs', '.js', '.json', '.node', '.svelte'] + extensions: ['.mjs', '.js', '.ts', '.json', '.node', '.svelte'] }), commonjs(), !production && { diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 12cfc63..956d912 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -1,29 +1,29 @@