-
-
-
- Overview
-
-
-
- Track the current state of your organizations, projects and flows at a single glance
- and keep an eye on how your activity has developed across every area over the recent
- weeks
-
-
-
-
-
-
-
- Organizations
-
- {newOrganizationsLastMonth > 0 &&
-
- +{newOrganizationsLastMonth} last month
- }
-
-
-
-
-
-
- {organizationNamespaces.length}
-
-
- You are the owner of {ownedOrganizationsCount} organizations and a
- member of {memberOrganizationsCount}
-
-
-
-
-
-
-
-
-
- Projects
-
- {newProjectsLastWeek > 0 &&
-
- +{newProjectsLastWeek} last week
- }
-
-
-
-
-
-
- {totalProjectsCount}
-
-
- {personalProjectsCount} in your personal workspace and
- {" "}{organizationProjectsCount} in your organizations
-
-
-
-
-
-
+
-
- Your work
-
-
-
- A quick summary of the personal projects you own and the organizations you belong to
- so you can jump straight back into your recent work or start something completely
- new
-
+
-
-
-
- Add new members
-
-
-
- Global runtimes are shared runtimes that can be used across multiple organizations.
-
-
- {/*@ts-ignore*/}
-
-
-
-
-
- Go back to members
-
-
-
- Add members
-
-
-
-
-
-}
\ No newline at end of file
+ const namespaceIndex = params.namespaceId as any as number
+ const namespaceId: Namespace['id'] = `gid://sagittarius/Namespace/${namespaceIndex}`
+ const settingsHref = `/namespace/${namespaceIndex}/settings`
+
+ return {
+ if (open) return
+
+ const nav = (window as unknown as { navigation?: { entries(): { url: string }[], currentEntry?: { index: number } } }).navigation
+ if (!nav?.entries) {
+ router.back()
+ return
+ }
+
+ const index = nav.currentEntry?.index ?? 0
+ if (index > 0) router.back()
+ else router.push(settingsHref)
+ }}/>
+}
diff --git a/src/packages/ce/src/member/pages/MembersPage.tsx b/src/packages/ce/src/member/pages/MembersPage.tsx
deleted file mode 100644
index 950a4ae3..00000000
--- a/src/packages/ce/src/member/pages/MembersPage.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-"use client"
-
-import {MembersView} from "@edition/member/views/MembersView";
-import React from "react";
-
-export const MembersPage: React.FC = () => {
- return
-
-
-}
\ No newline at end of file
diff --git a/src/packages/ce/src/member/views/MembersView.tsx b/src/packages/ce/src/member/views/MembersView.tsx
index 41a4fa67..9bd258f3 100644
--- a/src/packages/ce/src/member/views/MembersView.tsx
+++ b/src/packages/ce/src/member/views/MembersView.tsx
@@ -2,109 +2,167 @@
import React from "react";
import {useParams} from "next/navigation";
-import {Button, DataTableFilterProps, DataTableSortProps, Flex, Spacing, Text, ButtonGroup, Menu, MenuTrigger, MenuPortal, MenuContent, MenuCheckboxItem} from "@code0-tech/pictor";
+import {
+ Badge,
+ Button,
+ ButtonGroup,
+ Flex,
+ Menu,
+ MenuCheckboxItem,
+ MenuContent,
+ MenuItem,
+ MenuLabel,
+ MenuPortal,
+ MenuTrigger,
+ Spacing,
+ Text,
+ useService,
+ useStore
+} from "@code0-tech/pictor";
+import {DataTableSortProps} from "@code0-tech/pictor/dist/components/data-table/DataTable";
+import {IconAdjustmentsHorizontal, IconArrowsSort, IconCheck, IconPlus} from "@tabler/icons-react";
import Link from "next/link";
+import {Namespace, NamespaceMember} from "@code0-tech/sagittarius-graphql-types";
+import {MemberService} from "@edition/member/services/Member.service";
import {MemberDataTableComponent} from "@edition/member/components/MemberDataTableComponent";
-import {MemberDataTableFilterInputComponent} from "@edition/member/components/MemberDataTableFilterInputComponent";
-import {IconMinus, IconSortAscending, IconSortDescending} from "@tabler/icons-react";
+import {RoleService} from "@edition/role/services/Role.service";
+
+const sortLabels = {
+ "user.username": "Username",
+ createdAt: "Created",
+ updatedAt: "Updated",
+} as const
+
+const toggle = (value: string, setter: React.Dispatch>) =>
+ setter(prev => prev.includes(value) ? prev.filter(v => v !== value) : [...prev, value])
//TODO: user abilities for add user as member within namespace
export const MembersView: React.FC = () => {
const params = useParams()
- const namespaceId = params.namespaceId as any as number
+ const memberService = useService(MemberService)
+ const memberStore = useStore(MemberService)
+ const roleService = useService(RoleService)
+ const roleStore = useStore(RoleService)
- const [filter, setFilter] = React.useState({})
- const [sort, setSort] = React.useState({})
+ const namespaceIndex = params.namespaceId as any as number
+ const namespaceId: Namespace['id'] = `gid://sagittarius/Namespace/${namespaceIndex}`
- return <>
+ const members = React.useMemo(
+ () => memberService.values({namespaceId: namespaceId}),
+ [memberStore, namespaceId]
+ )
+
+ const roles = React.useMemo(
+ () => roleService.values({namespaceId: namespaceId}),
+ [roleStore, namespaceId]
+ )
+
+ const usernames = React.useMemo(
+ () => members.map(m => m?.user?.username).filter(Boolean) as string[],
+ [members]
+ )
+
+ const [sort, setSort] = React.useState("user.username")
+ const [usernameFilter, setUsernameFilter] = React.useState([])
+ const [roleFilter, setRoleFilter] = React.useState([])
-
-
-
- Members
-
-
- Manage members that belong to this namespace. You can add new members and manage their permissions.
-
+ const preFilter = React.useCallback((member: NamespaceMember) => {
+ if (usernameFilter.length > 0 && !usernameFilter.includes(member?.user?.username!)) return false
+ if (roleFilter.length > 0 && !member?.roles?.nodes?.some(n => roleFilter.includes(n?.id!))) return false
+ return true
+ }, [usernameFilter, roleFilter])
+
+ const visibleCount = React.useMemo(
+ () => members.filter(preFilter).length,
+ [members, preFilter]
+ )
+
+ const hasFilter = usernameFilter.length > 0 || roleFilter.length > 0
+
+ const sortProps: DataTableSortProps = {[sort]: "asc"}
+
+ return <>
+
+
+ Members
+ {visibleCount}
-
- Add user
-
+ {/* Filter */}
+
+ {/* Sort */}
+
+
+ {/* Add */}
+
+
+
+
+
-
-
- setFilter(filter)}/>
-
-
-
-
+
+
+ Manage members that belong to this namespace. You can add new members and manage their permissions.
+
+
+
>
-
-}
\ No newline at end of file
+}
diff --git a/src/packages/ce/src/module/components/ModuleConfigurationDialogComponent.tsx b/src/packages/ce/src/module/components/ModuleConfigurationDialogComponent.tsx
new file mode 100644
index 00000000..03cfdea7
--- /dev/null
+++ b/src/packages/ce/src/module/components/ModuleConfigurationDialogComponent.tsx
@@ -0,0 +1,286 @@
+"use client"
+
+import React from "react";
+import {useParams, useRouter} from "next/navigation";
+import {
+ Avatar,
+ Button,
+ Flex,
+ hashToColor,
+ Menu,
+ MenuContent,
+ MenuItem,
+ MenuPortal,
+ MenuTrigger,
+ SelectContent,
+ SelectItem,
+ SelectItemText,
+ SelectPortal,
+ SelectTrigger,
+ SelectValue,
+ SelectViewport,
+ Spacing,
+ Text,
+ useForm,
+ useService,
+ useStore
+} from "@code0-tech/pictor";
+import {ModuleService} from "@ce-internal/module/services/Module.service";
+import {
+ ModuleConfigurationInput,
+ Namespace,
+ NamespaceProject,
+ NamespaceProjectRuntimeAssignment,
+ RuntimeModule
+} from "@code0-tech/sagittarius-graphql-types";
+import {getTypeSchema} from "@code0-tech/triangulum";
+import {DatatypeService} from "@ce-internal/datatype/services/Datatype.service";
+import {DataTypeInputComponent} from "@ce-internal/datatype/components/inputs/DataTypeInputComponent";
+import {ButtonGroup} from "@code0-tech/pictor/dist/components/button-group/ButtonGroup";
+import {Select} from "@radix-ui/react-select";
+import {IconBadgeFilled, IconChevronDown} from "@tabler/icons-react";
+import {RuntimeService} from "@ce-internal/runtime/services/Runtime.service";
+import {ProjectService} from "@ce-internal/project/services/Project.service";
+import {toast} from "@code0-tech/pictor/dist/components/toast/Toast";
+import {InputDialog} from "@core/components/InputDialog";
+
+export interface ModuleConfigurationDialogComponentProps {
+ open?: boolean
+ onOpenChange?: (open: boolean) => void
+}
+
+export const ModuleConfigurationDialogComponent: React.FC = ({
+ open,
+ onOpenChange
+ }) => {
+
+ const params = useParams()
+ const moduleService = useService(ModuleService)
+ const moduleStore = useStore(ModuleService)
+ const dataTypeService = useService(DatatypeService)
+ const runtimeService = useService(RuntimeService)
+ const runtimeStore = useStore(RuntimeService)
+ const projectService = useService(ProjectService)
+ const projectStore = useStore(ProjectService)
+ const router = useRouter()
+
+ const namespaceIndex = params.namespaceId as any as number
+ const projectIndex = params.projectId as any as number
+ const moduleIndex = params.moduleId as any as number
+ const namespaceId: Namespace['id'] = `gid://sagittarius/Namespace/${namespaceIndex}`
+ const projectId: NamespaceProject['id'] = `gid://sagittarius/NamespaceProject/${projectIndex}`
+ const moduleId: RuntimeModule['id'] = `gid://sagittarius/RuntimeModule/${moduleIndex}`
+
+ const project = React.useMemo(
+ () => projectService.getById(projectId),
+ [projectStore, projectId]
+ )
+
+ const runtimes = React.useMemo(
+ () => runtimeService.values().filter(runtime => project?.runtimes?.nodes?.some(r => r?.id === runtime.id)),
+ [runtimeStore, project]
+ )
+
+ const runtime = React.useMemo(
+ () => runtimes.find((runtime) => runtime.modules?.nodes?.find(module => module?.id === moduleId)),
+ [runtimes]
+ )
+
+ const module = React.useMemo(
+ () => moduleService.getById(moduleId, {
+ namespaceId,
+ projectId,
+ runtimeId: runtime?.id
+ }),
+ [moduleId, moduleStore, namespaceId, projectId, runtime]
+ )
+
+ // Only runtimes that actually have this integration installed can be selected.
+ const selectableRuntimes = React.useMemo(
+ () => runtimes.filter(runtime => runtime.modules?.nodes?.some(m => m?.identifier === module?.identifier)),
+ [runtimes, module?.identifier]
+ )
+
+ const moduleConfigurationSchemas = module?.configurationDefinitions?.nodes?.map(moduleConfiguration => {
+ return getTypeSchema(moduleConfiguration?.type!, dataTypeService.values({
+ namespaceId,
+ projectId,
+ runtimeId: `gid://sagittarius/Runtime/1`
+ }))
+ }) ?? []
+
+ const initialValues = React.useMemo(
+ () => {
+ const values: Record = {}
+ module?.configurationDefinitions?.nodes?.forEach((moduleConfiguration, index) => {
+ const value = project?.runtimeAssignments?.nodes?.find(rA => rA?.runtime?.id == runtime?.id)?.moduleConfigurations?.nodes?.find?.(config => config?.definition?.id === moduleConfiguration?.id)?.value
+ values[moduleConfiguration?.id!] = value ? {__typename: "LiteralValue", value: value} : null
+ })
+ return values
+ },
+ [module, project, runtime?.id]
+ )
+
+ const onSubmit = React.useCallback((values: Record) => {
+ const namespaceProjectRuntimeAssignmentId: NamespaceProjectRuntimeAssignment['id'] = project?.runtimeAssignments?.nodes?.find(rA => rA?.runtime?.id == runtime?.id)?.id
+ const moduleConfigurations: Array = Object.entries(values)?.map(([key, value]) => {
+ return {
+ moduleConfigurationDefinitionId: key,
+ value: value?.value ?? null
+ } as ModuleConfigurationInput
+ })
+
+ if (!namespaceProjectRuntimeAssignmentId) return
+
+ projectService.projectModuleConfigurationUpdate({
+ namespaceProjectRuntimeAssignmentId,
+ moduleConfigurations
+ }).then(payload => {
+ if ((payload?.errors?.length ?? 0) <= 0) {
+ toast({title: "Saved configuration", color: "success"})
+ }
+ })
+
+ }, [projectService, project, runtime?.id])
+
+ const [inputs, validate] = useForm({
+ useInitialValidation: true,
+ truthyValidationBeforeSubmit: false,
+ initialValues: initialValues,
+ onSubmit: onSubmit,
+ })
+
+ console.log(module)
+
+ return onOpenChange?.(open)}
+ title={`${module?.names?.[0]?.content ?? "3-party"} Integration configuration`}
+ description={"Configure how this integration behaves within your project. Settings are applied per runtime and take effect for every flow that relies on it."}>
+
+
+ {module?.names?.[0]?.content ?? "Integration"}
+
+
+ {
+ const lRuntime = runtimes.find(r => r.id === value)
+ const lModule = lRuntime?.modules?.nodes?.find(m => m?.identifier === module?.identifier)
+ const number = lModule?.id?.match(/RuntimeModule\/(\d+)$/)?.[1]
+ router.push(`/namespace/${namespaceIndex}/project/${projectIndex}/module/${number}`)
+ }}>
+
+
+
+
+
+
+
+
+
+ {
+ selectableRuntimes.map(runtime => {
+ return
+
+
+
+
+ {runtime.name}
+
+ {runtime.id === project?.primaryRuntime?.id &&
+ }
+
+
+
+ })
+ }
+
+
+
+
+
+
+ Save
+
+
+
+
+
+ {module?.descriptions?.[0]?.content ?? "Configure how this integration behaves within your project. Settings are applied per runtime and take effect for every flow that relies on it."}
+
+
+
}
\ No newline at end of file
diff --git a/src/packages/ce/src/namespace/pages/NamespaceProjectsCreatePage.tsx b/src/packages/ce/src/namespace/pages/NamespaceProjectsCreatePage.tsx
index 03908fe0..9e187dce 100644
--- a/src/packages/ce/src/namespace/pages/NamespaceProjectsCreatePage.tsx
+++ b/src/packages/ce/src/namespace/pages/NamespaceProjectsCreatePage.tsx
@@ -1,108 +1,32 @@
"use client"
import React from "react";
-import {Button, Col, Flex, Spacing, Text, TextInput, toast, useForm, useService} from "@code0-tech/pictor";
-import Link from "next/link";
import {useParams, useRouter} from "next/navigation";
-import {ProjectService} from "@edition/project/services/Project.service";
import {Namespace} from "@code0-tech/sagittarius-graphql-types";
+import {ProjectCreateDialogComponent} from "@edition/project/components/ProjectCreateDialogComponent";
export const NamespaceProjectsCreatePage: React.FC = () => {
const params = useParams()
- const projectService = useService(ProjectService)
- const [, startTransition] = React.useTransition()
const router = useRouter()
- const namespaceIndex = params?.namespaceId as string
- const namespaceId: Namespace['id'] = `gid://sagittarius/Namespace/${namespaceIndex as unknown as number}`
+ const namespaceIndex = params.namespaceId as any as number
+ const namespaceId: Namespace['id'] = `gid://sagittarius/Namespace/${namespaceIndex}`
+ const fallbackHref = `/namespace/${namespaceIndex}`
- //TODO: user abilities for project creation within namespace
+ return {
+ if (open) return
- const [inputs, validate] = useForm({
- useInitialValidation: false,
- initialValues: {
- name: null,
- description: null,
- },
- validate: {
- name: (value) => {
- if (!value) return "Name is required"
- if ((value as string).length < 3) return "Name needs to be at least 3 characters"
- if ((value as string).length > 50) return "Name needs to be less than 50 characters"
- return null
- },
- description: (value) => {
- if (!value) return "Description is required"
- if ((value as string).length > 50) return "Description needs to be less than 50 characters"
- return null
- },
- },
- onSubmit: (values) => {
- startTransition(() => {
- if (!namespaceId) {
- toast({
- title: "The current user does not have a personal namespace.",
- color: "error",
- dismissible: true,
- })
- return
- }
- projectService.projectCreate({
- name: values.name as unknown as string,
- description: values.description as unknown as string,
- namespaceId: namespaceId
- }).then(payload => {
- if ((payload?.errors?.length ?? 0) <= 0) {
- router.push(`/namespace/${namespaceIndex}`)
- }
- })
- })
- }
- })
+ const nav = (window as unknown as { navigation?: { entries(): { url: string }[], currentEntry?: { index: number } } }).navigation
+ if (!nav?.entries) {
+ router.back()
+ return
+ }
- return
-
-
-
- Create new project
-
-
-
- Global runtimes are shared runtimes that can be used across multiple organizations.
-
-
-
-
-
-
-
-
-
- Go back to projects
-
-
-
- Create project
-
-
-
-
-
-}
\ No newline at end of file
+ const index = nav.currentEntry?.index ?? 0
+ if (index > 0) router.back()
+ else router.push(fallbackHref)
+ }}/>
+}
diff --git a/src/packages/ce/src/namespace/pages/NamespaceProjectsPage.tsx b/src/packages/ce/src/namespace/pages/NamespaceProjectsPage.tsx
deleted file mode 100644
index 1b432954..00000000
--- a/src/packages/ce/src/namespace/pages/NamespaceProjectsPage.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-"use client"
-
-import {ProjectsView} from "@edition/project/views/ProjectsView";
-import React from "react";
-
-export const NamespaceProjectsPage: React.FC = () => {
- return
-
-
-}
\ No newline at end of file
diff --git a/src/packages/ce/src/namespace/pages/NamespaceSettingsPage.tsx b/src/packages/ce/src/namespace/pages/NamespaceSettingsPage.tsx
index 1a1235eb..081f9928 100644
--- a/src/packages/ce/src/namespace/pages/NamespaceSettingsPage.tsx
+++ b/src/packages/ce/src/namespace/pages/NamespaceSettingsPage.tsx
@@ -1,56 +1,29 @@
"use client"
import React from "react";
-import {Tab, TabContent} from "@code0-tech/pictor/dist/components/tab/Tab";
-import {NamespaceDeleteView} from "@edition/namespace/views/NamespaceDeleteView";
-import {NamespaceGeneralSettingsView} from "@edition/namespace/views/NamespaceGeneralSettingsView";
-import {
- ResizableHandle,
- ResizablePanel,
- ResizablePanelGroup
-} from "@code0-tech/pictor/dist/components/resizable/Resizable";
-import {SidebarComponent} from "@core/components/SidebarComponent";
-import {NamespaceTabListView} from "@edition/namespace/views/NamespaceTabListView";
-import {useParams} from "next/navigation";
-import {useService, useStore} from "@code0-tech/pictor";
-import {NamespaceService} from "@edition/namespace/services/Namespace.service";
-import {Namespace} from "@code0-tech/sagittarius-graphql-types";
+import {useParams, useRouter} from "next/navigation";
+import {NamespaceSettingsDialogComponent} from "@edition/namespace/components/NamespaceSettingsDialogComponent";
export const NamespaceSettingsPage: React.FC = () => {
const params = useParams()
- const namespaceService = useService(NamespaceService)
- const namespaceStore = useStore(NamespaceService)
+ const router = useRouter()
const namespaceIndex = params.namespaceId as any as number
- const namespaceId: Namespace['id'] = `gid://sagittarius/Namespace/${namespaceIndex}`
+ const namespaceHref = `/namespace/${namespaceIndex}`
- const namespace = React.useMemo(
- () => namespaceService.getById(namespaceId),
- [namespaceStore, namespaceId]
- )
+ return {
+ if (open) return
- //TODO: add ability check for organization settings access for every settings tab
+ const nav = (window as unknown as { navigation?: { entries(): { url: string }[], currentEntry?: { index: number } } }).navigation
+ if (!nav?.entries) {
+ router.back()
+ return
+ }
- return
-
-
-
-
-
-
- <>
-
-
-
-
-
-
- >
-
-
-
-}
\ No newline at end of file
+ const index = nav.currentEntry?.index ?? 0
+ if (index > 0) router.back()
+ else router.push(namespaceHref)
+ }}/>
+}
diff --git a/src/packages/ce/src/namespace/pages/NamespacesPage.tsx b/src/packages/ce/src/namespace/pages/NamespacesPage.tsx
deleted file mode 100644
index 7edc513d..00000000
--- a/src/packages/ce/src/namespace/pages/NamespacesPage.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-"use client"
-
-import React from "react";
-import {NamespacesView} from "@edition/namespace/views/NamespacesView";
-import {
- AuroraBackground,
- Avatar,
- Badge,
- Button,
- Flex,
- ScrollArea,
- ScrollAreaScrollbar,
- ScrollAreaThumb,
- ScrollAreaViewport,
- Spacing,
- Text,
- useService,
- useStore
-} from "@code0-tech/pictor";
-import {IconMail, IconSparkles, IconUser} from "@tabler/icons-react";
-import {UserService} from "@edition/user/services/User.service";
-import {useUserSession} from "@edition/user/hooks/User.session.hook";
-import {Layout} from "@code0-tech/pictor/dist/components/layout/Layout";
-
-export const NamespacesPage = () => {
-
-
- const userStore = useStore(UserService)
- const userService = useService(UserService)
-
- const userSession = useUserSession()
- const currentUser = React.useMemo(() => userService.getById(userSession?.user?.id), [userStore, userSession])
-
- const leftContent =
-
-
-
-
- Workspaces
-
-
-
- Workspaces bring your organizations, projects and flows together in one shared place
- so you and your team can create, organize and manage everything from a single central
- view
-
-
-
- Create new organization
-
-
-
- Organizations are helpfully if managing a group of users and plenty of projects.
-
-
-
-
-
-
-
- Go back to organizations
-
-
-
- Create organization
-
-
-
-
-
-}
\ No newline at end of file
+ const nav = (window as unknown as { navigation?: { entries(): { url: string }[], currentEntry?: { index: number } } }).navigation
+ if (!nav?.entries) {
+ router.back()
+ return
+ }
+
+ const index = nav.currentEntry?.index ?? 0
+ if (index > 0) router.back()
+ else router.push("/workspaces")
+ }}/>
+}
diff --git a/src/packages/ce/src/organization/pages/OrganizationsPage.tsx b/src/packages/ce/src/organization/pages/OrganizationsPage.tsx
deleted file mode 100644
index ee3ab751..00000000
--- a/src/packages/ce/src/organization/pages/OrganizationsPage.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-"use client"
-
-import React from "react";
-import {OrganizationsView} from "@edition/organization/views/OrganizationsView";
-
-export const OrganizationsPage = () => {
- return
-
-
-
-}
\ No newline at end of file
diff --git a/src/packages/ce/src/organization/views/OrganizationsTopView.tsx b/src/packages/ce/src/organization/views/OrganizationsTopView.tsx
deleted file mode 100644
index bab3740c..00000000
--- a/src/packages/ce/src/organization/views/OrganizationsTopView.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-"use client"
-
-import React from "react";
-import {useRouter} from "next/navigation";
-import {OrganizationDataTableComponent} from "@edition/organization/components/OrganizationDataTableComponent";
-
-export const OrganizationsTopView = () => {
-
- const router = useRouter()
-
- return <>
- index < 5} onSelect={(organization) => {
- const number = organization?.namespace?.id?.match(/Namespace\/(\d+)$/)?.[1]
- router.push(`/namespace/${number}`)
- }}/>
- >
-
-}
\ No newline at end of file
diff --git a/src/packages/ce/src/organization/views/OrganizationsView.tsx b/src/packages/ce/src/organization/views/OrganizationsView.tsx
deleted file mode 100644
index 3f7cb0d1..00000000
--- a/src/packages/ce/src/organization/views/OrganizationsView.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-"use client"
-
-import {
- Button,
- Flex,
- Menu,
- MenuCheckboxItem,
- MenuContent,
- MenuPortal,
- MenuTrigger,
- Spacing,
- Text
-} from "@code0-tech/pictor";
-import Link from "next/link";
-import React from "react";
-import {useRouter} from "next/navigation";
-import {OrganizationDataTableComponent} from "@edition/organization/components/OrganizationDataTableComponent";
-import {
- OrganizationDataTableFilterInputComponent
-} from "@edition/organization/components/OrganizationDataTableFilterInputComponent";
-import {DataTableFilterProps, DataTableSortProps} from "@code0-tech/pictor/dist/components/data-table/DataTable";
-import {ButtonGroup} from "@code0-tech/pictor/dist/components/button-group/ButtonGroup";
-import {IconMinus, IconSortAscending, IconSortDescending} from "@tabler/icons-react";
-
-export const OrganizationsView = () => {
-
- const router = useRouter()
-
- const [filter, setFilter] = React.useState({})
- const [sort, setSort] = React.useState({})
-
- return <>
-
-
-
- Organizations
-
-
- Manage organizations that you belong to. You can create new organizations and switch between them.
-
-
-
-
- Create
-
-
-
-
-
-
-
- Projects
-
-
-
- Manage projects that you belong to. You can create new projects and switch between them.
-
-
-
-
- Create
-
-
-
-
-
-
- setFilter(filter)}/>
-
-
- {projectsList}
-
- >
-}
\ No newline at end of file
diff --git a/src/packages/ce/src/role/components/RoleCreateDialogComponent.tsx b/src/packages/ce/src/role/components/RoleCreateDialogComponent.tsx
new file mode 100644
index 00000000..79d7088b
--- /dev/null
+++ b/src/packages/ce/src/role/components/RoleCreateDialogComponent.tsx
@@ -0,0 +1,70 @@
+"use client"
+
+import React, {startTransition} from "react";
+import {Button, Spacing, TextInput, toast, useForm, useService} from "@code0-tech/pictor";
+import {Namespace} from "@code0-tech/sagittarius-graphql-types";
+import {RoleService} from "@edition/role/services/Role.service";
+import {InputDialog} from "@core/components/InputDialog";
+
+export interface RoleCreateDialogComponentProps {
+ open?: boolean
+ onOpenChange?: (open: boolean) => void
+ namespaceId?: Namespace['id']
+}
+
+export const RoleCreateDialogComponent: React.FC = ({open, onOpenChange, namespaceId}) => {
+
+ //TODO: user abilities for add role within namespace
+
+ const roleService = useService(RoleService)
+
+ const initialValues = React.useMemo(() => ({name: ""}), [])
+
+ const [inputs, validate] = useForm({
+ useInitialValidation: false,
+ initialValues,
+ validate: {
+ name: (value) => {
+ if (!value) return "Name is required"
+ if (value.length < 3) return "Name needs to be at least 3 characters"
+ if (value.length > 50) return "Name needs to be less than 50 characters"
+ return null
+ },
+ },
+ onSubmit: (values) => {
+ startTransition(() => {
+ if (!namespaceId) {
+ toast({
+ title: "The current user does not have a personal namespace.",
+ color: "error",
+ })
+ return
+ }
+ roleService.roleCreate({
+ name: values.name as unknown as string,
+ namespaceId: namespaceId
+ }).then(payload => {
+ if ((payload?.errors?.length ?? 0) <= 0) {
+ toast({title: "Created role", color: "success"})
+ onOpenChange?.(false)
+ }
+ })
+ })
+ }
+ })
+
+ return onOpenChange?.(open)}>
+
+
+ Create role
+
+}
diff --git a/src/packages/ce/src/role/components/RoleDataTableFilterInputComponent.tsx b/src/packages/ce/src/role/components/RoleDataTableFilterInputComponent.tsx
deleted file mode 100644
index 353a20c8..00000000
--- a/src/packages/ce/src/role/components/RoleDataTableFilterInputComponent.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import React from "react";
-import {
- DataTableFilterInput,
- DataTableFilterSuggestionMenu,
- MenuCheckboxItem,
- useService,
- useStore
-} from "@code0-tech/pictor";
-import {IconCheck} from "@tabler/icons-react";
-import {Namespace} from "@code0-tech/sagittarius-graphql-types";
-import {DataTableFilterProps} from "@code0-tech/pictor/dist/components/data-table/DataTable";
-import {RoleService} from "@edition/role/services/Role.service";
-
-export interface RoleDataTableFilterInputComponentProps {
- namespaceId: Namespace['id']
- onChange: (filter: DataTableFilterProps) => void
-}
-
-export const RoleDataTableFilterInputComponent: React.FC = (props) => {
-
- const {namespaceId, onChange} = props
-
- const roleService = useService(RoleService)
- const roleStore = useStore(RoleService)
-
- const roles = React.useMemo(
- () => roleService.values({namespaceId: namespaceId}),
- [roleStore, namespaceId]
- )
-
- const abilities = React.useMemo(() => {
- const all = new Set()
- roles.forEach(role => role.abilities?.forEach(ability => all.add(ability)))
- return Array.from(all)
- }, [roles])
-
- return {
-
- const split = currentValue.split(",").map(s => s.trim()).filter(Boolean)
-
- return
- {roles.map(role => {
-
- const isChecked = split.includes(role?.name!)
-
- return {
- e.preventDefault();
- e.stopPropagation();
- const updated = isChecked
- ? split.filter(name => name !== role?.name)
- : [...split, role?.name];
- applySuggestion(updated.join(","), true);
- }}>
- {isChecked ? : }
- {role?.name}
-
- })}
-
- }
- }, {
- token: "Permissions",
- key: "abilities",
- operators: ["isOneOf"],
- suggestion: (context, operator, currentValue, applySuggestion) => {
-
- const split = currentValue.split(",").map(s => s.trim()).filter(Boolean)
-
- return
- {abilities.map(ability => {
-
- const isChecked = split.includes(ability)
-
- return {
- e.preventDefault();
- e.stopPropagation();
- const updated = isChecked
- ? split.filter(name => name !== ability)
- : [...split, ability];
- applySuggestion(updated.join(","), true);
- }}>
- {isChecked ? : }
- {ability}
-
- })}
-
- }
- }
- ]}/>
-}
\ No newline at end of file
diff --git a/src/packages/ce/src/role/components/RoleSettingsDialogComponent.tsx b/src/packages/ce/src/role/components/RoleSettingsDialogComponent.tsx
new file mode 100644
index 00000000..555e595f
--- /dev/null
+++ b/src/packages/ce/src/role/components/RoleSettingsDialogComponent.tsx
@@ -0,0 +1,59 @@
+"use client"
+
+import React from "react";
+import {Button, Text} from "@code0-tech/pictor";
+import {TabList, TabTrigger} from "@code0-tech/pictor/dist/components/tab/Tab";
+import {IconFolder, IconSettings2, IconShieldLock, IconTrash} from "@tabler/icons-react";
+import {RoleGeneralAdjustmentView} from "@edition/role/views/RoleGeneralAdjustmentView";
+import {RolePermissionView} from "@edition/role/views/RolePermissionView";
+import {RoleProjectView} from "@edition/role/views/RoleProjectView";
+import {RoleDeleteView} from "@edition/role/views/RoleDeleteView";
+import {SettingDialog} from "@core/components/SettingDialog";
+
+export interface RoleSettingsDialogComponentProps {
+ open?: boolean
+ onOpenChange?: (open: boolean) => void
+}
+
+export const RoleSettingsDialogComponent: React.FC = (props) => {
+
+ const {open, onOpenChange} = props
+
+ //TODO: limit tabs based on user abilities for roles
+
+ return onOpenChange?.(open)}
+ title={"Role settings"}
+ description={"General settings and permissions for this role. These settings affect every member the role is assigned to."}
+ trigger={
+
+
+
+ General
+
+
+
+
+
+ Permissions
+
+
+
+
+
+ Limit to projects
+
+
+
+
+
+ Delete role
+
+
+ }>
+
+
+
+
+
+}
diff --git a/src/packages/ce/src/role/pages/RoleCreatePage.tsx b/src/packages/ce/src/role/pages/RoleCreatePage.tsx
index 8031713a..b2757529 100644
--- a/src/packages/ce/src/role/pages/RoleCreatePage.tsx
+++ b/src/packages/ce/src/role/pages/RoleCreatePage.tsx
@@ -1,95 +1,32 @@
"use client"
import React from "react";
-import {Button, Col, Flex, Spacing, Text, TextInput, toast, useForm, useService} from "@code0-tech/pictor";
-import Link from "next/link";
import {useParams, useRouter} from "next/navigation";
import {Namespace} from "@code0-tech/sagittarius-graphql-types";
-import {RoleService} from "@edition/role/services/Role.service";
+import {RoleCreateDialogComponent} from "@edition/role/components/RoleCreateDialogComponent";
export const RoleCreatePage: React.FC = () => {
- //TODO: user abilities for add role within namespace
-
const params = useParams()
- const roleService = useService(RoleService)
- const [, startTransition] = React.useTransition()
const router = useRouter()
- const namespaceIndex = params?.namespaceId as string
- const namespaceId: Namespace['id'] = `gid://sagittarius/Namespace/${namespaceIndex as unknown as number}`
-
- //TODO: user abilities for project creation within namespace
-
- const [inputs, validate] = useForm({
- useInitialValidation: false,
- initialValues: {
- name: ""
- },
- validate: {
- name: (value) => {
- if (!value) return "Name is required"
- if (value.length < 3) return "Name needs to be at least 3 characters"
- if (value.length > 50) return "Name needs to be less than 50 characters"
- return null
- },
- },
- onSubmit: (values) => {
- startTransition(() => {
- if (!namespaceId) {
- toast({
- title: "The current user does not have a personal namespace.",
- color: "error",
- dismissible: true,
- })
- return
- }
- roleService.roleCreate({
- name: values.name as unknown as string,
- namespaceId: namespaceId
- }).then(payload => {
- if ((payload?.errors?.length ?? 0) <= 0) {
- router.push(`/namespace/${namespaceIndex}/roles`)
- }
- })
- })
- }
- })
-
- return
-
-
-
- Create new role
-
-
-
- Global runtimes are shared runtimes that can be used across multiple organizations.
-
-
-
-
-
-
-
- Go back to roles
-
-
-
- Create role
-
-
-
-
-
-}
\ No newline at end of file
+ const namespaceIndex = params.namespaceId as any as number
+ const namespaceId: Namespace['id'] = `gid://sagittarius/Namespace/${namespaceIndex}`
+ const settingsHref = `/namespace/${namespaceIndex}/settings`
+
+ return {
+ if (open) return
+
+ const nav = (window as unknown as { navigation?: { entries(): { url: string }[], currentEntry?: { index: number } } }).navigation
+ if (!nav?.entries) {
+ router.back()
+ return
+ }
+
+ const index = nav.currentEntry?.index ?? 0
+ if (index > 0) router.back()
+ else router.push(settingsHref)
+ }}/>
+}
diff --git a/src/packages/ce/src/role/pages/RoleSettingsPage.tsx b/src/packages/ce/src/role/pages/RoleSettingsPage.tsx
index 8a66870b..3c96e149 100644
--- a/src/packages/ce/src/role/pages/RoleSettingsPage.tsx
+++ b/src/packages/ce/src/role/pages/RoleSettingsPage.tsx
@@ -1,68 +1,29 @@
"use client"
import React from "react";
-import {Button, ScrollArea, ScrollAreaScrollbar, ScrollAreaThumb, ScrollAreaViewport, Text} from "@code0-tech/pictor";
-import {Tab, TabList, TabTrigger} from "@code0-tech/pictor/dist/components/tab/Tab";
-import {RoleProjectView} from "@edition/role/views/RoleProjectView";
-import {RolePermissionView} from "@edition/role/views/RolePermissionView";
-import {RoleGeneralAdjustmentView} from "@edition/role/views/RoleGeneralAdjustmentView";
-import {RoleDeleteView} from "@edition/role/views/RoleDeleteView";
-import {
- ResizableHandle,
- ResizablePanel,
- ResizablePanelGroup
-} from "@code0-tech/pictor/dist/components/resizable/Resizable";
-import {SidebarComponent} from "@core/components/SidebarComponent";
+import {useParams, useRouter} from "next/navigation";
+import {RoleSettingsDialogComponent} from "@edition/role/components/RoleSettingsDialogComponent";
export const RoleSettingsPage: React.FC = () => {
- //TODO: limit tabs based on user abilities for roles
+ const params = useParams()
+ const router = useRouter()
- return <>
-
-
-
-
-
-
- General
-
-
-
-
- Permissions
-
-
-
-
- Limit to projects
-
-
-
-
- Delete role
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
-}
\ No newline at end of file
+ const namespaceIndex = params.namespaceId as any as number
+ const settingsHref = `/namespace/${namespaceIndex}/settings`
+
+ return {
+ if (open) return
+
+ const nav = (window as unknown as { navigation?: { entries(): { url: string }[], currentEntry?: { index: number } } }).navigation
+ if (!nav?.entries) {
+ router.back()
+ return
+ }
+
+ const index = nav.currentEntry?.index ?? 0
+ if (index > 0) router.back()
+ else router.push(settingsHref)
+ }}/>
+}
diff --git a/src/packages/ce/src/role/pages/RolesPage.tsx b/src/packages/ce/src/role/pages/RolesPage.tsx
deleted file mode 100644
index 4e110b45..00000000
--- a/src/packages/ce/src/role/pages/RolesPage.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-"use client"
-
-import React from "react";
-import {RolesView} from "@edition/role/views/RolesView";
-
-export const RolesPage: React.FC = () => {
- return
-}
\ No newline at end of file
diff --git a/src/packages/ce/src/role/views/RoleDeleteView.tsx b/src/packages/ce/src/role/views/RoleDeleteView.tsx
index 85d8db88..d6fdece6 100644
--- a/src/packages/ce/src/role/views/RoleDeleteView.tsx
+++ b/src/packages/ce/src/role/views/RoleDeleteView.tsx
@@ -2,12 +2,12 @@
import React from "react";
import {useParams, useRouter} from "next/navigation";
-import {Button, Card, Flex, Spacing, Text, useService} from "@code0-tech/pictor";
+import {Button, DialogClose, Spacing, Text, useService} from "@code0-tech/pictor";
import {RoleService} from "@edition/role/services/Role.service";
import {MemberService} from "@edition/member/services/Member.service";
import type {NamespaceRole} from "@code0-tech/sagittarius-graphql-types";
import {TabContent} from "@code0-tech/pictor/dist/components/tab/Tab";
-import {addIslandSuccessNotification} from "@code0-tech/pictor/dist/components/island/Island.hook";
+import {toast} from "@code0-tech/pictor/dist/components/toast/Toast";
export const RoleDeleteView: React.FC = () => {
@@ -28,32 +28,23 @@ export const RoleDeleteView: React.FC = () => {
}).then(payload => {
if ((payload?.errors?.length ?? 0) <= 0) {
memberService.removeRoleFromMembers(roleId)
- addIslandSuccessNotification({
- message: "Deleted role"
- })
+ toast({title: "Deleted role", color: "success"})
}
- router.push(`/namespace/${namespaceIndex}/roles`)
+ router.push(`/namespace/${namespaceIndex}/settings`)
})
})
}, [])
return
-
- Delete role
+ Delete role
+
+
+ Permanently delete this role and unassign it from all members. This action cannot be undone.
-
-
-
-
-
- This will delete the role and cannot be undone.
-
-
-
- Delete role
-
-
-
+
+
+ Delete role
+
}
\ No newline at end of file
diff --git a/src/packages/ce/src/role/views/RoleGeneralAdjustmentView.tsx b/src/packages/ce/src/role/views/RoleGeneralAdjustmentView.tsx
index 6f2d6065..8b2b6162 100644
--- a/src/packages/ce/src/role/views/RoleGeneralAdjustmentView.tsx
+++ b/src/packages/ce/src/role/views/RoleGeneralAdjustmentView.tsx
@@ -1,13 +1,12 @@
"use client"
import React from "react";
-import {Button, Card, Flex, Spacing, Text, TextInput, useForm, useService, useStore} from "@code0-tech/pictor";
-import CardSection from "@code0-tech/pictor/dist/components/card/CardSection";
+import {Button, Flex, Spacing, Text, TextInput, useForm, useService, useStore} from "@code0-tech/pictor";
import {TabContent} from "@code0-tech/pictor/dist/components/tab/Tab";
import {RoleService} from "@edition/role/services/Role.service";
import type {Namespace, NamespaceRole} from "@code0-tech/sagittarius-graphql-types";
import {useParams} from "next/navigation";
-import {addIslandSuccessNotification} from "@code0-tech/pictor/dist/components/island/Island.hook";
+import {toast} from "@code0-tech/pictor/dist/components/toast/Toast";
export const RoleGeneralAdjustmentView: React.FC = () => {
@@ -50,9 +49,7 @@ export const RoleGeneralAdjustmentView: React.FC = () => {
name: values.name!
}).then(payload => {
if ((payload?.errors?.length ?? 0) <= 0) {
- addIslandSuccessNotification({
- message: "Updated role name"
- })
+ toast({title: "Updated role name", color: "success"})
}
})
})
@@ -61,21 +58,17 @@ export const RoleGeneralAdjustmentView: React.FC = () => {
return
-
- General
-
-
- Save changes
-
+ General
+ Save changes
-
-
-
-
- Name
-
-
-
-
+
+
+ The name and general settings for this role, visible to every member it is assigned to.
+
+
+
}
\ No newline at end of file
diff --git a/src/packages/ce/src/role/views/RolePermissionView.tsx b/src/packages/ce/src/role/views/RolePermissionView.tsx
index e6bceef4..24426f71 100644
--- a/src/packages/ce/src/role/views/RolePermissionView.tsx
+++ b/src/packages/ce/src/role/views/RolePermissionView.tsx
@@ -21,7 +21,8 @@ import {TabContent} from "@code0-tech/pictor/dist/components/tab/Tab";
import {useParams} from "next/navigation";
import {RoleService} from "@edition/role/services/Role.service";
import {RolePermissionComponent} from "@edition/role/components/RolePermissionComponent";
-import {addIslandSuccessNotification} from "@code0-tech/pictor/dist/components/island/Island.hook";
+import {toast} from "@code0-tech/pictor/dist/components/toast/Toast";
+import BorderBeam from "border-beam";
type Permission = {
label: string
@@ -219,9 +220,7 @@ export const RolePermissionView: React.FC = () => {
abilities: updatedAbilities
}).then(payload => {
if ((payload?.errors?.length ?? 0) <= 0) {
- addIslandSuccessNotification({
- message: "Updated role permissions"
- })
+ toast({title: "Updated role permissions", color: "success"})
}
})
})
@@ -230,14 +229,16 @@ export const RolePermissionView: React.FC = () => {
return
-
- Select from role templates
-
-
- Save changes
-
+ Permissions
+ Save changes
-
+
+
+ Assign permissions to this role. Members with this role inherit every permission you enable here.
+
+
+ Role templates
+
{React.useMemo(() => {
return
{permissionTemplates.map(permissionTemplate => {
@@ -253,32 +254,30 @@ export const RolePermissionView: React.FC = () => {
const isSelected = templateAbilities.length === roleAbilities.length
&& templateAbilities.every(a => new Set(roleAbilities).has(a));
+ const card =
+
+ {permissionTemplate.name}
+
+
+ enabled)
+ .map(([ability, _]) => ability as NamespaceRoleAbility)}/>
+
+ setInitialValues(permissionTemplate.abilities)}>Select
+ template
+
- return
-
- {rolesList}
-
+
+
+ Manage roles that you belong to. You can create new roles and switch between them.
+
+
+ {
+ const roleIndex = role?.id?.match(/NamespaceRole\/(\d+)$/)?.[1]
+ router.push(`/namespace/${namespaceIndex}/roles/${roleIndex}/settings`)
+ }}/>
>
-
-}
\ No newline at end of file
+}
diff --git a/src/packages/ce/src/runtime/components/RuntimeCreateDialogComponent.tsx b/src/packages/ce/src/runtime/components/RuntimeCreateDialogComponent.tsx
new file mode 100644
index 00000000..b8dc34d5
--- /dev/null
+++ b/src/packages/ce/src/runtime/components/RuntimeCreateDialogComponent.tsx
@@ -0,0 +1,112 @@
+"use client"
+
+import React, {startTransition} from "react";
+import {
+ Button,
+ Spacing,
+ TextInput,
+ toast,
+ useForm,
+ useService
+} from "@code0-tech/pictor";
+import {RuntimeService} from "@edition/runtime/services/Runtime.service";
+import {Namespace} from "@code0-tech/sagittarius-graphql-types";
+import {InputDialog} from "@core/components/InputDialog";
+
+export interface RuntimeCreateDialogComponentProps {
+ open?: boolean
+ onOpenChange?: (open: boolean) => void
+ namespaceId?: Namespace['id']
+}
+
+export const RuntimeCreateDialogComponent: React.FC = ({open, onOpenChange, namespaceId}) => {
+
+ const runtimeService = useService(RuntimeService)
+ const [token, setToken] = React.useState(undefined)
+
+ const initialValues = React.useMemo(
+ () => ({
+ name: "",
+ description: "",
+ }),
+ []
+ )
+
+ const [inputs, validate] = useForm({
+ useInitialValidation: false,
+ initialValues,
+ validate: {
+ name: (value) => {
+ if (!value) return "Name is required"
+ if (value.length < 3) return "Name needs to be at least 3 characters"
+ if (value.length > 50) return "Name needs to be less than 50 characters"
+ return null
+ },
+ description: (value) => {
+ if (!value) return "Description is required"
+ if ((value as string).length > 50) return "Description needs to be less than 50 characters"
+ return null
+ }
+ },
+ onSubmit: (values) => {
+ startTransition(() => {
+ runtimeService.runtimeCreate({
+ name: values.name as unknown as string,
+ description: values.description as unknown as string,
+ ...(namespaceId ? {namespaceId} : {})
+ }).then(payload => {
+ if ((payload?.errors?.length ?? 0) <= 0) {
+ if (payload?.runtime?.token) {
+ setToken(payload.runtime.token)
+ toast({title: "Created runtime", color: "success"})
+ } else {
+ toast({
+ title: "The runtime was created but no token was provided.",
+ color: "error",
+ })
+ onOpenChange?.(false)
+ }
+ }
+ })
+ })
+ }
+ })
+
+ return onOpenChange?.(open)}>
+ {!token ? (
+ <>
+
+
+
+ >
+ ) : (
+
+ )}
+
+ {!token ? (
+ Create runtime
+ ) : (
+ onOpenChange?.(false)}>Done
+ )}
+
+}
diff --git a/src/packages/ce/src/runtime/components/RuntimeDataTableFilterInputComponent.tsx b/src/packages/ce/src/runtime/components/RuntimeDataTableFilterInputComponent.tsx
deleted file mode 100644
index e41cdbda..00000000
--- a/src/packages/ce/src/runtime/components/RuntimeDataTableFilterInputComponent.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import React from "react";
-import {
- DataTableFilterInput,
- DataTableFilterSuggestionMenu,
- MenuCheckboxItem,
- useService,
- useStore
-} from "@code0-tech/pictor";
-import {IconCheck} from "@tabler/icons-react";
-import {Namespace, Runtime} from "@code0-tech/sagittarius-graphql-types";
-import {DataTableFilterProps} from "@code0-tech/pictor/dist/components/data-table/DataTable";
-import {RuntimeService} from "@edition/runtime/services/Runtime.service";
-
-export interface RoleDataTableFilterInputComponentProps {
- namespaceId?: Namespace['id']
- onChange: (filter: DataTableFilterProps) => void
- preFilter?: (project: Runtime, index: number) => boolean
-}
-
-export const RuntimeDataTableFilterInputComponent: React.FC = (props) => {
-
- const {namespaceId, onChange, preFilter = () => true} = props
-
- const runtimeService = useService(RuntimeService)
- const runtimeStore = useStore(RuntimeService)
-
- const runtimes = React.useMemo(
- () => runtimeService.values({namespaceId}).filter(preFilter),
- [runtimeStore, namespaceId]
- )
-
- return {
-
- const split = currentValue.split(",").map(s => s.trim()).filter(Boolean)
-
- return
- {runtimes.map(runtime => {
-
- const isChecked = split.includes(runtime?.name!)
-
- return {
- e.preventDefault();
- e.stopPropagation();
- const updated = isChecked
- ? split.filter(name => name !== runtime?.name)
- : [...split, runtime?.name];
- applySuggestion(updated.join(","), true);
- }}>
- {isChecked ? : }
- {runtime?.name}
-
- })}
-
- }
- }, {
- token: "Status",
- key: "status",
- operators: ["isOneOf"],
- suggestion: (context, operator, currentValue, applySuggestion) => {
-
- const split = currentValue.split(",").map(s => s.trim()).filter(Boolean)
-
- return
- {["CONNECTED", "DISCONNECTED"].map(status => {
-
- const isChecked = split.includes(status)
-
- return {
- e.preventDefault();
- e.stopPropagation();
- const updated = isChecked
- ? split.filter(name => name !== status)
- : [...split, status];
- applySuggestion(updated.join(","), true);
- }}>
- {isChecked ? : }
- {status}
-
- })}
-
- }
- }
- ]}/>
-}
\ No newline at end of file
diff --git a/src/packages/ce/src/runtime/components/RuntimeSettingsDialogComponent.tsx b/src/packages/ce/src/runtime/components/RuntimeSettingsDialogComponent.tsx
new file mode 100644
index 00000000..c3cd04a7
--- /dev/null
+++ b/src/packages/ce/src/runtime/components/RuntimeSettingsDialogComponent.tsx
@@ -0,0 +1,175 @@
+"use client"
+
+import React from "react";
+import {Button, DialogClose, Flex, Spacing, Text, TextInput, useForm, useService, useStore} from "@code0-tech/pictor";
+import {TabContent, TabList, TabTrigger} from "@code0-tech/pictor/dist/components/tab/Tab";
+import {Runtime} from "@code0-tech/sagittarius-graphql-types";
+import {RuntimeService} from "@edition/runtime/services/Runtime.service";
+import {useRouter} from "next/navigation";
+import {toast} from "@code0-tech/pictor/dist/components/toast/Toast";
+import {IconBackground, IconPlugConnected, IconTrash} from "@tabler/icons-react";
+import {SettingDialog} from "@core/components/SettingDialog";
+
+export interface RuntimeSettingsDialogComponentProps {
+ runtimeId?: Runtime['id']
+ namespaceId?: number
+ open?: boolean
+ onOpenChange?: (open: boolean) => void
+}
+
+export const RuntimeSettingsDialogComponent: React.FC = (props) => {
+
+ const {runtimeId, namespaceId, open, onOpenChange} = props
+
+ const runtimeService = useService(RuntimeService)
+ const runtimeStore = useStore(RuntimeService)
+ const [, startTransition] = React.useTransition()
+ const [token, setToken] = React.useState(undefined)
+ const router = useRouter()
+
+ const runtime = React.useMemo(
+ () => runtimeService.getById(runtimeId),
+ [runtimeStore, runtimeId]
+ )
+
+ const initialValues = React.useMemo(() => ({
+ name: runtime?.name,
+ description: runtime?.description,
+ }), [runtime])
+
+ const [inputs, validate] = useForm({
+ useInitialValidation: false,
+ initialValues: initialValues,
+ validate: {
+ name: (value) => {
+ if (!value) return "Name is required"
+ return null
+ },
+ description: (value) => {
+ if (!value) return "Description is required"
+ return null
+ }
+ },
+ onSubmit: (values) => {
+ if (!runtime) return
+ startTransition(() => {
+ runtimeService.runtimeUpdate({
+ name: values.name as unknown as string,
+ description: values.description as unknown as string,
+ runtimeId: runtime.id!
+ }).then(payload => {
+ if ((payload?.errors?.length ?? 0) <= 0) {
+ toast({title: "Updated runtime", color: "success"})
+ }
+ })
+ })
+ }
+ })
+
+ const deleteRuntime = React.useCallback(() => {
+ if (!runtime) return
+ startTransition(() => {
+ runtimeService.runtimeDelete({
+ runtimeId: runtime.id!
+ }).then(payload => {
+ if ((payload?.errors?.length ?? 0) <= 0) {
+ toast({title: "Deleted runtime", color: "success"})
+ }
+ onOpenChange?.(false)
+ router.push(namespaceId ? `/namespace/${namespaceId}/runtimes` : "/runtimes")
+ })
+ })
+ }, [runtime, namespaceId, onOpenChange, router])
+
+ const rotateToken = React.useCallback(() => {
+ if (!runtime) return
+ startTransition(() => {
+ runtimeService.runtimeRotateToken({
+ runtimeId: runtime.id!
+ }).then(payload => {
+ if ((payload?.errors?.length ?? 0) <= 0) {
+ if (payload?.runtime?.token) {
+ setToken(payload.runtime.token)
+ toast({title: "Rotated runtime token", color: "success"})
+ }
+ }
+ })
+ })
+ }, [runtime])
+
+ return onOpenChange?.(open)}
+ title={`Settings of ${runtime?.name ?? ""}`}
+ description={"General settings and restrictions for your runtime. These settings affect how the runtime connects to and behaves within your Sculptor application."}
+ trigger={
+
+
+
+ General
+
+
+
+
+
+ How to connect
+
+
+
+
+
+ Delete
+
+
+ }>
+
+
+ General
+
+ Save changes
+
+
+
+
+ General configuration for your runtime, including the name and description used to recognise it.
+
+
+
+
+
+
+
+ How to connect
+
+
+ Connect your runtime to Sculptor using an access token. Keep it secret and rotate it when exposed.
+
+
+ {token ? (
+
+ ) : (
+
+ Generate new token
+
+ )}
+
+
+ Delete runtime
+
+
+ Permanently remove this runtime from your application. This action cannot be undone.
+
+
+
+ Delete runtime
+
+
+
+}
diff --git a/src/packages/ce/src/runtime/pages/RuntimeAssignPage.tsx b/src/packages/ce/src/runtime/pages/RuntimeAssignPage.tsx
deleted file mode 100644
index 884e4c1c..00000000
--- a/src/packages/ce/src/runtime/pages/RuntimeAssignPage.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-"use client"
-
-import React, {startTransition} from "react";
-import {useParams} from "next/navigation";
-import {
- Avatar, Badge,
- Button,
- Flex, hashToColor,
- Menu,
- MenuContent,
- MenuItem,
- MenuPortal, MenuSeparator,
- MenuTrigger, Spacing,
- Text,
- useService,
- useStore
-} from "@code0-tech/pictor";
-import {RuntimeService} from "@edition/runtime/services/Runtime.service";
-import {ProjectService} from "@edition/project/services/Project.service";
-import {Runtime} from "@code0-tech/sagittarius-graphql-types";
-import {formatDistanceToNow} from "date-fns";
-import {RuntimeProjectDataTableComponent} from "@edition/runtime/components/RuntimeProjectDataTableComponent";
-
-export const RuntimeAssignPage: React.FC = () => {
-
- const params = useParams()
- const runtimeService = useService(RuntimeService)
- const runtimeStore = useStore(RuntimeService)
- const projectService = useService(ProjectService)
- const projectStore = useStore(ProjectService)
-
- const projectId = params.projectId as any as number
- const namespaceId = params.namespaceId as any as number
-
- const availableRuntimes = React.useMemo(
- () => {
- const globalRuntimes = runtimeService.values().filter(runtime => !runtime?.namespace?.id)
- const namespaceRuntimes = runtimeService.values({namespaceId: `gid://sagittarius/Namespace/${namespaceId}`})
- return [...globalRuntimes, ...namespaceRuntimes]
- },
- [runtimeStore, projectId]
- )
-
- const project = React.useMemo(
- () => projectService.getById(`gid://sagittarius/NamespaceProject/${projectId}`),
- [projectStore, projectId]
- )
-
- const assignRuntime = React.useCallback((runtimeId: Runtime['id']) => {
- const runtimeIds = project?.runtimes?.nodes?.map(r => r?.id!) ?? []
-
- if (!runtimeId) return
- if (runtimeIds.includes(runtimeId)) return
-
- startTransition(() => {
- projectService.projectAssignRuntimes({
- runtimeIds: [...runtimeIds, runtimeId],
- namespaceProjectId: `gid://sagittarius/NamespaceProject/${projectId}`
- })
- })
- }, [projectService, projectId, project])
-
- return
-
- Assigned runtimes
-
-
-
-
-
-}
\ No newline at end of file
diff --git a/src/packages/ce/src/runtime/pages/RuntimeCreatePage.tsx b/src/packages/ce/src/runtime/pages/RuntimeCreatePage.tsx
index 1e32e128..e91ea522 100644
--- a/src/packages/ce/src/runtime/pages/RuntimeCreatePage.tsx
+++ b/src/packages/ce/src/runtime/pages/RuntimeCreatePage.tsx
@@ -1,144 +1,45 @@
"use client"
import React from "react";
-import {
- Button,
- Col,
- Flex,
- Spacing,
- Text,
- TextInput,
- toast,
- useForm,
- useService,
- useStore
-} from "@code0-tech/pictor";
-import Link from "next/link";
+import {useService, useStore} from "@code0-tech/pictor";
import {notFound, useParams, useRouter} from "next/navigation";
-import {RuntimeService} from "@edition/runtime/services/Runtime.service";
+import {RuntimeCreateDialogComponent} from "@edition/runtime/components/RuntimeCreateDialogComponent";
import {UserService} from "@edition/user/services/User.service";
-import {NamespaceService} from "@edition/namespace/services/Namespace.service";
import {useUserSession} from "@edition/user/hooks/User.session.hook";
export const RuntimeCreatePage: React.FC = () => {
const params = useParams()
+ const router = useRouter()
const namespaceIndex = params.namespaceId as any as number
- const runtimeService = useService(RuntimeService)
- const [, startTransition] = React.useTransition()
- const [token, setToken] = React.useState(undefined)
- const router = useRouter()
const currentSession = useUserSession()
- const userStore = useStore(UserService)
const userService = useService(UserService)
- const namespaceService = useService(NamespaceService)
- const namespaceStore = useStore(NamespaceService)
-
- const currentUser = React.useMemo(() => userService.getById(currentSession?.user?.id), [userStore, currentSession])
- const namespace = React.useMemo(() => namespaceService.getById(`gid://sagittarius/Namespace/${namespaceIndex}`), [namespaceStore, namespaceIndex])
+ const userStore = useStore(UserService)
+ const currentUser = React.useMemo(
+ () => userService.getById(currentSession?.user?.id),
+ [userStore, currentSession]
+ )
if (!namespaceIndex && currentUser && !currentUser.admin) {
notFound()
}
- //TODO: user abilities for runtime creation within namespace
- // if (namespace.)
+ const runtimesHref = namespaceIndex ? `/namespace/${namespaceIndex}/runtimes` : "/runtimes"
+
+ return {
+ if (open) return
- const [inputs, validate] = useForm({
- useInitialValidation: false,
- initialValues: {
- name: "",
- description: "",
- },
- validate: {
- name: (value) => {
- if (!value) return "Name is required"
- if (value.length < 3) return "Name needs to be at least 3 characters"
- if (value.length > 50) return "Name needs to be less than 50 characters"
- return null
- },
- description: (value) => {
- if (!value) return "Description is required"
- if ((value as string).length > 50) return "Description needs to be less than 50 characters"
- return null
- }
- },
- onSubmit: (values) => {
- startTransition(() => {
- runtimeService.runtimeCreate({
- name: values.name as unknown as string,
- description: values.description as unknown as string,
- ...(namespaceIndex ? {namespaceId: `gid://sagittarius/Namespace/${namespaceIndex}`} : {})
- }).then(payload => {
- if ((payload?.errors?.length ?? 0) <= 0) {
- if (payload?.runtime?.token) {
- if (!token) setToken(payload.runtime.token)
- } else {
- toast({
- title: "The runtime was created but no token was provided.",
- color: "error",
- dismissible: true,
- })
- router.push(namespaceIndex ? `/namespace/${namespaceIndex}/runtimes` : "/runtimes")
- }
- }
- })
- })
- }
- })
+ const nav = (window as unknown as { navigation?: { entries(): { url: string }[], currentEntry?: { index: number } } }).navigation
+ if (!nav?.entries) {
+ router.back()
+ return
+ }
- return
-
-
-
- {!token ? "Create new runtime" : "Runtime created successfully"}
-
-
-
- Runtimes are shared runtimes that can be used across multiple organizations.
-
-
- {!token ? (
- <>
-
-
-
-
- >
- ) : (
- <>
-
-
- >
- )}
-
-
-
- Go back to runtimes
-
-
- {!token ? (
-
- Create runtime
-
- ) : null}
-
-
-
-
-}
\ No newline at end of file
+ const index = nav.currentEntry?.index ?? 0
+ if (index > 0) router.back()
+ else router.push(runtimesHref)
+ }}/>
+}
diff --git a/src/packages/ce/src/runtime/pages/RuntimeSettingsPage.tsx b/src/packages/ce/src/runtime/pages/RuntimeSettingsPage.tsx
index ea3195b3..ffcb1533 100644
--- a/src/packages/ce/src/runtime/pages/RuntimeSettingsPage.tsx
+++ b/src/packages/ce/src/runtime/pages/RuntimeSettingsPage.tsx
@@ -1,202 +1,48 @@
"use client"
import React from "react";
-import {Button, Card, Flex, Spacing, Text, TextInput, useForm, useService, useStore} from "@code0-tech/pictor";
-import {RuntimeService} from "@edition/runtime/services/Runtime.service";
+import {useService, useStore} from "@code0-tech/pictor";
import {notFound, useParams, useRouter} from "next/navigation";
-import {Tab, TabContent, TabList, TabTrigger} from "@code0-tech/pictor/dist/components/tab/Tab";
-import CardSection from "@code0-tech/pictor/dist/components/card/CardSection";
-import {
- ResizableHandle,
- ResizablePanel,
- ResizablePanelGroup
-} from "@code0-tech/pictor/dist/components/resizable/Resizable";
-import {SidebarComponent} from "@core/components/SidebarComponent";
-import {addIslandSuccessNotification} from "@code0-tech/pictor/dist/components/island/Island.hook";
+import {Runtime} from "@code0-tech/sagittarius-graphql-types";
+import {RuntimeService} from "@edition/runtime/services/Runtime.service";
+import {RuntimeSettingsDialogComponent} from "@edition/runtime/components/RuntimeSettingsDialogComponent";
export const RuntimeSettingsPage: React.FC = () => {
- //TODO: add ability check for runtime settings access for every settings tab
-
const params = useParams()
- const namespaceId = params.namespaceId as any as number
- const runtimeId = params.runtimeId as any as number
+ const router = useRouter()
+ const namespaceIndex = params.namespaceId as any as number
+ const runtimeIndex = params.runtimeId as any as number
const runtimeService = useService(RuntimeService)
const runtimeStore = useStore(RuntimeService)
- const [, startTransition] = React.useTransition()
- const [token, setToken] = React.useState(undefined)
- const router = useRouter()
- const runtime = React.useMemo(() => runtimeService.getById(`gid://sagittarius/Runtime/${runtimeId}`), [runtimeStore, params])
+ const runtimeId = `gid://sagittarius/Runtime/${runtimeIndex}` as Runtime['id']
+ const runtime = React.useMemo(
+ () => runtimeService.getById(runtimeId),
+ [runtimeStore, runtimeId]
+ )
- if (runtime?.userAbilities && (!runtime?.userAbilities?.updateRuntime || !runtime.userAbilities.deleteRuntime || !runtime.userAbilities.rotateRuntimeToken)) {
+ if (runtime?.userAbilities && (!runtime.userAbilities.updateRuntime || !runtime.userAbilities.deleteRuntime || !runtime.userAbilities.rotateRuntimeToken)) {
notFound()
}
- const initialValues = React.useMemo(() => ({
- name: runtime?.name,
- description: runtime?.description,
- }), [runtime])
-
- const [inputs, validate] = useForm({
- useInitialValidation: false,
- initialValues: initialValues,
- validate: {
- name: (value) => {
- if (!value) return "Name is required"
- return null
- },
- description: (value) => {
- if (!value) return "Description is required"
- return null
- }
- },
- onSubmit: (values) => {
- startTransition(() => {
- runtimeService.runtimeUpdate({
- name: values.name as unknown as string,
- description: values.description as unknown as string,
- runtimeId: runtime?.id!!
- }).then(payload => {
- if ((payload?.errors?.length ?? 0) <= 0) {
- addIslandSuccessNotification({
- message: "Updated runtime"
- })
- }
- })
- })
- }
- })
-
- const deleteRuntime = React.useCallback(() => {
- startTransition(() => {
- runtimeService.runtimeDelete({
- runtimeId: runtime?.id!!
- }).then(payload => {
- if ((payload?.errors?.length ?? 0) <= 0) {
- addIslandSuccessNotification({
- message: "Deleted runtime"
- })
- }
- router.push(namespaceId ? `/namespace/${namespaceId}/runtimes` : "/runtimes")
- })
- })
- }, [runtime, router])
-
- const rotateToken = React.useCallback(() => {
- startTransition(() => {
- runtimeService.runtimeRotateToken({
- runtimeId: runtime?.id!!
- }).then(payload => {
- if ((payload?.errors?.length ?? 0) <= 0) {
- if (payload?.runtime?.token) {
- setToken(payload.runtime.token)
- addIslandSuccessNotification({
- message: "Rotated runtime token"
- })
- }
- }
- })
- })
- }, [runtime])
-
-
- return
-
-
-
-
-
- General
-
-
-
-
- How to connect
-
-
-
-
- Delete
-
-
-
-
-
-
- <>
-
-
- General
-
- Save changes
-
-
-
-
-
-
- Name
-
-
-
-
-
- Description
-
-
-
-
-
-
- How to connect
-
-
-
-
-
- Access token
- Is used to connect the runtime to our
- service
-
- {token ? (
-
- ) : (
-
- Generate new token
-
- )}
-
-
-
-
-
-
-
- Delete
-
-
-
-
-
-
- This will delete the runtime and cannot be undone.
-
-
-
- Delete runtime
-
-
-
-
- >
-
-
-
-}
\ No newline at end of file
+ const runtimesHref = namespaceIndex ? `/namespace/${namespaceIndex}/runtimes` : "/runtimes"
+
+ return {
+ if (open) return
+
+ const nav = (window as unknown as { navigation?: { entries(): { url: string }[], currentEntry?: { index: number } } }).navigation
+ if (!nav?.entries) {
+ router.back()
+ return
+ }
+
+ const index = nav.currentEntry?.index ?? 0
+ if (index > 0) router.back()
+ else router.push(runtimesHref)
+ }}/>
+}
diff --git a/src/packages/ce/src/runtime/pages/RuntimesPage.tsx b/src/packages/ce/src/runtime/pages/RuntimesPage.tsx
deleted file mode 100644
index aff01cab..00000000
--- a/src/packages/ce/src/runtime/pages/RuntimesPage.tsx
+++ /dev/null
@@ -1,145 +0,0 @@
-"use client"
-
-import React from "react";
-import {
- Button,
- Flex,
- Menu,
- MenuCheckboxItem,
- MenuContent,
- MenuPortal,
- MenuTrigger,
- Spacing,
- Text,
- useService,
- useStore
-} from "@code0-tech/pictor";
-import Link from "next/link";
-import {notFound, useParams, useRouter} from "next/navigation";
-import {UserService} from "@edition/user/services/User.service";
-import {RuntimeDataTableComponent} from "@edition/runtime/components/RuntimeDataTableComponent";
-import {DataTableFilterProps, DataTableSortProps} from "@code0-tech/pictor/dist/components/data-table/DataTable";
-import {RuntimeDataTableFilterInputComponent} from "@edition/runtime/components/RuntimeDataTableFilterInputComponent";
-import {IconMinus, IconSortAscending, IconSortDescending} from "@tabler/icons-react";
-import {ButtonGroup} from "@code0-tech/pictor/dist/components/button-group/ButtonGroup";
-import {useUserSession} from "@edition/user/hooks/User.session.hook";
-
-export const RuntimesPage: React.FC = () => {
-
- const params = useParams()
- const currentSession = useUserSession()
- const userStore = useStore(UserService)
- const userService = useService(UserService)
- const currentUser = React.useMemo(() => userService.getById(currentSession?.user?.id), [userStore, currentSession])
-
- const [filter, setFilter] = React.useState({})
- const [sort, setSort] = React.useState({})
-
- const namespaceIndex = params.namespaceId as any as number
-
- if (!namespaceIndex && currentUser && !currentUser.admin) {
- notFound()
- }
-
- const router = useRouter()
- return
-
-
-
- Runtimes
-
-
- Manage runtimes that you have access to. Runtimes are the environments where your applications run.
-
-
-
-
- Create
-
-
-
-
-
-