datajunction-ui 0.0.1-rc.9 → 0.0.2-0.dev1

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 (244) hide show
  1. package/.env +2 -0
  2. package/.prettierignore +3 -1
  3. package/Makefile +9 -0
  4. package/cleanup-deps.sh +70 -0
  5. package/dj-logo.svg +10 -0
  6. package/package.json +53 -14
  7. package/public/favicon.ico +0 -0
  8. package/public/index.html +1 -1
  9. package/runit.sh +30 -0
  10. package/runit2.sh +30 -0
  11. package/src/__tests__/reportWebVitals.test.jsx +44 -0
  12. package/src/app/__tests__/__snapshots__/index.test.tsx.snap +5 -109
  13. package/src/app/components/AddNodeDropdown.jsx +44 -0
  14. package/src/app/components/ListGroupItem.jsx +9 -1
  15. package/src/app/components/NamespaceHeader.jsx +4 -13
  16. package/src/app/components/NodeListActions.jsx +69 -0
  17. package/src/app/components/NodeMaterializationDelete.jsx +90 -0
  18. package/src/app/components/NotificationBell.tsx +223 -0
  19. package/src/app/components/QueryInfo.jsx +172 -0
  20. package/src/app/components/Search.jsx +94 -0
  21. package/src/app/components/Tab.jsx +8 -1
  22. package/src/app/components/ToggleSwitch.jsx +20 -0
  23. package/src/app/components/UserMenu.tsx +100 -0
  24. package/src/app/components/__tests__/NodeListActions.test.jsx +94 -0
  25. package/src/app/components/__tests__/NodeMaterializationDelete.test.jsx +263 -0
  26. package/src/app/components/__tests__/NotificationBell.test.tsx +302 -0
  27. package/src/app/components/__tests__/QueryInfo.test.jsx +183 -0
  28. package/src/app/components/__tests__/Search.test.jsx +307 -0
  29. package/src/app/components/__tests__/Tab.test.jsx +27 -0
  30. package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
  31. package/src/app/components/__tests__/UserMenu.test.tsx +241 -0
  32. package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +8 -3
  33. package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -18
  34. package/src/app/components/djgraph/Collapse.jsx +47 -0
  35. package/src/app/components/djgraph/DJNode.jsx +61 -83
  36. package/src/app/components/djgraph/DJNodeColumns.jsx +75 -0
  37. package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
  38. package/src/app/components/djgraph/LayoutFlow.jsx +106 -0
  39. package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
  40. package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
  41. package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
  42. package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +84 -40
  43. package/src/app/components/forms/Action.jsx +8 -0
  44. package/src/app/components/forms/NodeNameField.jsx +64 -0
  45. package/src/app/components/search.css +17 -0
  46. package/src/app/constants.js +2 -0
  47. package/src/app/icons/AddItemIcon.jsx +16 -0
  48. package/src/app/icons/AlertIcon.jsx +33 -0
  49. package/src/app/icons/CollapsedIcon.jsx +15 -0
  50. package/src/app/icons/CommitIcon.jsx +45 -0
  51. package/src/app/icons/DJLogo.jsx +36 -0
  52. package/src/app/icons/DeleteIcon.jsx +21 -0
  53. package/src/app/icons/DiffIcon.jsx +63 -0
  54. package/src/app/icons/EditIcon.jsx +18 -0
  55. package/src/app/icons/ExpandedIcon.jsx +15 -0
  56. package/src/app/icons/EyeIcon.jsx +20 -0
  57. package/src/app/icons/FilterIcon.jsx +7 -0
  58. package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
  59. package/src/app/icons/InvalidIcon.jsx +16 -0
  60. package/src/app/icons/JupyterExportIcon.jsx +25 -0
  61. package/src/app/icons/LoadingIcon.jsx +14 -0
  62. package/src/app/icons/NodeIcon.jsx +49 -0
  63. package/src/app/icons/NotificationIcon.jsx +27 -0
  64. package/src/app/icons/PythonIcon.jsx +14 -0
  65. package/src/app/icons/SettingsIcon.jsx +28 -0
  66. package/src/app/icons/TableIcon.jsx +14 -0
  67. package/src/app/icons/ValidIcon.jsx +16 -0
  68. package/src/app/icons/WrenchIcon.jsx +36 -0
  69. package/src/app/index.tsx +130 -37
  70. package/src/app/pages/AddEditNodePage/AlertMessage.jsx +10 -0
  71. package/src/app/pages/AddEditNodePage/ColumnMetadata.jsx +61 -0
  72. package/src/app/pages/AddEditNodePage/ColumnsMetadataInput.jsx +72 -0
  73. package/src/app/pages/AddEditNodePage/ColumnsSelect.jsx +84 -0
  74. package/src/app/pages/AddEditNodePage/CustomMetadataField.jsx +144 -0
  75. package/src/app/pages/AddEditNodePage/DescriptionField.jsx +17 -0
  76. package/src/app/pages/AddEditNodePage/DisplayNameField.jsx +16 -0
  77. package/src/app/pages/AddEditNodePage/ExperimentationExtension.jsx +338 -0
  78. package/src/app/pages/AddEditNodePage/FormikSelect.jsx +64 -0
  79. package/src/app/pages/AddEditNodePage/FullNameField.jsx +38 -0
  80. package/src/app/pages/AddEditNodePage/Loadable.jsx +20 -0
  81. package/src/app/pages/AddEditNodePage/MetricMetadataFields.jsx +75 -0
  82. package/src/app/pages/AddEditNodePage/MetricQueryField.jsx +71 -0
  83. package/src/app/pages/AddEditNodePage/NamespaceField.jsx +40 -0
  84. package/src/app/pages/AddEditNodePage/NodeModeField.jsx +14 -0
  85. package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +94 -0
  86. package/src/app/pages/AddEditNodePage/OwnersField.jsx +54 -0
  87. package/src/app/pages/AddEditNodePage/RequiredDimensionsSelect.jsx +54 -0
  88. package/src/app/pages/AddEditNodePage/TagsField.jsx +47 -0
  89. package/src/app/pages/AddEditNodePage/UpstreamNodeField.jsx +49 -0
  90. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +110 -0
  91. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +291 -0
  92. package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +75 -0
  93. package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +31 -0
  94. package/src/app/pages/AddEditNodePage/__tests__/NodeQueryField.test.jsx +30 -0
  95. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +54 -0
  96. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +3 -0
  97. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
  98. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +224 -0
  99. package/src/app/pages/AddEditNodePage/index.jsx +545 -0
  100. package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
  101. package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
  102. package/src/app/pages/AddEditTagPage/index.jsx +132 -0
  103. package/src/app/pages/CubeBuilderPage/DimensionsSelect.jsx +152 -0
  104. package/src/app/pages/CubeBuilderPage/Loadable.jsx +16 -0
  105. package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +75 -0
  106. package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +373 -0
  107. package/src/app/pages/CubeBuilderPage/index.jsx +291 -0
  108. package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
  109. package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
  110. package/src/app/pages/LoginPage/__tests__/index.test.jsx +97 -0
  111. package/src/app/pages/LoginPage/assets/sign-in-with-github.png +0 -0
  112. package/src/app/pages/LoginPage/assets/sign-in-with-google.png +0 -0
  113. package/src/app/pages/LoginPage/index.jsx +17 -0
  114. package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +85 -0
  115. package/src/app/pages/NamespacePage/Explorer.jsx +232 -0
  116. package/src/app/pages/NamespacePage/FieldControl.jsx +21 -0
  117. package/src/app/pages/NamespacePage/NodeModeSelect.jsx +27 -0
  118. package/src/app/pages/NamespacePage/NodeTypeSelect.jsx +30 -0
  119. package/src/app/pages/NamespacePage/TagSelect.jsx +44 -0
  120. package/src/app/pages/NamespacePage/UserSelect.jsx +47 -0
  121. package/src/app/pages/NamespacePage/__tests__/AddNamespacePopover.test.jsx +283 -0
  122. package/src/app/pages/NamespacePage/__tests__/index.test.jsx +331 -0
  123. package/src/app/pages/NamespacePage/index.jsx +356 -42
  124. package/src/app/pages/NodePage/AddBackfillPopover.jsx +165 -0
  125. package/src/app/pages/NodePage/AddComplexDimensionLinkPopover.jsx +367 -0
  126. package/src/app/pages/NodePage/AddMaterializationPopover.jsx +222 -0
  127. package/src/app/pages/NodePage/AvailabilityStateBlock.jsx +67 -0
  128. package/src/app/pages/NodePage/ClientCodePopover.jsx +94 -0
  129. package/src/app/pages/NodePage/DimensionFilter.jsx +86 -0
  130. package/src/app/pages/NodePage/EditColumnDescriptionPopover.jsx +116 -0
  131. package/src/app/pages/NodePage/EditColumnPopover.jsx +116 -0
  132. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +164 -0
  133. package/src/app/pages/NodePage/ManageDimensionLinksDialog.jsx +526 -0
  134. package/src/app/pages/NodePage/MaterializationConfigField.jsx +60 -0
  135. package/src/app/pages/NodePage/NodeColumnTab.jsx +421 -30
  136. package/src/app/pages/NodePage/NodeDependenciesTab.jsx +153 -0
  137. package/src/app/pages/NodePage/NodeGraphTab.jsx +119 -148
  138. package/src/app/pages/NodePage/NodeHistory.jsx +236 -0
  139. package/src/app/pages/NodePage/NodeInfoTab.jsx +346 -49
  140. package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
  141. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +585 -0
  142. package/src/app/pages/NodePage/NodeRevisionMaterializationTab.jsx +58 -0
  143. package/src/app/pages/NodePage/NodeStatus.jsx +100 -31
  144. package/src/app/pages/NodePage/NodeValidateTab.jsx +367 -0
  145. package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
  146. package/src/app/pages/NodePage/NotebookDownload.jsx +36 -0
  147. package/src/app/pages/NodePage/PartitionColumnPopover.jsx +151 -0
  148. package/src/app/pages/NodePage/PartitionValueForm.jsx +60 -0
  149. package/src/app/pages/NodePage/RevisionDiff.jsx +209 -0
  150. package/src/app/pages/NodePage/WatchNodeButton.jsx +226 -0
  151. package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +56 -0
  152. package/src/app/pages/NodePage/__tests__/AddComplexDimensionLinkPopover.test.jsx +459 -0
  153. package/src/app/pages/NodePage/__tests__/AddMaterializationPopover.test.jsx +87 -0
  154. package/src/app/pages/NodePage/__tests__/DimensionFilter.test.jsx +74 -0
  155. package/src/app/pages/NodePage/__tests__/EditColumnDescriptionPopover.test.jsx +149 -0
  156. package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +144 -0
  157. package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +132 -0
  158. package/src/app/pages/NodePage/__tests__/ManageDimensionLinksDialog.test.jsx +390 -0
  159. package/src/app/pages/NodePage/__tests__/NodeColumnTab.test.jsx +166 -0
  160. package/src/app/pages/NodePage/__tests__/NodeDependenciesTab.test.jsx +151 -0
  161. package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +595 -0
  162. package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +58 -0
  163. package/src/app/pages/NodePage/__tests__/NodeMaterializationTab.test.jsx +190 -0
  164. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +882 -0
  165. package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
  166. package/src/app/pages/NodePage/__tests__/RevisionDiff.test.jsx +164 -0
  167. package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +19 -0
  168. package/src/app/pages/NodePage/index.jsx +190 -44
  169. package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
  170. package/src/app/pages/NotificationsPage/Loadable.jsx +6 -0
  171. package/src/app/pages/NotificationsPage/__tests__/index.test.jsx +287 -0
  172. package/src/app/pages/NotificationsPage/index.jsx +136 -0
  173. package/src/app/pages/OverviewPage/ByStatusPanel.jsx +69 -0
  174. package/src/app/pages/OverviewPage/DimensionNodeUsagePanel.jsx +48 -0
  175. package/src/app/pages/OverviewPage/GovernanceWarningsPanel.jsx +107 -0
  176. package/src/app/pages/OverviewPage/Loadable.jsx +16 -0
  177. package/src/app/pages/OverviewPage/NodesByTypePanel.jsx +63 -0
  178. package/src/app/pages/OverviewPage/OverviewPanel.jsx +94 -0
  179. package/src/app/pages/OverviewPage/TrendsPanel.jsx +66 -0
  180. package/src/app/pages/OverviewPage/__tests__/ByStatusPanel.test.jsx +36 -0
  181. package/src/app/pages/OverviewPage/__tests__/DimensionNodeUsagePanel.test.jsx +76 -0
  182. package/src/app/pages/OverviewPage/__tests__/GovernanceWarningsPanel.test.jsx +77 -0
  183. package/src/app/pages/OverviewPage/__tests__/NodesByTypePanel.test.jsx +86 -0
  184. package/src/app/pages/OverviewPage/__tests__/OverviewPanel.test.jsx +78 -0
  185. package/src/app/pages/OverviewPage/__tests__/TrendsPanel.test.jsx +120 -0
  186. package/src/app/pages/OverviewPage/__tests__/index.test.jsx +54 -0
  187. package/src/app/pages/OverviewPage/index.jsx +22 -0
  188. package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
  189. package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +112 -0
  190. package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +38 -0
  191. package/src/app/pages/RegisterTablePage/index.jsx +142 -0
  192. package/src/app/pages/Root/__tests__/index.test.jsx +44 -0
  193. package/src/app/pages/Root/index.tsx +92 -10
  194. package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
  195. package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
  196. package/src/app/pages/SQLBuilderPage/index.jsx +390 -0
  197. package/src/app/pages/SettingsPage/CreateServiceAccountModal.jsx +152 -0
  198. package/src/app/pages/SettingsPage/Loadable.jsx +16 -0
  199. package/src/app/pages/SettingsPage/NotificationSubscriptionsSection.jsx +189 -0
  200. package/src/app/pages/SettingsPage/ProfileSection.jsx +41 -0
  201. package/src/app/pages/SettingsPage/ServiceAccountsSection.jsx +95 -0
  202. package/src/app/pages/SettingsPage/__tests__/CreateServiceAccountModal.test.jsx +318 -0
  203. package/src/app/pages/SettingsPage/__tests__/NotificationSubscriptionsSection.test.jsx +233 -0
  204. package/src/app/pages/SettingsPage/__tests__/ProfileSection.test.jsx +65 -0
  205. package/src/app/pages/SettingsPage/__tests__/ServiceAccountsSection.test.jsx +150 -0
  206. package/src/app/pages/SettingsPage/__tests__/index.test.jsx +184 -0
  207. package/src/app/pages/SettingsPage/index.jsx +148 -0
  208. package/src/app/pages/TagPage/Loadable.jsx +16 -0
  209. package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
  210. package/src/app/pages/TagPage/index.jsx +79 -0
  211. package/src/app/services/DJService.js +1444 -21
  212. package/src/app/services/__tests__/DJService.test.jsx +2118 -0
  213. package/src/app/utils/__tests__/date.test.js +198 -0
  214. package/src/app/utils/date.js +65 -0
  215. package/src/index.tsx +1 -0
  216. package/src/mocks/mockNodes.jsx +1477 -0
  217. package/src/setupTests.ts +31 -1
  218. package/src/styles/dag.css +117 -5
  219. package/src/styles/index.css +1028 -31
  220. package/src/styles/loading.css +34 -0
  221. package/src/styles/login.css +81 -0
  222. package/src/styles/nav-bar.css +274 -0
  223. package/src/styles/node-creation.scss +276 -0
  224. package/src/styles/node-list.css +4 -0
  225. package/src/styles/overview.css +72 -0
  226. package/src/styles/settings.css +787 -0
  227. package/src/styles/sorted-table.css +15 -0
  228. package/src/styles/styles.scss +44 -0
  229. package/src/styles/styles.scss.d.ts +9 -0
  230. package/src/utils/form.jsx +23 -0
  231. package/webpack.config.js +17 -6
  232. package/.babelrc +0 -4
  233. package/.env.local +0 -4
  234. package/.env.production +0 -1
  235. package/.github/pull_request_template.md +0 -11
  236. package/.github/workflows/ci.yml +0 -33
  237. package/.vscode/extensions.json +0 -7
  238. package/.vscode/launch.json +0 -15
  239. package/.vscode/settings.json +0 -25
  240. package/Dockerfile +0 -7
  241. package/src/app/pages/ListNamespacesPage/Loadable.jsx +0 -14
  242. package/src/app/pages/ListNamespacesPage/index.jsx +0 -62
  243. package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +0 -45
  244. package/src/app/pages/NamespacePage/__tests__/index.test.tsx +0 -14
@@ -0,0 +1,152 @@
1
+ import React, { useState } from 'react';
2
+
3
+ /**
4
+ * Modal for creating a new service account.
5
+ * Shows a form initially, then displays credentials after successful creation.
6
+ */
7
+ export function CreateServiceAccountModal({ isOpen, onClose, onCreate }) {
8
+ const [name, setName] = useState('');
9
+ const [creating, setCreating] = useState(false);
10
+ const [credentials, setCredentials] = useState(null);
11
+
12
+ const handleSubmit = async e => {
13
+ e.preventDefault();
14
+ if (!name.trim()) return;
15
+
16
+ setCreating(true);
17
+ try {
18
+ const result = await onCreate(name.trim());
19
+ if (result.client_id) {
20
+ setCredentials(result);
21
+ setName('');
22
+ } else if (result.message) {
23
+ alert(result.message);
24
+ }
25
+ } catch (error) {
26
+ console.error('Error creating service account:', error);
27
+ alert('Failed to create service account');
28
+ } finally {
29
+ setCreating(false);
30
+ }
31
+ };
32
+
33
+ const handleClose = () => {
34
+ setName('');
35
+ setCredentials(null);
36
+ onClose();
37
+ };
38
+
39
+ const copyToClipboard = text => {
40
+ navigator.clipboard.writeText(text);
41
+ };
42
+
43
+ if (!isOpen) return null;
44
+
45
+ return (
46
+ <div className="modal-overlay" onClick={handleClose}>
47
+ <div className="modal-content" onClick={e => e.stopPropagation()}>
48
+ <div className="modal-header">
49
+ <h3>Create Service Account</h3>
50
+ <button
51
+ className="btn-close-modal"
52
+ onClick={handleClose}
53
+ title="Close"
54
+ >
55
+ ×
56
+ </button>
57
+ </div>
58
+
59
+ {credentials ? (
60
+ /* Show credentials after creation */
61
+ <div className="modal-body">
62
+ <div className="credentials-success">
63
+ <span className="success-icon">✓</span>
64
+ <h4>Service Account Created!</h4>
65
+ </div>
66
+ <p className="credentials-warning">
67
+ Save these credentials now. The client secret will not be shown
68
+ again.
69
+ </p>
70
+ <div className="credentials-grid">
71
+ <div className="credential-item">
72
+ <label>Name</label>
73
+ <code>{credentials.name}</code>
74
+ </div>
75
+ <div className="credential-item">
76
+ <label>Client ID</label>
77
+ <div className="credential-value">
78
+ <code>{credentials.client_id}</code>
79
+ <button
80
+ className="btn-copy"
81
+ onClick={() => copyToClipboard(credentials.client_id)}
82
+ title="Copy"
83
+ >
84
+ 📋
85
+ </button>
86
+ </div>
87
+ </div>
88
+ <div className="credential-item">
89
+ <label>Client Secret</label>
90
+ <div className="credential-value">
91
+ <code>{credentials.client_secret}</code>
92
+ <button
93
+ className="btn-copy"
94
+ onClick={() => copyToClipboard(credentials.client_secret)}
95
+ title="Copy"
96
+ >
97
+ 📋
98
+ </button>
99
+ </div>
100
+ </div>
101
+ </div>
102
+ <div className="modal-actions">
103
+ <button className="btn-primary" onClick={handleClose}>
104
+ Done
105
+ </button>
106
+ </div>
107
+ </div>
108
+ ) : (
109
+ /* Show creation form */
110
+ <form onSubmit={handleSubmit}>
111
+ <div className="modal-body">
112
+ <div className="form-group">
113
+ <label htmlFor="service-account-name">Name</label>
114
+ <input
115
+ id="service-account-name"
116
+ type="text"
117
+ placeholder="e.g., my-pipeline, etl-job, ci-cd"
118
+ value={name}
119
+ onChange={e => setName(e.target.value)}
120
+ disabled={creating}
121
+ autoFocus
122
+ />
123
+ <span className="form-hint">
124
+ A descriptive name to identify this service account
125
+ </span>
126
+ </div>
127
+ </div>
128
+ <div className="modal-actions">
129
+ <button
130
+ type="button"
131
+ className="btn-secondary"
132
+ onClick={handleClose}
133
+ disabled={creating}
134
+ >
135
+ Cancel
136
+ </button>
137
+ <button
138
+ type="submit"
139
+ className="btn-primary"
140
+ disabled={creating || !name.trim()}
141
+ >
142
+ {creating ? 'Creating...' : 'Create'}
143
+ </button>
144
+ </div>
145
+ </form>
146
+ )}
147
+ </div>
148
+ </div>
149
+ );
150
+ }
151
+
152
+ export default CreateServiceAccountModal;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Asynchronously loads the component for the Settings page
3
+ */
4
+
5
+ import * as React from 'react';
6
+ import { lazyLoad } from '../../../utils/loadable';
7
+
8
+ export const SettingsPage = props => {
9
+ return lazyLoad(
10
+ () => import('./index'),
11
+ module => module.SettingsPage,
12
+ {
13
+ fallback: <div></div>,
14
+ },
15
+ )(props);
16
+ };
@@ -0,0 +1,189 @@
1
+ import React, { useState } from 'react';
2
+ import EditIcon from '../../icons/EditIcon';
3
+
4
+ // Available activity types for subscriptions
5
+ const ACTIVITY_TYPES = [
6
+ { value: 'create', label: 'Create' },
7
+ { value: 'update', label: 'Update' },
8
+ { value: 'delete', label: 'Delete' },
9
+ { value: 'status_change', label: 'Status Change' },
10
+ { value: 'refresh', label: 'Refresh' },
11
+ { value: 'tag', label: 'Tag' },
12
+ ];
13
+
14
+ /**
15
+ * Displays and manages notification subscriptions.
16
+ */
17
+ export function NotificationSubscriptionsSection({
18
+ subscriptions,
19
+ onUpdate,
20
+ onUnsubscribe,
21
+ }) {
22
+ const [editingIndex, setEditingIndex] = useState(null);
23
+ const [editedActivityTypes, setEditedActivityTypes] = useState([]);
24
+ const [saving, setSaving] = useState(false);
25
+
26
+ const startEditing = index => {
27
+ setEditingIndex(index);
28
+ setEditedActivityTypes([...subscriptions[index].activity_types]);
29
+ };
30
+
31
+ const cancelEditing = () => {
32
+ setEditingIndex(null);
33
+ setEditedActivityTypes([]);
34
+ };
35
+
36
+ const toggleActivityType = activityType => {
37
+ if (editedActivityTypes.includes(activityType)) {
38
+ setEditedActivityTypes(
39
+ editedActivityTypes.filter(t => t !== activityType),
40
+ );
41
+ } else {
42
+ setEditedActivityTypes([...editedActivityTypes, activityType]);
43
+ }
44
+ };
45
+
46
+ const saveSubscription = async sub => {
47
+ if (editedActivityTypes.length === 0) {
48
+ alert('Please select at least one activity type');
49
+ return;
50
+ }
51
+
52
+ setSaving(true);
53
+ try {
54
+ await onUpdate(sub, editedActivityTypes);
55
+ setEditingIndex(null);
56
+ setEditedActivityTypes([]);
57
+ } catch (error) {
58
+ console.error('Error updating subscription:', error);
59
+ alert('Failed to update subscription');
60
+ } finally {
61
+ setSaving(false);
62
+ }
63
+ };
64
+
65
+ const handleUnsubscribe = async (sub, index) => {
66
+ const confirmed = window.confirm(
67
+ `Unsubscribe from notifications for "${sub.entity_name}"?`,
68
+ );
69
+ if (!confirmed) return;
70
+
71
+ try {
72
+ await onUnsubscribe(sub);
73
+ } catch (error) {
74
+ console.error('Error unsubscribing:', error);
75
+ }
76
+ };
77
+
78
+ return (
79
+ <section className="settings-section" id="notifications">
80
+ <h2 className="settings-section-title">Notification Subscriptions</h2>
81
+ <div className="settings-card">
82
+ {subscriptions.length === 0 ? (
83
+ <p className="empty-state">
84
+ You're not watching any nodes yet. Visit a node page and click
85
+ "Watch" to subscribe to updates.
86
+ </p>
87
+ ) : (
88
+ <div className="subscriptions-list">
89
+ {subscriptions.map((sub, index) => (
90
+ <div key={index} className="subscription-item">
91
+ <div className="subscription-header">
92
+ <div className="subscription-entity">
93
+ <a href={`/nodes/${sub.entity_name}`}>{sub.entity_name}</a>
94
+ {sub.node_type ? (
95
+ <span
96
+ className={`node_type__${sub.node_type} badge node_type`}
97
+ >
98
+ {sub.node_type.toUpperCase()}
99
+ </span>
100
+ ) : (
101
+ <span className="entity-type-badge">
102
+ {sub.entity_type}
103
+ </span>
104
+ )}
105
+ {sub.status === 'invalid' && (
106
+ <span className="status-badge status-invalid">
107
+ INVALID
108
+ </span>
109
+ )}
110
+ </div>
111
+ <div className="subscription-actions">
112
+ {editingIndex === index ? (
113
+ <>
114
+ <button
115
+ className="btn-save"
116
+ onClick={() => saveSubscription(sub)}
117
+ disabled={saving}
118
+ >
119
+ {saving ? 'Saving...' : 'Save'}
120
+ </button>
121
+ <button
122
+ className="btn-cancel"
123
+ onClick={cancelEditing}
124
+ disabled={saving}
125
+ >
126
+ Cancel
127
+ </button>
128
+ </>
129
+ ) : (
130
+ <>
131
+ <button
132
+ className="btn-icon btn-edit"
133
+ onClick={() => startEditing(index)}
134
+ title="Edit subscription"
135
+ >
136
+ <EditIcon />
137
+ </button>
138
+ <button
139
+ className="btn-icon btn-unsubscribe"
140
+ onClick={() => handleUnsubscribe(sub, index)}
141
+ title="Unsubscribe"
142
+ >
143
+ ×
144
+ </button>
145
+ </>
146
+ )}
147
+ </div>
148
+ </div>
149
+
150
+ <div className="subscription-activity-types">
151
+ <label className="activity-types-label">
152
+ Activity types:
153
+ </label>
154
+ {editingIndex === index ? (
155
+ <div className="activity-types-checkboxes">
156
+ {ACTIVITY_TYPES.map(type => (
157
+ <label key={type.value} className="checkbox-label">
158
+ <input
159
+ type="checkbox"
160
+ checked={editedActivityTypes.includes(type.value)}
161
+ onChange={() => toggleActivityType(type.value)}
162
+ />
163
+ {type.label}
164
+ </label>
165
+ ))}
166
+ </div>
167
+ ) : (
168
+ <div className="activity-types-badges">
169
+ {sub.activity_types?.map(type => (
170
+ <span
171
+ key={type}
172
+ className={`activity-badge activity-badge-${type}`}
173
+ >
174
+ {type}
175
+ </span>
176
+ )) || <span className="text-muted">All</span>}
177
+ </div>
178
+ )}
179
+ </div>
180
+ </div>
181
+ ))}
182
+ </div>
183
+ )}
184
+ </div>
185
+ </section>
186
+ );
187
+ }
188
+
189
+ export default NotificationSubscriptionsSection;
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * Displays user profile information including avatar, username, and email.
5
+ */
6
+ export function ProfileSection({ user }) {
7
+ const getInitials = () => {
8
+ if (user?.name) {
9
+ return user.name
10
+ .split(' ')
11
+ .map(n => n[0])
12
+ .join('')
13
+ .toUpperCase()
14
+ .slice(0, 2);
15
+ }
16
+ return user?.username?.slice(0, 2).toUpperCase() || '?';
17
+ };
18
+
19
+ return (
20
+ <section className="settings-section">
21
+ <h2 className="settings-section-title">Profile</h2>
22
+ <div className="settings-card">
23
+ <div className="profile-info">
24
+ <div className="profile-avatar">{getInitials()}</div>
25
+ <div className="profile-details">
26
+ <div className="profile-field">
27
+ <label>Username</label>
28
+ <span>{user?.username || '-'}</span>
29
+ </div>
30
+ <div className="profile-field">
31
+ <label>Email</label>
32
+ <span>{user?.email || '-'}</span>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </section>
38
+ );
39
+ }
40
+
41
+ export default ProfileSection;
@@ -0,0 +1,95 @@
1
+ import React, { useState } from 'react';
2
+ import CreateServiceAccountModal from './CreateServiceAccountModal';
3
+
4
+ /**
5
+ * Displays and manages service accounts.
6
+ */
7
+ export function ServiceAccountsSection({ accounts, onCreate, onDelete }) {
8
+ const [showModal, setShowModal] = useState(false);
9
+
10
+ const handleDelete = async account => {
11
+ const confirmed = window.confirm(
12
+ `Delete service account "${account.name}"?\n\nThis will revoke all access for this account and cannot be undone.`,
13
+ );
14
+ if (!confirmed) return;
15
+
16
+ try {
17
+ await onDelete(account.client_id);
18
+ } catch (error) {
19
+ console.error('Error deleting service account:', error);
20
+ alert('Failed to delete service account');
21
+ }
22
+ };
23
+
24
+ const handleCreate = async name => {
25
+ const result = await onCreate(name);
26
+ return result;
27
+ };
28
+
29
+ return (
30
+ <section className="settings-section" id="service-accounts">
31
+ <div className="section-title-row">
32
+ <h2 className="settings-section-title">Service Accounts</h2>
33
+ <button className="btn-create" onClick={() => setShowModal(true)}>
34
+ + Create
35
+ </button>
36
+ </div>
37
+ <div className="settings-card">
38
+ <p className="section-description">
39
+ Service accounts allow programmatic access to the DJ API. Create
40
+ accounts for your applications, scripts, or CI/CD pipelines.
41
+ </p>
42
+
43
+ {accounts.length > 0 ? (
44
+ <div className="service-accounts-list">
45
+ <table className="service-accounts-table">
46
+ <thead>
47
+ <tr>
48
+ <th>Name</th>
49
+ <th>Client ID</th>
50
+ <th>Created</th>
51
+ <th></th>
52
+ </tr>
53
+ </thead>
54
+ <tbody>
55
+ {accounts.map(account => (
56
+ <tr key={account.id}>
57
+ <td>{account.name}</td>
58
+ <td>
59
+ <code className="client-id">{account.client_id}</code>
60
+ </td>
61
+ <td className="created-date">
62
+ {new Date(account.created_at).toLocaleDateString()}
63
+ </td>
64
+ <td className="actions-cell">
65
+ <button
66
+ className="btn-icon btn-delete-account"
67
+ onClick={() => handleDelete(account)}
68
+ title="Delete service account"
69
+ >
70
+ ×
71
+ </button>
72
+ </td>
73
+ </tr>
74
+ ))}
75
+ </tbody>
76
+ </table>
77
+ </div>
78
+ ) : (
79
+ <p className="empty-state">
80
+ No service accounts yet. Create one to enable programmatic API
81
+ access.
82
+ </p>
83
+ )}
84
+ </div>
85
+
86
+ <CreateServiceAccountModal
87
+ isOpen={showModal}
88
+ onClose={() => setShowModal(false)}
89
+ onCreate={handleCreate}
90
+ />
91
+ </section>
92
+ );
93
+ }
94
+
95
+ export default ServiceAccountsSection;