@vertesia/ui 0.72.0 → 0.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/lib/esm/features/facets/RunsFacetsNav.js +15 -5
  2. package/lib/esm/features/facets/RunsFacetsNav.js.map +1 -1
  3. package/lib/esm/shell/apps/AppInstallationProvider.js +13 -0
  4. package/lib/esm/shell/apps/AppInstallationProvider.js.map +1 -0
  5. package/lib/esm/shell/apps/AppProjectSelector.js +25 -0
  6. package/lib/esm/shell/apps/AppProjectSelector.js.map +1 -0
  7. package/lib/esm/shell/apps/StandaloneApp.js +60 -0
  8. package/lib/esm/shell/apps/StandaloneApp.js.map +1 -0
  9. package/lib/esm/shell/apps/index.js +3 -1
  10. package/lib/esm/shell/apps/index.js.map +1 -1
  11. package/lib/esm/shell/login/InviteAcceptModal.js +1 -1
  12. package/lib/esm/shell/login/InviteAcceptModal.js.map +1 -1
  13. package/lib/tsconfig.tsbuildinfo +1 -1
  14. package/lib/types/features/facets/RunsFacetsNav.d.ts +1 -0
  15. package/lib/types/features/facets/RunsFacetsNav.d.ts.map +1 -1
  16. package/lib/types/features/facets/VFacetsNav.d.ts +1 -1
  17. package/lib/types/features/facets/VFacetsNav.d.ts.map +1 -1
  18. package/lib/types/shell/apps/AppInstallationProvider.d.ts +12 -0
  19. package/lib/types/shell/apps/AppInstallationProvider.d.ts.map +1 -0
  20. package/lib/types/shell/apps/AppProjectSelector.d.ts +12 -0
  21. package/lib/types/shell/apps/AppProjectSelector.d.ts.map +1 -0
  22. package/lib/types/shell/apps/StandaloneApp.d.ts +23 -0
  23. package/lib/types/shell/apps/StandaloneApp.d.ts.map +1 -0
  24. package/lib/types/shell/apps/index.d.ts +3 -1
  25. package/lib/types/shell/apps/index.d.ts.map +1 -1
  26. package/lib/vertesia-ui-features.js +1 -1
  27. package/lib/vertesia-ui-features.js.map +1 -1
  28. package/lib/vertesia-ui-shell.js +1 -1
  29. package/lib/vertesia-ui-shell.js.map +1 -1
  30. package/package.json +7 -7
  31. package/src/features/facets/RunsFacetsNav.tsx +17 -5
  32. package/src/features/facets/VFacetsNav.tsx +1 -1
  33. package/src/shell/apps/AppInstallationProvider.tsx +22 -0
  34. package/src/shell/apps/AppProjectSelector.tsx +47 -0
  35. package/src/shell/apps/StandaloneApp.tsx +108 -0
  36. package/src/shell/apps/index.ts +3 -1
  37. package/src/shell/login/InviteAcceptModal.tsx +1 -1
  38. package/lib/esm/shell/apps/CheckAppAccess.js +0 -23
  39. package/lib/esm/shell/apps/CheckAppAccess.js.map +0 -1
  40. package/lib/types/shell/apps/CheckAppAccess.d.ts +0 -6
  41. package/lib/types/shell/apps/CheckAppAccess.d.ts.map +0 -1
  42. package/src/shell/apps/CheckAppAccess.tsx +0 -25
@@ -13,6 +13,7 @@ interface RunsFacetsNavProps {
13
13
  environments?: any[];
14
14
  models?: any[];
15
15
  statuses?: any[];
16
+ tags?: any[];
16
17
  finish_reason?: any[];
17
18
  created_by?: any[];
18
19
  };
@@ -35,16 +36,25 @@ export function useRunsFilterGroups(facets: RunsFacetsNavProps['facets']): Filte
35
36
  if (facets.environments) {
36
37
  const environmentFilterGroup = VEnvironmentFacet({
37
38
  buckets: facets.environments || [],
38
- name: 'Environments'
39
+ name: 'environments',
39
40
  });
40
41
  customFilterGroups.push(environmentFilterGroup);
41
42
  }
42
43
 
44
+ // Add tags filter as stringList type (allows custom input)
45
+ const tagsFilterGroup = {
46
+ name: 'tags',
47
+ placeholder: 'Tags',
48
+ type: 'stringList' as const,
49
+ multiple: true
50
+ };
51
+ customFilterGroups.push(tagsFilterGroup);
52
+
43
53
  if (facets.models) {
44
54
  const modelFilterGroup = VStringFacet({
45
55
  search: null as any, // This will be provided by the search context
46
56
  buckets: facets.models || [],
47
- name: 'Model'
57
+ name: 'model'
48
58
  });
49
59
  customFilterGroups.push(modelFilterGroup);
50
60
  }
@@ -53,7 +63,7 @@ export function useRunsFilterGroups(facets: RunsFacetsNavProps['facets']): Filte
53
63
  const statusFilterGroup = VStringFacet({
54
64
  search: null as any, // This will be provided by the search context
55
65
  buckets: facets.statuses || [],
56
- name: 'Status'
66
+ name: 'status'
57
67
  });
58
68
  customFilterGroups.push(statusFilterGroup);
59
69
  }
@@ -105,11 +115,13 @@ export function useRunsFilterGroups(facets: RunsFacetsNavProps['facets']): Filte
105
115
  export function useRunsFilterHandler(search: SearchInterface) {
106
116
  return (newFilters: BaseFilter[]) => {
107
117
  if (newFilters.length === 0) {
108
- search.clearFilters();
118
+ // Clear filters without applying defaults - user wants to remove all filters
119
+ search.clearFilters(true, false);
109
120
  return;
110
121
  }
111
122
 
112
- search.clearFilters(false);
123
+ // Clear all filters first without defaults, then apply new ones
124
+ search.clearFilters(false, false);
113
125
 
114
126
  newFilters.forEach(filter => {
115
127
  if (filter.value && filter.value.length > 0) {
@@ -4,7 +4,7 @@ import { useState } from 'react';
4
4
  export interface SearchInterface {
5
5
  getFilterValue(name: string): any;
6
6
  setFilterValue(name: string, value: any): void;
7
- clearFilters(autoSearch?: boolean): void;
7
+ clearFilters(autoSearch?: boolean, applyDefaults?: boolean): void;
8
8
  search(): Promise<boolean | undefined>;
9
9
  readonly isRunning: boolean;
10
10
  query: Record<string, any>;
@@ -0,0 +1,22 @@
1
+ import { AppInstallationWithManifest } from "@vertesia/common";
2
+ import { createContext, ReactNode, useContext } from "react";
3
+
4
+
5
+ export const AppInstallationContext = createContext<AppInstallationWithManifest | null>(null);
6
+
7
+ export function AppInstallationProvider({ installation, children }: { installation: AppInstallationWithManifest, children: ReactNode }) {
8
+ return (
9
+ <AppInstallationContext.Provider value={installation}>
10
+ {children}
11
+ </AppInstallationContext.Provider>
12
+ )
13
+ }
14
+
15
+ /**
16
+ * Get the current app installation obejct when called in an app context otheriwse returns null
17
+ */
18
+ export function useAppInstallation() {
19
+ return useContext(AppInstallationContext);
20
+ }
21
+
22
+
@@ -0,0 +1,47 @@
1
+ import { ProjectRef, RequireAtLeastOne } from "@vertesia/common";
2
+ import { SelectBox, useFetch } from "@vertesia/ui/core";
3
+ import { useUserSession } from "@vertesia/ui/session";
4
+ import { useState } from "react";
5
+
6
+ interface AppProjectSelectorProps {
7
+ app: RequireAtLeastOne<{ id?: string, name?: string }, 'id' | 'name'>;
8
+ onChange: (value: ProjectRef) => void;
9
+ placeholder?: string;
10
+ }
11
+ export function AppProjectSelector({ app, onChange, placeholder }: AppProjectSelectorProps) {
12
+ const { client, project } = useUserSession();
13
+ const { data: projects, error } = useFetch(() => {
14
+ return client.apps.getAppInstallationProjects(app);
15
+ }, [app.id, app.name])
16
+
17
+ if (error) {
18
+ return <span className='text-red-600'>Error: failed to fetch projects: {error.message}</span>
19
+ }
20
+
21
+ return <SelectProject placeholder={placeholder} initialValue={project?.id} projects={projects || []} onChange={onChange} />
22
+ }
23
+
24
+ interface SelectProjectProps {
25
+ initialValue?: string
26
+ projects: ProjectRef[]
27
+ onChange: (value: ProjectRef) => void
28
+ placeholder?: string;
29
+ }
30
+ function SelectProject({ initialValue, projects, onChange, placeholder = "Select Project" }: Readonly<SelectProjectProps>) {
31
+ const [value, setValue] = useState<ProjectRef | undefined>(() => {
32
+ return initialValue ? projects.find(p => p.id === initialValue) : undefined
33
+ });
34
+ const _onChange = (value: ProjectRef) => {
35
+ setValue(value)
36
+ onChange(value)
37
+ }
38
+ return (
39
+ <SelectBox
40
+ by="id"
41
+ value={value}
42
+ options={projects}
43
+ optionLabel={(option) => option.name}
44
+ placeholder={placeholder}
45
+ onChange={_onChange} />
46
+ )
47
+ }
@@ -0,0 +1,108 @@
1
+ import { AppInstallationWithManifest, ProjectRef } from "@vertesia/common";
2
+ import { Center } from "@vertesia/ui/core";
3
+ import { LastSelectedAccountId_KEY, LastSelectedProjectId_KEY, useUserSession } from "@vertesia/ui/session";
4
+ import { LockIcon } from "lucide-react";
5
+ import { ComponentType, ReactNode, useEffect, useState } from "react";
6
+ import { AppInstallationProvider } from "./AppInstallationProvider";
7
+ import { AppProjectSelector } from "./AppProjectSelector";
8
+
9
+
10
+ interface StandaloneAppProps {
11
+ /**
12
+ * The app name.
13
+ * The name must be the name used to register the app in Vertesia. It will be used to check if thre user has access to the app.
14
+ *
15
+ * Also, this component is providing an AppInfo context that can be retrieved using the useAppInfo() hook.
16
+ */
17
+ name: string;
18
+
19
+ /**
20
+ * A react element to display if the access is denied to the app.
21
+ * If not specified a simple message will be displayed
22
+ */
23
+ AccessDenied?: ComponentType<AccessDeniedMessageProps>;
24
+
25
+ children: ReactNode;
26
+ }
27
+ export function StandaloneApp({ name, AccessDenied = AccessDeniedMessage, children }: StandaloneAppProps) {
28
+ return name ? (
29
+ <StandaloneAppImpl name={name} AccessDenied={AccessDenied}>{children}</StandaloneAppImpl>
30
+ ) : (
31
+ <UnknownAppName />
32
+ )
33
+ }
34
+ export function StandaloneAppImpl({ name, AccessDenied = AccessDeniedMessage, children }: StandaloneAppProps) {
35
+ const { authToken, client } = useUserSession();
36
+ const [installation, setInstallation] = useState<AppInstallationWithManifest | null>(null)
37
+ const [state, setState] = useState<"loading" | "error" | "loaded">("loading");
38
+
39
+ useEffect(() => {
40
+ if (!authToken) {
41
+ setState("loading");
42
+ } else {
43
+ const isAppVisible = authToken.apps.includes(name);
44
+ if (isAppVisible) {
45
+ client.apps.getAppInstallationByName(name).then(inst => {
46
+ if (!inst) {
47
+ console.log(`App ${name} not found!`);
48
+ setState("error");
49
+ } else {
50
+ setState("loaded");
51
+ setInstallation(inst);
52
+ }
53
+ });
54
+ } else {
55
+ setState("error");
56
+ }
57
+ }
58
+ }, [name, authToken]);
59
+
60
+ if (state === "loading") {
61
+ return null;
62
+ } else if (state === "error") {
63
+ return <AccessDenied name={name} />
64
+ } else if (installation) {
65
+ return <AppInstallationProvider installation={installation}>
66
+ {children}
67
+ </AppInstallationProvider>
68
+ }
69
+ }
70
+
71
+ interface AccessDeniedMessageProps {
72
+ name: string;
73
+ }
74
+ function AccessDeniedMessage({ name }: AccessDeniedMessageProps) {
75
+ const { project } = useUserSession();
76
+ const onChange = (project: ProjectRef) => {
77
+ localStorage.setItem(LastSelectedAccountId_KEY, project.account);
78
+ localStorage.setItem(LastSelectedProjectId_KEY + '-' + project.account, project.id);
79
+ window.location.reload();
80
+ }
81
+ return (
82
+ <Center className="pt-10 flex flex-col items-center text-center text-gray-700">
83
+ <LockIcon className="w-10 h-10 mb-4 text-gray-500" />
84
+ <div className="text-xl font-semibold">Access Denied</div>
85
+ <div className="mt-2 text-sm text-gray-500">
86
+ You don&apos;t have permission to view the <span className="font-semibold">{name}</span> app in project: <span className="font-semibold">&laquo;{project?.name}&raquo;</span>.
87
+ </div>
88
+ <div className="mt-4">
89
+ <AppProjectSelector app={{ name }} onChange={onChange} />
90
+ </div>
91
+ </Center>
92
+ )
93
+ }
94
+
95
+ function UnknownAppName() {
96
+ return (
97
+ <Center className="pt-10 flex flex-col items-center text-center text-gray-700">
98
+ <LockIcon className="w-10 h-10 mb-4 text-gray-500" />
99
+ <div className="text-xl font-semibold">Application not registered</div>
100
+ <div className="mt-2 text-sm text-gray-500">
101
+ Before starting to code a Vertesia application you must register an application manifest
102
+ in Vertesia Studio then install it in one or more projects.
103
+ <p />
104
+ Then use the created app name as a parameter to <code>&lt;StandaloneApp name=&quot;your-app-name&quot;&gt;</code> in the <code>src/main.ts</code> file.
105
+ </div>
106
+ </Center>
107
+ )
108
+ }
@@ -1 +1,3 @@
1
- export * from "./CheckAppAccess";
1
+ export * from "./AppInstallationProvider";
2
+ export * from "./StandaloneApp";
3
+ export * from "./AppProjectSelector";
@@ -68,7 +68,7 @@ export function InviteAcceptModal() {
68
68
  <div className="w-full font-semibold">{invite.data.account.name ?? invite.data.account}</div>
69
69
  {invite.data.project && <div className="w-full text-base">- {invite.data.project.name}</div>}
70
70
  <div className="text-xs">Role: {invite.data.role}</div>
71
- <div className="text-xs">by {invite.data.invitedBy.name}</div>
71
+ <div className="text-xs">by {invite.data.invited_by.name}</div>
72
72
  </div>
73
73
  <div className="flex flex-col gap-4">
74
74
  <Button size={'xs'} onClick={() => accept(invite)}>Accept</Button> <Button size={'xs'} variant="secondary" onClick={() => reject(invite)}>Reject</Button>
@@ -1,23 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { ErrorBox } from "@vertesia/ui/core";
3
- import { useUserSession } from "@vertesia/ui/session";
4
- import { useEffect, useState } from "react";
5
- export function CheckAppAccess({ name, children }) {
6
- const [hasAccess, setHasAccess] = useState(null);
7
- const { authToken } = useUserSession();
8
- useEffect(() => {
9
- if (authToken) {
10
- setHasAccess(authToken.apps?.includes(name) || false);
11
- }
12
- }, [authToken]);
13
- if (hasAccess == null) {
14
- return null;
15
- }
16
- if (hasAccess) {
17
- return children;
18
- }
19
- else {
20
- return _jsx(ErrorBox, { title: "Forbidden", children: "Access is not granted to this application" });
21
- }
22
- }
23
- //# sourceMappingURL=CheckAppAccess.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CheckAppAccess.js","sourceRoot":"","sources":["../../../../src/shell/apps/CheckAppAccess.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAa,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGvD,MAAM,UAAU,cAAc,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAyC;IACpF,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAiB,IAAI,CAAC,CAAC;IACjE,MAAM,EAAE,SAAS,EAAE,GAAG,cAAc,EAAE,CAAC;IAEvC,SAAS,CAAC,GAAG,EAAE;QACX,IAAI,SAAS,EAAE,CAAC;YACZ,YAAY,CAAC,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC;QAC1D,CAAC;IACL,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAA;IAEf,IAAI,SAAS,IAAI,IAAI,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,IAAI,SAAS,EAAE,CAAC;QACZ,OAAO,QAAQ,CAAC;IACpB,CAAC;SAAM,CAAC;QACJ,OAAO,KAAC,QAAQ,IAAC,KAAK,EAAC,WAAW,0DAAqD,CAAA;IAC3F,CAAC;AACL,CAAC"}
@@ -1,6 +0,0 @@
1
- import { ReactNode } from "react";
2
- export declare function CheckAppAccess({ name, children }: {
3
- name: string;
4
- children: ReactNode;
5
- }): string | number | bigint | boolean | import("react/jsx-runtime").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined;
6
- //# sourceMappingURL=CheckAppAccess.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CheckAppAccess.d.ts","sourceRoot":"","sources":["../../../../src/shell/apps/CheckAppAccess.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAuB,MAAM,OAAO,CAAC;AAGvD,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,2UAmBvF"}
@@ -1,25 +0,0 @@
1
- import { ErrorBox } from "@vertesia/ui/core";
2
- import { useUserSession } from "@vertesia/ui/session";
3
- import { ReactNode, useEffect, useState } from "react";
4
-
5
-
6
- export function CheckAppAccess({ name, children }: { name: string, children: ReactNode }) {
7
- const [hasAccess, setHasAccess] = useState<boolean | null>(null);
8
- const { authToken } = useUserSession();
9
-
10
- useEffect(() => {
11
- if (authToken) {
12
- setHasAccess(authToken.apps?.includes(name) || false);
13
- }
14
- }, [authToken])
15
-
16
- if (hasAccess == null) {
17
- return null;
18
- }
19
-
20
- if (hasAccess) {
21
- return children;
22
- } else {
23
- return <ErrorBox title="Forbidden">Access is not granted to this application</ErrorBox>
24
- }
25
- }