Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
99b4b40
feat: add GraphQL queries for application, flow, namespace, and proje…
nicosammito Aug 26, 2026
ab921bf
feat: enhance ApplicationGeneralSettingsView with improved layout and…
nicosammito Aug 26, 2026
b99947c
feat: integrate ApplicationUsageView into ApplicationNavigationView
nicosammito Aug 26, 2026
b3814a9
feat: implement ApplicationUsageView to display usage indicators for …
nicosammito Aug 26, 2026
8ef9ef2
feat: update PICTOR version to 0.18.1 in environment configuration
nicosammito Aug 26, 2026
5875a7e
feat: integrate UsageService into ApplicationLayout for usage tracking
nicosammito Aug 26, 2026
c55b370
feat: update PICTOR version to 0.18.1 in package.json and package-loc…
nicosammito Aug 26, 2026
690c5c3
feat: add UsageIndicatorComponent for displaying usage metrics with t…
nicosammito Aug 26, 2026
8e7ad0d
feat: implement UsageService for tracking application, namespace, pro…
nicosammito Aug 26, 2026
9a010fc
feat: refactor LicenseAddPage for improved file input handling and va…
nicosammito Aug 27, 2026
e176788
feat: update ApplicationMiddlewareComponent to use userStore from use…
nicosammito Aug 27, 2026
bd4924b
feat: enhance ApplicationNavigationView to conditionally render compo…
nicosammito Aug 27, 2026
25f2070
feat: enhance usage tracking by adding date range parameters to usage…
nicosammito Aug 27, 2026
ed6fc78
feat: enhance LicenseAddPage with improved file upload UI and add lin…
nicosammito Aug 27, 2026
e420658
feat: move session check to improve routing logic in ApplicationMiddl…
nicosammito Aug 27, 2026
ed66d3e
feat: enhance ProjectSettingsGeneralView with improved input descript…
nicosammito Aug 27, 2026
589cd11
feat: enhance FlowPanelLayoutComponent with flow update functionality…
nicosammito Aug 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.local
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ PLAYGROUND_MOCK_DIR=#./mock
PLAYGROUND_FRAME_ANCESTORS=#"'self'"

NEXT_PUBLIC_SCULPTOR_VERSION=0.0.0
NEXT_PUBLIC_PICTOR_VERSION=0.18.0
NEXT_PUBLIC_PICTOR_VERSION=0.18.1
NEXT_PUBLIC_ALLOWED_REDIRECT_DOMAINS=*.code0.tech,*.codezero.build
NEXT_PUBLIC_SUBSCRIPTION_URL=https://codezero.build/subscription

Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"dependencies": {
"@apollo/client": "^4.0.9",
"@code0-tech/pictor": "^0.18.0",
"@code0-tech/pictor": "^0.18.1",
"@code0-tech/triangulum": "^0.33.0",
"@code0-tech/tucana": "^0.0.82",
"@codemirror/lang-javascript": "^6.2.5",
Expand Down
20 changes: 12 additions & 8 deletions src/app/(dashboard)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {DatatypeService} from "@edition/datatype/services/Datatype.service";
import {FlowTypeService} from "@edition/flowtype/services/FlowType.service";
import {ModuleService} from "@edition/module/services/Module.service";
import {AIService, Model} from "@edition/ai/services/AI.service";
import {UsageEntry, UsageService} from "@edition/usage/services/Usage.service";

interface ApplicationLayoutProps {
children: React.ReactNode
Expand Down Expand Up @@ -76,6 +77,7 @@ const ApplicationLayout: React.FC<ApplicationLayoutProps> = ({children, sidebar,
const flowtype = usePersistentReactiveArrayService<FlowType, FlowTypeService>(`dashboard::flowtypes::${currentSession?.id}`, (store) => new FlowTypeService(graphqlClient, store))
const module = usePersistentReactiveArrayService<RuntimeModule, ModuleService>(`dashboard::modules::${currentSession?.id}`, (store) => new ModuleService(graphqlClient, store))
const ai = usePersistentReactiveArrayService<Model, AIService>(`dashboard::ai::${currentSession?.id}`, (store) => new AIService(graphqlClient, store))
const usage = usePersistentReactiveArrayService<UsageEntry, UsageService>(`dashboard::usage::${currentSession?.id}`, (store) => new UsageService(graphqlClient, store))

const runtimeId = React.useMemo(() => project[1].getById(projectId, {namespaceId})?.primaryRuntime?.id, [projectId, project[0], namespaceId])

Expand All @@ -89,10 +91,10 @@ const ApplicationLayout: React.FC<ApplicationLayoutProps> = ({children, sidebar,
module[1].values({namespaceId, projectId, runtimeId})
}, [runtimeId, namespaceId, projectId, currentSession, flow, functions, datatype, flowtype])

const isAddLicensePage = pathname === '/licenses/add';
const isAddLicensePage = pathname === '/licenses/add'

return <ContextStoreProvider
services={[application, user, organization, member, namespace, runtime, project, role, flow, functions, datatype, flowtype, module, ai]}>
services={[application, user, organization, member, namespace, runtime, project, role, flow, functions, datatype, flowtype, module, ai, usage]}>
<ApplicationMiddlewareComponent>
<MfaProviderComponent>
<FullScreen bg={"var(--primary)"}>
Expand Down Expand Up @@ -132,12 +134,14 @@ const ApplicationLayout: React.FC<ApplicationLayoutProps> = ({children, sidebar,
<AuroraBackground/>

</div>
<Layout p={1} showLayoutSplitter={false} layoutGap={32} leftContent={<ApplicationNavigationView/>}>
<Layout showLayoutSplitter={false} layoutGap={32} leftContent={<div style={{
height: "100%",
boxSizing: "border-box",
maxWidth: "20vw"
}}>{sidebar}</div>}>
<Layout p={1} showLayoutSplitter={false} layoutGap={32}
leftContent={<ApplicationNavigationView/>}>
<Layout showLayoutSplitter={false} layoutGap={32}
leftContent={!isAddLicensePage ? <div style={{
height: "100%",
boxSizing: "border-box",
maxWidth: "20vw"
}}>{sidebar}</div> : undefined}>
<>
{children}
{modal}
Expand Down
4 changes: 3 additions & 1 deletion src/app/(dashboard)/licenses/add/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"use client"

import React from "react";
import {Col, Flex, Spacing, Text} from "@code0-tech/pictor";
import {Col, Flex, Icon, Spacing, Text} from "@code0-tech/pictor";

export default function LicenseAddLayout({children}: Readonly<{ children: React.ReactNode }>) {
return <Flex h={"100%"} w={"100%"} align={"center"} justify={"center"}>
<Col xs={4} style={{marginTop: "auto", marginBottom: "auto"}}>
<Icon icon={"codezero:codezero"} color={"#fff"} size={40}/>
<Spacing spacing={"xl"}/>
<Text style={{fontSize: "2rem", fontWeight: "600"}}
hierarchy={"primary"}
display={"inline"}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Button,
Card,
Flex,
getSize,
NumberInput,
Spacing,
Text,
Expand Down Expand Up @@ -39,9 +40,6 @@ export const ApplicationGeneralSettingsView: React.FC = () => {
[application]
)

// The +/- buttons of NumberInput write to the input element directly without
// triggering a React change event, so the useForm value goes stale. Read the
// value from the element itself on submit instead.
const runtimeHeartbeatInputRef = React.useRef<HTMLInputElement>(null)

const [inputs, validate] = useForm({
Expand Down Expand Up @@ -77,82 +75,91 @@ export const ApplicationGeneralSettingsView: React.FC = () => {
</Text>
<Spacing spacing={"md"}/>
<Text size={"md"} hierarchy={"secondary"}>Versions</Text>
<Spacing spacing={"xs"}/>
<Spacing spacing={"lg"}/>
<Card color={"secondary"}>
<CardSection border>
<Flex justify={"space-between"} align={"center"}>
<Flex style={{gap: ".35rem", flexDirection: "column"}}>
<Flex justify={"space-between"} align={"center"} style={{gap: getSize("md")}}>
<Flex style={{gap: getSize("xxxs"), flexDirection: "column"}}>
<Text size={"md"} hierarchy={"primary"}>Sculptor version</Text>
<Text size={"md"} hierarchy={"tertiary"}>Version of this application</Text>
</Flex>
<Badge color={"info"}>{process.env.NEXT_PUBLIC_SCULPTOR_VERSION}</Badge>
<Badge color={"info"}>
<Text c={"inherit"}>{process.env.NEXT_PUBLIC_SCULPTOR_VERSION ?? "Unknown version"}</Text>
</Badge>
</Flex>
</CardSection>

<CardSection border>
<Flex justify={"space-between"} align={"center"}>
<Flex style={{gap: ".35rem", flexDirection: "column"}}>
<Flex justify={"space-between"} align={"center"} style={{gap: getSize("md")}}>
<Flex style={{gap: getSize("xxxs"), flexDirection: "column"}}>
<Text size={"md"} hierarchy={"primary"}>Pictor version</Text>
<Text size={"md"} hierarchy={"tertiary"}>Version of the UI component
library</Text>
</Flex>
<Badge
color={"info"}>{process.env.NEXT_PUBLIC_PICTOR_VERSION}</Badge>
<Badge color={"info"}>
<Text c={"inherit"}>{process.env.NEXT_PUBLIC_PICTOR_VERSION ?? "Unknown version"}</Text>
</Badge>
</Flex>
</CardSection>
<CardSection border>
<Flex justify={"space-between"} align={"center"}>
<Flex style={{gap: ".35rem", flexDirection: "column"}}>
<Flex justify={"space-between"} align={"center"} style={{gap: getSize("md")}}>
<Flex style={{gap: getSize("xxxs"), flexDirection: "column"}}>
<Text size={"md"} hierarchy={"primary"}>Sagittarius version</Text>
<Text size={"md"} hierarchy={"tertiary"}>Version of the backend</Text>
</Flex>
<Badge color={"info"}>{application?.metadata?.version}</Badge>
<Badge color={"info"} style={{wordBreak: "break-all"}} maw={"50%"}>
<Text c={"inherit"}>{application?.metadata?.version ?? "Unknown version"}</Text>
</Badge>
</Flex>
</CardSection>
</Card>
<Spacing spacing={"lg"}/>
<Text size={"md"} hierarchy={"secondary"}>Legal URLs</Text>
<Spacing spacing={"xs"}/>
<Text size={"md"} hierarchy={"secondary"}>URLs</Text>
<Spacing spacing={"lg"}/>
<Card color={"secondary"}>
<CardSection border>
<Flex justify={"space-between"} align={"center"}>
<Flex style={{gap: ".35rem", flexDirection: "column"}}>
<Text size={"md"} hierarchy={"primary"}>Legal notice url</Text>
</Flex>
<TextInput {...inputs.getInputProps("legalNoticeUrl")}/>
</Flex>
<Text size={"md"} hierarchy={"primary"}>Legal notice url</Text>
<Spacing spacing={"xxs"}/>
<Text size={"sm"} hierarchy={"tertiary"}>General configuration for your Sculptor application, including version information, legal links and runtime behaviour.</Text>
<Spacing spacing={"xs"}/>
<TextInput clearable
placeholder={"https://codezero.build/legal"}
{...inputs.getInputProps("legalNoticeUrl")}/>
</CardSection>
<CardSection border>
<Flex justify={"space-between"} align={"center"}>
<Flex style={{gap: ".35rem", flexDirection: "column"}}>
<Text size={"md"} hierarchy={"primary"}>Privacy information url</Text>
</Flex>
<TextInput {...inputs.getInputProps("privacyUrl")}/>
</Flex>
<Text size={"md"} hierarchy={"primary"}>Privacy information url</Text>
<Spacing spacing={"xxs"}/>
<Text size={"sm"} hierarchy={"tertiary"}>General configuration for your Sculptor application, including version information, legal links and runtime behaviour.</Text>
<Spacing spacing={"xs"}/>

<TextInput clearable
placeholder={"https://codezero.build/privacy"}
{...inputs.getInputProps("privacyUrl")}/>
</CardSection>
<CardSection border>
<Flex justify={"space-between"} align={"center"}>
<Flex style={{gap: ".35rem", flexDirection: "column"}}>
<Text size={"md"} hierarchy={"primary"}>Terms and conditions url</Text>
</Flex>
<TextInput {...inputs.getInputProps("termsAndConditionsUrl")}/>
</Flex>
<Text size={"md"} hierarchy={"primary"}>Terms and conditions url</Text>
<Spacing spacing={"xxs"}/>
<Text size={"sm"} hierarchy={"tertiary"}>General configuration for your Sculptor application, including version information, legal links and runtime behaviour.</Text>
<Spacing spacing={"xs"}/>

<TextInput clearable
placeholder={"https://codezero.build/terms"}
{...inputs.getInputProps("termsAndConditionsUrl")}/>
</CardSection>
</Card>
<Spacing spacing={"lg"}/>
<Text size={"md"} hierarchy={"secondary"}>Runtimes</Text>
<Spacing spacing={"xs"}/>
<Spacing spacing={"lg"}/>
<Card color={"secondary"}>
<CardSection border>
<Flex justify={"space-between"} align={"center"}>
<Flex style={{gap: ".35rem", flexDirection: "column"}}>
<Text size={"md"} hierarchy={"primary"}>Max heartbeat interval</Text>
<Text size={"md"} hierarchy={"tertiary"}>The maximum amount of minutes a runtime is
shown as connected after the last heartbeat.</Text>
</Flex>
<NumberInput ref={runtimeHeartbeatInputRef}
{...inputs.getInputProps("runtimeMaxHeartbeatIntervalMinutes")}/>
</Flex>
<Text size={"md"} hierarchy={"primary"}>Max heartbeat interval</Text>
<Spacing spacing={"xxs"}/>
<Text size={"sm"} hierarchy={"tertiary"}>The maximum amount of minutes a runtime is shown as connected after the last heartbeat.</Text>
<Spacing spacing={"xs"}/>

<NumberInput
ref={runtimeHeartbeatInputRef}
{...inputs.getInputProps("runtimeMaxHeartbeatIntervalMinutes")}/>
</CardSection>
</Card>
</TabContent>
Expand Down
69 changes: 38 additions & 31 deletions src/packages/ce/src/application/views/ApplicationNavigationView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import React, {startTransition} from "react";
import {
Avatar,
Button,
Flex, getSize,
Flex,
getSize,
Icon,
Menu,
MenuContent,
Expand All @@ -26,27 +27,30 @@ import {
IconApps,
IconArrowAutofitLeft,
IconArrowAutofitLeftFilled,
IconHomeFilled,
IconSettingsFilled,
IconUser
} from "@tabler/icons-react";
import Link from "next/link";
import {UserService} from "@edition/user/services/User.service";
import {useUserSession} from "@edition/user/hooks/User.session.hook";
import {useRouter} from "next/navigation";
import {usePathname, useRouter} from "next/navigation";
import {ApplicationUsageView} from "@edition/application/views/ApplicationUsageView";

export const ApplicationNavigationView: React.FC = () => {


const pathname = usePathname()
const router = useRouter()
const userService = useService(UserService)
const userStore = useStore(UserService)

const router = useRouter()
const currentSession = useUserSession()

const isAddLicensePage = pathname === '/licenses/add';

const currentUser = React.useMemo(
() => userService.getById(currentSession?.user?.id),
[userStore, currentSession]
)

const userNamespaceIndex = React.useMemo(
() => currentUser?.namespace?.id?.match(/Namespace\/(\d+)$/)?.[1],
[currentUser]
Expand Down Expand Up @@ -83,7 +87,7 @@ export const ApplicationNavigationView: React.FC = () => {
</TooltipContent>
</TooltipPortal>
</Tooltip>
{currentUser?.admin && (
{currentUser?.admin && !isAddLicensePage && (
<Tooltip>
<TooltipTrigger asChild>
<Link href={"/settings"} prefetch>
Expand All @@ -101,8 +105,9 @@ export const ApplicationNavigationView: React.FC = () => {
</TooltipPortal>
</Tooltip>
)}
{!isAddLicensePage && <ApplicationUsageView/>}
<Flex style={{marginTop: "auto", boxSizing: "border-box", flexDirection: 'column', gap: "0.7rem"}}>
<Tooltip>
{!isAddLicensePage && <Tooltip>
<TooltipTrigger asChild>
<Link href={"/users/@me/settings"} prefetch>
<Button variant={"none"} style={{padding: getSize("xs"), marginTop: 'auto'}}>
Expand All @@ -117,7 +122,7 @@ export const ApplicationNavigationView: React.FC = () => {
</Text>
</TooltipContent>
</TooltipPortal>
</Tooltip>
</Tooltip>}
<Tooltip>
<TooltipTrigger asChild>
<Button onClick={userLogout} variant={"none"} style={{padding: getSize("xs"), marginTop: 'auto'}}>
Expand All @@ -143,28 +148,30 @@ export const ApplicationNavigationView: React.FC = () => {
<MenuLabel>
User menu
</MenuLabel>
<Link href={`/users/@me`}>
<MenuItem>
<IconUser size={16}/>Profile
</MenuItem>
</Link>
<Link href={`/users/@me/settings`}>
<MenuItem>
<IconUser color={"transparent"} size={16}/>Settings
</MenuItem>
</Link>
<MenuSeparator/>
<Link href={"/"}>
<MenuItem>
<IconApps size={16}/>Workspaces
</MenuItem>
</Link>
<Link href={`/namespace/${userNamespaceIndex}`}>
<MenuItem>
<IconApps color={"transparent"} size={16}/>Personal Workspace
</MenuItem>
</Link>
<MenuSeparator/>
{!isAddLicensePage && <>
<Link href={`/users/@me`}>
<MenuItem>
<IconUser size={16}/>Profile
</MenuItem>
</Link>
<Link href={`/users/@me/settings`}>
<MenuItem>
<IconUser color={"transparent"} size={16}/>Settings
</MenuItem>
</Link>
<MenuSeparator/>
<Link href={"/"}>
<MenuItem>
<IconApps size={16}/>Workspaces
</MenuItem>
</Link>
<Link href={`/namespace/${userNamespaceIndex}`}>
<MenuItem>
<IconApps color={"transparent"} size={16}/>Personal Workspace
</MenuItem>
</Link>
<MenuSeparator/>
</>}
<MenuItem onSelect={userLogout}>
<IconArrowAutofitLeft size={16}/>Logout
</MenuItem>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use client"

import React from "react";
import {UsageIndicatorComponent} from "@edition/usage/components/UsageIndicatorComponent";

export const ApplicationUsageView: React.FC = () =>
<UsageIndicatorComponent/>
Loading