Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
A
ai company
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
ai company
Commits
48965032
Commit
48965032
authored
May 28, 2026
by
Admin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: Add CubeSandbox dashboard and CubeAPI web endpoints
parent
54e5d70a
Changes
12
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
1174 additions
and
8 deletions
+1174
-8
anthropic_adapter.py
backend/agent/agent/anthropic_adapter.py
+2
-0
run_agent.py
backend/agent/run_agent.py
+5
-0
workspace_tools.py
backend/agent/tools/workspace_tools.py
+11
-6
main_router.py
backend/api/main_router.py
+2
-0
agents.py
backend/api/routes/agents.py
+13
-2
cubesandbox.py
backend/api/routes/cubesandbox.py
+102
-0
cubesandbox.py
backend/services/cubesandbox.py
+294
-0
test_cubesandbox.py
backend/tests/test_cubesandbox.py
+85
-0
App.tsx
frontend/src/App.tsx
+2
-0
cubesandbox.ts
frontend/src/api/cubesandbox.ts
+61
-0
Sidebar.tsx
frontend/src/components/Sidebar.tsx
+2
-0
CubeSandboxDashboard.tsx
frontend/src/pages/CubeSandboxDashboard.tsx
+595
-0
No files found.
backend/agent/agent/anthropic_adapter.py
View file @
48965032
...
@@ -485,9 +485,11 @@ def _requires_bearer_auth(base_url: str | None) -> bool:
...
@@ -485,9 +485,11 @@ def _requires_bearer_auth(base_url: str | None) -> bool:
return
(
return
(
normalized
.
startswith
((
"https://api.minimax.io/anthropic"
,
"https://api.minimaxi.com/anthropic"
))
normalized
.
startswith
((
"https://api.minimax.io/anthropic"
,
"https://api.minimaxi.com/anthropic"
))
or
"azure.com"
in
normalized
or
"azure.com"
in
normalized
or
"workers.dev"
in
normalized
)
)
def
_base_url_needs_context_1m_beta
(
base_url
:
str
|
None
)
->
bool
:
def
_base_url_needs_context_1m_beta
(
base_url
:
str
|
None
)
->
bool
:
"""Return True for endpoints that still gate 1M context behind a beta."""
"""Return True for endpoints that still gate 1M context behind a beta."""
normalized
=
_normalize_base_url_text
(
base_url
)
.
lower
()
normalized
=
_normalize_base_url_text
(
base_url
)
.
lower
()
...
...
backend/agent/run_agent.py
View file @
48965032
...
@@ -4172,9 +4172,14 @@ class AIAgent:
...
@@ -4172,9 +4172,14 @@ class AIAgent:
try
:
try
:
from
tools.get_tools
import
register_company_tools
from
tools.get_tools
import
register_company_tools
register_company_tools
(
self
.
company_id
)
register_company_tools
(
self
.
company_id
)
# Re-load tools list from the registry so they are visible to the agent loop
from
model_tools
import
get_tool_definitions
self
.
tools
=
get_tool_definitions
(
quiet_mode
=
self
.
quiet_mode
)
self
.
valid_tool_names
=
{
tool
[
"function"
][
"name"
]
for
tool
in
self
.
tools
}
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
f
"Error registering company tools: {e}"
,
exc_info
=
True
)
logger
.
error
(
f
"Error registering company tools: {e}"
,
exc_info
=
True
)
from
agent.conversation_loop
import
run_conversation
from
agent.conversation_loop
import
run_conversation
result
=
run_conversation
(
self
,
user_message
,
system_message
,
conversation_history
,
task_id
,
stream_callback
,
persist_user_message
)
result
=
run_conversation
(
self
,
user_message
,
system_message
,
conversation_history
,
task_id
,
stream_callback
,
persist_user_message
)
...
...
backend/agent/tools/workspace_tools.py
View file @
48965032
...
@@ -37,11 +37,18 @@ def run_command(command: str, cwd: Optional[str] = None) -> str:
...
@@ -37,11 +37,18 @@ def run_command(command: str, cwd: Optional[str] = None) -> str:
except
Exception
as
e
:
except
Exception
as
e
:
return
f
"Error executing command: {str(e)}"
return
f
"Error executing command: {str(e)}"
def
_resolve_path
(
path
:
str
)
->
str
:
if
not
os
.
path
.
isabs
(
path
):
workspace
=
os
.
environ
.
get
(
"AGENT_WORKSPACE"
)
if
workspace
:
return
os
.
path
.
abspath
(
os
.
path
.
join
(
workspace
,
path
))
return
os
.
path
.
abspath
(
path
)
return
path
def
read_file
(
path
:
str
)
->
str
:
def
read_file
(
path
:
str
)
->
str
:
"""Read the contents of a file in the workspace."""
"""Read the contents of a file in the workspace."""
try
:
try
:
if
not
os
.
path
.
isabs
(
path
):
path
=
_resolve_path
(
path
)
path
=
os
.
path
.
abspath
(
path
)
if
not
os
.
path
.
exists
(
path
):
if
not
os
.
path
.
exists
(
path
):
return
f
"Error: File '{path}' does not exist."
return
f
"Error: File '{path}' does not exist."
...
@@ -54,8 +61,7 @@ def read_file(path: str) -> str:
...
@@ -54,8 +61,7 @@ def read_file(path: str) -> str:
def
write_file
(
path
:
str
,
content
:
str
)
->
str
:
def
write_file
(
path
:
str
,
content
:
str
)
->
str
:
"""Create or overwrite a file in the workspace with new content."""
"""Create or overwrite a file in the workspace with new content."""
try
:
try
:
if
not
os
.
path
.
isabs
(
path
):
path
=
_resolve_path
(
path
)
path
=
os
.
path
.
abspath
(
path
)
# Ensure parent directories exist
# Ensure parent directories exist
os
.
makedirs
(
os
.
path
.
dirname
(
path
),
exist_ok
=
True
)
os
.
makedirs
(
os
.
path
.
dirname
(
path
),
exist_ok
=
True
)
...
@@ -69,8 +75,7 @@ def write_file(path: str, content: str) -> str:
...
@@ -69,8 +75,7 @@ def write_file(path: str, content: str) -> str:
def
replace_content
(
path
:
str
,
target
:
str
,
replacement
:
str
)
->
str
:
def
replace_content
(
path
:
str
,
target
:
str
,
replacement
:
str
)
->
str
:
"""Replace a specific unique string in a file with new content."""
"""Replace a specific unique string in a file with new content."""
try
:
try
:
if
not
os
.
path
.
isabs
(
path
):
path
=
_resolve_path
(
path
)
path
=
os
.
path
.
abspath
(
path
)
if
not
os
.
path
.
exists
(
path
):
if
not
os
.
path
.
exists
(
path
):
return
f
"Error: File '{path}' does not exist."
return
f
"Error: File '{path}' does not exist."
...
...
backend/api/main_router.py
View file @
48965032
...
@@ -62,6 +62,7 @@ from .routes.environment_config import router as environment_config_router
...
@@ -62,6 +62,7 @@ from .routes.environment_config import router as environment_config_router
from
.routes.environment_probe
import
router
as
environment_probe_router
from
.routes.environment_probe
import
router
as
environment_probe_router
from
.routes.document_revisions
import
router
as
document_revisions_router
from
.routes.document_revisions
import
router
as
document_revisions_router
from
.routes.marketplace
import
router
as
marketplace_router
from
.routes.marketplace
import
router
as
marketplace_router
from
.routes.cubesandbox
import
router
as
cubesandbox_router
main_router
=
APIRouter
()
main_router
=
APIRouter
()
...
@@ -139,6 +140,7 @@ api_sub_router.include_router(environment_config_router)
...
@@ -139,6 +140,7 @@ api_sub_router.include_router(environment_config_router)
api_sub_router
.
include_router
(
environment_probe_router
)
api_sub_router
.
include_router
(
environment_probe_router
)
api_sub_router
.
include_router
(
document_revisions_router
)
api_sub_router
.
include_router
(
document_revisions_router
)
api_sub_router
.
include_router
(
marketplace_router
)
api_sub_router
.
include_router
(
marketplace_router
)
api_sub_router
.
include_router
(
cubesandbox_router
)
# Register /api sub-router to the main router
# Register /api sub-router to the main router
main_router
.
include_router
(
api_sub_router
)
main_router
.
include_router
(
api_sub_router
)
backend/api/routes/agents.py
View file @
48965032
...
@@ -199,7 +199,7 @@ async def run_agent_in_background(
...
@@ -199,7 +199,7 @@ async def run_agent_in_background(
from
config
import
settings
from
config
import
settings
if
settings
.
openai_api_key
==
"wrapper-test-key"
:
if
settings
.
openai_api_key
==
"wrapper-test-key"
and
os
.
environ
.
get
(
"REAL_AGENT_RUN"
)
!=
"true"
:
import
asyncio
import
asyncio
import
json
import
json
from
pathlib
import
Path
from
pathlib
import
Path
...
@@ -318,6 +318,16 @@ async def run_agent_in_background(
...
@@ -318,6 +318,16 @@ async def run_agent_in_background(
)
)
return
return
base_url
=
settings
.
openai_base_url
api_mode
=
None
if
base_url
and
"workers.dev"
in
base_url
:
api_mode
=
"anthropic_messages"
# Strip trailing /v1 for native Anthropic Messages API
if
base_url
.
endswith
(
"/v1"
):
base_url
=
base_url
[:
-
3
]
elif
base_url
.
endswith
(
"/v1/"
):
base_url
=
base_url
[:
-
4
]
ai_agent
=
AIAgent
(
ai_agent
=
AIAgent
(
model_name
=
model_name
,
model_name
=
model_name
,
company_id
=
company_id
,
company_id
=
company_id
,
...
@@ -326,7 +336,8 @@ async def run_agent_in_background(
...
@@ -326,7 +336,8 @@ async def run_agent_in_background(
step_callback
=
log_writer
.
on_step
,
step_callback
=
log_writer
.
on_step
,
status_callback
=
log_writer
.
on_status
,
status_callback
=
log_writer
.
on_status
,
api_key
=
settings
.
openai_api_key
,
api_key
=
settings
.
openai_api_key
,
base_url
=
settings
.
openai_base_url
,
base_url
=
base_url
,
api_mode
=
api_mode
,
)
)
...
...
backend/api/routes/cubesandbox.py
0 → 100644
View file @
48965032
"""
CubeSandbox API Endpoints.
Wraps the E2B-compatible REST APIs for frontend integration and admin controls.
"""
from
typing
import
Dict
,
Any
,
List
from
fastapi
import
APIRouter
,
HTTPException
,
Depends
,
status
,
Body
from
pydantic
import
BaseModel
from
middleware.auth
import
get_current_user
from
schemas.auth
import
AuthUserResponse
from
services.cubesandbox
import
CubeSandboxService
router
=
APIRouter
(
prefix
=
"/cubesandbox"
,
tags
=
[
"cubesandbox"
])
service
=
CubeSandboxService
()
class
CreateInstanceRequest
(
BaseModel
):
template_id
:
str
class
ExecuteCommandRequest
(
BaseModel
):
command
:
str
@
router
.
get
(
"/stats"
)
async
def
get_stats
(
current_user
:
AuthUserResponse
=
Depends
(
get_current_user
)
)
->
Dict
[
str
,
Any
]:
"""Get general CubeSandbox host and clustering metrics."""
return
await
service
.
get_stats
()
@
router
.
get
(
"/templates"
)
async
def
list_templates
(
current_user
:
AuthUserResponse
=
Depends
(
get_current_user
)
)
->
List
[
Dict
[
str
,
Any
]]:
"""List all prebuilt template images in Cube registry."""
return
await
service
.
list_templates
()
@
router
.
get
(
"/instances"
)
async
def
list_instances
(
current_user
:
AuthUserResponse
=
Depends
(
get_current_user
)
)
->
List
[
Dict
[
str
,
Any
]]:
"""List currently active microVM instances."""
return
await
service
.
list_instances
()
@
router
.
post
(
"/instances"
,
status_code
=
status
.
HTTP_201_CREATED
)
async
def
create_instance
(
payload
:
CreateInstanceRequest
,
current_user
:
AuthUserResponse
=
Depends
(
get_current_user
)
)
->
Dict
[
str
,
Any
]:
"""Provision and boot a hardware-isolated sandbox instance."""
try
:
return
await
service
.
create_instance
(
payload
.
template_id
)
except
Exception
as
e
:
raise
HTTPException
(
status_code
=
status
.
HTTP_500_INTERNAL_SERVER_ERROR
,
detail
=
f
"Failed to create microVM instance: {str(e)}"
)
@
router
.
delete
(
"/instances/{instance_id}"
)
async
def
delete_instance
(
instance_id
:
str
,
current_user
:
AuthUserResponse
=
Depends
(
get_current_user
)
)
->
Dict
[
str
,
Any
]:
"""Destroy a microVM sandbox instance."""
success
=
await
service
.
delete_instance
(
instance_id
)
if
not
success
:
raise
HTTPException
(
status_code
=
status
.
HTTP_404_NOT_FOUND
,
detail
=
f
"Instance {instance_id} not found or failed to delete"
)
return
{
"status"
:
"ok"
,
"message"
:
f
"Instance {instance_id} terminated."
}
@
router
.
post
(
"/instances/{instance_id}/execute"
)
async
def
execute_command
(
instance_id
:
str
,
payload
:
ExecuteCommandRequest
,
current_user
:
AuthUserResponse
=
Depends
(
get_current_user
)
)
->
Dict
[
str
,
Any
]:
"""Execute shell command in microVM instance."""
try
:
# Verify instance exists first
instances
=
await
service
.
list_instances
()
if
not
any
(
inst
[
"id"
]
==
instance_id
for
inst
in
instances
):
raise
HTTPException
(
status_code
=
status
.
HTTP_404_NOT_FOUND
,
detail
=
f
"Instance {instance_id} not found"
)
return
await
service
.
execute_command
(
instance_id
,
payload
.
command
)
except
HTTPException
:
raise
except
Exception
as
e
:
raise
HTTPException
(
status_code
=
status
.
HTTP_500_INTERNAL_SERVER_ERROR
,
detail
=
f
"Failed to execute command: {str(e)}"
)
backend/services/cubesandbox.py
0 → 100644
View file @
48965032
This diff is collapsed.
Click to expand it.
backend/tests/test_cubesandbox.py
0 → 100644
View file @
48965032
import
pytest
import
sys
from
pathlib
import
Path
from
unittest.mock
import
MagicMock
# Insert backend root to sys.path to resolve imports correctly in tests
sys
.
path
.
insert
(
0
,
str
(
Path
(
__file__
)
.
parent
.
parent
))
from
server
import
app
from
middleware.auth
import
get_current_user
from
services.cubesandbox
import
CubeSandboxService
@
pytest
.
mark
.
asyncio
async
def
test_cubesandbox_endpoints
():
import
httpx
# Setup mock user to return
mock_auth_user
=
MagicMock
()
mock_auth_user
.
id
=
"user_123"
async
def
mock_get_current_user
():
return
mock_auth_user
app
.
dependency_overrides
[
get_current_user
]
=
mock_get_current_user
transport
=
httpx
.
ASGITransport
(
app
=
app
)
try
:
async
with
httpx
.
AsyncClient
(
transport
=
transport
,
base_url
=
"http://testserver"
)
as
client
:
# 1. Test GET /api/cubesandbox/stats
response
=
await
client
.
get
(
"/api/cubesandbox/stats"
)
assert
response
.
status_code
==
200
stats
=
response
.
json
()
assert
"apiEndpoint"
in
stats
assert
"isLiveService"
in
stats
assert
"activeInstances"
in
stats
# 2. Test GET /api/cubesandbox/templates
response
=
await
client
.
get
(
"/api/cubesandbox/templates"
)
assert
response
.
status_code
==
200
templates
=
response
.
json
()
assert
len
(
templates
)
>
0
assert
templates
[
0
][
"id"
]
==
"sandbox-code-latest"
# 3. Test GET /api/cubesandbox/instances
response
=
await
client
.
get
(
"/api/cubesandbox/instances"
)
assert
response
.
status_code
==
200
instances
=
response
.
json
()
assert
len
(
instances
)
>
0
# 4. Test POST /api/cubesandbox/instances (Create)
response
=
await
client
.
post
(
"/api/cubesandbox/instances"
,
json
=
{
"template_id"
:
"sandbox-code-latest"
}
)
assert
response
.
status_code
==
201
new_instance
=
response
.
json
()
assert
"id"
in
new_instance
assert
new_instance
[
"templateId"
]
==
"sandbox-code-latest"
instance_id
=
new_instance
[
"id"
]
# 5. Test POST /api/cubesandbox/instances/{instance_id}/execute (Execute)
response
=
await
client
.
post
(
f
"/api/cubesandbox/instances/{instance_id}/execute"
,
json
=
{
"command"
:
"uname -a"
}
)
assert
response
.
status_code
==
200
exec_result
=
response
.
json
()
assert
"stdout"
in
exec_result
assert
"exitCode"
in
exec_result
# 6. Test DELETE /api/cubesandbox/instances/{instance_id} (Delete)
response
=
await
client
.
delete
(
f
"/api/cubesandbox/instances/{instance_id}"
)
assert
response
.
status_code
==
200
del_result
=
response
.
json
()
assert
del_result
[
"status"
]
==
"ok"
# 7. Test execute on deleted instance (should fail with 404)
response
=
await
client
.
post
(
f
"/api/cubesandbox/instances/{instance_id}/execute"
,
json
=
{
"command"
:
"uname -a"
}
)
assert
response
.
status_code
==
404
finally
:
app
.
dependency_overrides
.
clear
()
frontend/src/App.tsx
View file @
48965032
...
@@ -22,6 +22,7 @@ import { RoutineDetail } from "./pages/RoutineDetail";
...
@@ -22,6 +22,7 @@ import { RoutineDetail } from "./pages/RoutineDetail";
import
{
UserProfile
}
from
"./pages/UserProfile"
;
import
{
UserProfile
}
from
"./pages/UserProfile"
;
import
{
ExecutionWorkspaceDetail
}
from
"./pages/ExecutionWorkspaceDetail"
;
import
{
ExecutionWorkspaceDetail
}
from
"./pages/ExecutionWorkspaceDetail"
;
import
{
Marketplace
}
from
"./pages/Marketplace"
;
import
{
Marketplace
}
from
"./pages/Marketplace"
;
import
{
CubeSandboxDashboard
}
from
"./pages/CubeSandboxDashboard"
;
import
{
Goals
}
from
"./pages/Goals"
;
import
{
Goals
}
from
"./pages/Goals"
;
import
{
GoalDetail
}
from
"./pages/GoalDetail"
;
import
{
GoalDetail
}
from
"./pages/GoalDetail"
;
import
{
Approvals
}
from
"./pages/Approvals"
;
import
{
Approvals
}
from
"./pages/Approvals"
;
...
@@ -121,6 +122,7 @@ function boardRoutes() {
...
@@ -121,6 +122,7 @@ function boardRoutes() {
<
Route
path=
"execution-workspaces/:workspaceId/live-tracking"
element=
{
<
ExecutionWorkspaceDetail
/>
}
/>
<
Route
path=
"execution-workspaces/:workspaceId/live-tracking"
element=
{
<
ExecutionWorkspaceDetail
/>
}
/>
<
Route
path=
"execution-workspaces/:workspaceId/ceo-briefing"
element=
{
<
ExecutionWorkspaceDetail
/>
}
/>
<
Route
path=
"execution-workspaces/:workspaceId/ceo-briefing"
element=
{
<
ExecutionWorkspaceDetail
/>
}
/>
<
Route
path=
"marketplace"
element=
{
<
Marketplace
/>
}
/>
<
Route
path=
"marketplace"
element=
{
<
Marketplace
/>
}
/>
<
Route
path=
"cubesandbox"
element=
{
<
CubeSandboxDashboard
/>
}
/>
<
Route
path=
"goals"
element=
{
<
Goals
/>
}
/>
<
Route
path=
"goals"
element=
{
<
Goals
/>
}
/>
<
Route
path=
"goals/:goalId"
element=
{
<
GoalDetail
/>
}
/>
<
Route
path=
"goals/:goalId"
element=
{
<
GoalDetail
/>
}
/>
<
Route
path=
"approvals"
element=
{
<
Navigate
to=
"/approvals/pending"
replace
/>
}
/>
<
Route
path=
"approvals"
element=
{
<
Navigate
to=
"/approvals/pending"
replace
/>
}
/>
...
...
frontend/src/api/cubesandbox.ts
0 → 100644
View file @
48965032
import
{
api
}
from
"./client"
;
export
interface
SandboxTemplate
{
id
:
string
;
name
:
string
;
image
:
string
;
description
:
string
;
writable_layer_size
:
string
;
memory_limit
:
string
;
status
:
string
;
}
export
interface
SandboxInstance
{
id
:
string
;
template_id
:
string
;
status
:
string
;
ip_address
:
string
;
boot_time_ms
:
number
;
memory_overhead_mb
:
number
;
cpu_usage_pct
:
number
;
created_at
:
string
;
kernel
:
string
;
commands_run
:
number
;
}
export
interface
SandboxStats
{
is_live_service
:
boolean
;
api_endpoint
:
string
;
isolation_type
:
string
;
active_instances
:
number
;
total_instances_created
:
number
;
avg_boot_time_ms
:
number
;
avg_memory_overhead_mb
:
number
;
total_sandbox_memory_mb
:
number
;
host_cpu_usage_pct
:
number
;
host_memory_free_gb
:
number
;
host_kernel
:
string
;
}
export
interface
ExecuteResult
{
stdout
:
string
;
stderr
:
string
;
exit_code
:
number
;
}
export
const
cubesandboxApi
=
{
getStats
:
()
=>
api
.
get
<
SandboxStats
>
(
"/cubesandbox/stats"
),
listTemplates
:
()
=>
api
.
get
<
SandboxTemplate
[]
>
(
"/cubesandbox/templates"
),
listInstances
:
()
=>
api
.
get
<
SandboxInstance
[]
>
(
"/cubesandbox/instances"
),
createInstance
:
(
templateId
:
string
)
=>
api
.
post
<
SandboxInstance
>
(
"/cubesandbox/instances"
,
{
template_id
:
templateId
}),
deleteInstance
:
(
instanceId
:
string
)
=>
api
.
delete
<
{
status
:
string
;
message
:
string
}
>
(
`/cubesandbox/instances/
${
instanceId
}
`
),
executeCommand
:
(
instanceId
:
string
,
command
:
string
)
=>
api
.
post
<
ExecuteResult
>
(
`/cubesandbox/instances/
${
instanceId
}
/execute`
,
{
command
})
};
frontend/src/components/Sidebar.tsx
View file @
48965032
...
@@ -13,6 +13,7 @@ import {
...
@@ -13,6 +13,7 @@ import {
GitBranch
,
GitBranch
,
Settings
,
Settings
,
Store
,
Store
,
Cpu
,
}
from
"lucide-react"
;
}
from
"lucide-react"
;
import
{
useQuery
}
from
"@tanstack/react-query"
;
import
{
useQuery
}
from
"@tanstack/react-query"
;
import
{
NavLink
}
from
"@/lib/router"
;
import
{
NavLink
}
from
"@/lib/router"
;
...
@@ -92,6 +93,7 @@ export function Sidebar() {
...
@@ -92,6 +93,7 @@ export function Sidebar() {
alert=
{
inboxBadge
.
failedRuns
>
0
}
alert=
{
inboxBadge
.
failedRuns
>
0
}
/>
/>
<
SidebarNavItem
to=
"/marketplace"
label=
"Marketplace"
icon=
{
Store
}
/>
<
SidebarNavItem
to=
"/marketplace"
label=
"Marketplace"
icon=
{
Store
}
/>
<
SidebarNavItem
to=
"/cubesandbox"
label=
"Cube Sandbox"
icon=
{
Cpu
}
/>
<
PluginSlotOutlet
<
PluginSlotOutlet
slotTypes=
{
[
"sidebar"
]
}
slotTypes=
{
[
"sidebar"
]
}
context=
{
pluginContext
}
context=
{
pluginContext
}
...
...
frontend/src/pages/CubeSandboxDashboard.tsx
0 → 100644
View file @
48965032
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment