@postgres.ai/shared 3.5.1-pr-1027.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 (202) hide show
  1. package/.gitlab-ci.yml +60 -0
  2. package/components/AlertSnackbar/index.tsx +23 -0
  3. package/components/AlertSnackbar/useAlertSnackbar.tsx +65 -0
  4. package/components/Button/index.tsx +79 -0
  5. package/components/Button2/index.tsx +43 -0
  6. package/components/Button2/styles.module.scss +82 -0
  7. package/components/DestroyCloneModal/index.tsx +56 -0
  8. package/components/DestroyCloneRestrictionModal/index.tsx +50 -0
  9. package/components/ErrorStub/index.tsx +83 -0
  10. package/components/FormattedText/index.tsx +44 -0
  11. package/components/FormattedText/styles.module.scss +34 -0
  12. package/components/GatewayLink/index.tsx +33 -0
  13. package/components/HorizontalScrollContainer/index.tsx +131 -0
  14. package/components/HorizontalScrollContainer/types.ts +12 -0
  15. package/components/HorizontalScrollContainer/utils.ts +16 -0
  16. package/components/ImportantText/index.tsx +29 -0
  17. package/components/Link2/index.tsx +31 -0
  18. package/components/Link2/styles.module.scss +12 -0
  19. package/components/MenuButton/index.tsx +80 -0
  20. package/components/MenuButton/styles.module.scss +42 -0
  21. package/components/Modal/index.tsx +93 -0
  22. package/components/PageSpinner/index.tsx +18 -0
  23. package/components/PageSpinner/styles.module.scss +13 -0
  24. package/components/ResetCloneModal/index.tsx +154 -0
  25. package/components/SectionTitle/index.tsx +74 -0
  26. package/components/Select/index.tsx +42 -0
  27. package/components/SimpleModalControls/index.tsx +56 -0
  28. package/components/Spinner/icon.tsx +29 -0
  29. package/components/Spinner/index.tsx +16 -0
  30. package/components/Spinner/styles.module.scss +33 -0
  31. package/components/Status/index.tsx +61 -0
  32. package/components/Status/styles.module.scss +45 -0
  33. package/components/StubContainer/index.tsx +41 -0
  34. package/components/StubSpinner/index.tsx +49 -0
  35. package/components/StubSpinnerFlex/index.tsx +20 -0
  36. package/components/StubSpinnerFlex/styles.module.scss +20 -0
  37. package/components/SyntaxHighlight/index.tsx +107 -0
  38. package/components/Table/RowMenu/index.tsx +111 -0
  39. package/components/Table/index.tsx +140 -0
  40. package/components/Text/index.tsx +28 -0
  41. package/components/TextField/index.tsx +117 -0
  42. package/components/Tooltip/index.tsx +52 -0
  43. package/config/index.ts +32 -0
  44. package/config/links.ts +6 -0
  45. package/craco.config.js +80 -0
  46. package/helpers/getEntropy.ts +232 -0
  47. package/helpers/localStorage.ts +15 -0
  48. package/helpers/request.ts +47 -0
  49. package/hooks/useWindowDimensions.ts +16 -0
  50. package/icons/ArrowDropDown/index.tsx +29 -0
  51. package/icons/Circle/index.tsx +27 -0
  52. package/icons/External/index.tsx +14 -0
  53. package/icons/Info/index.tsx +12 -0
  54. package/icons/Renewable/index.tsx +65 -0
  55. package/icons/Shield/index.tsx +33 -0
  56. package/icons/Warning/index.tsx +29 -0
  57. package/meta.json +1 -0
  58. package/package.json +55 -0
  59. package/pages/Clone/Status/index.tsx +73 -0
  60. package/pages/Clone/context.ts +22 -0
  61. package/pages/Clone/index.tsx +634 -0
  62. package/pages/Clone/stores/Main.ts +206 -0
  63. package/pages/Clone/useCreatedStores.ts +11 -0
  64. package/pages/Configuration/Header/index.tsx +84 -0
  65. package/pages/Configuration/InputWithTooltip/index.tsx +240 -0
  66. package/pages/Configuration/ResponseMessage/index.tsx +71 -0
  67. package/pages/Configuration/configOptions.ts +60 -0
  68. package/pages/Configuration/index.tsx +1184 -0
  69. package/pages/Configuration/styles.module.scss +122 -0
  70. package/pages/Configuration/tooltipText.tsx +157 -0
  71. package/pages/Configuration/useForm.ts +108 -0
  72. package/pages/Configuration/utils/index.ts +153 -0
  73. package/pages/CreateClone/index.tsx +311 -0
  74. package/pages/CreateClone/stores/Main.ts +107 -0
  75. package/pages/CreateClone/styles.module.scss +71 -0
  76. package/pages/CreateClone/useCreatedStores.ts +11 -0
  77. package/pages/CreateClone/useForm.ts +36 -0
  78. package/pages/Instance/Clones/Header/Item/index.tsx +15 -0
  79. package/pages/Instance/Clones/Header/Item/styles.module.scss +17 -0
  80. package/pages/Instance/Clones/Header/index.tsx +74 -0
  81. package/pages/Instance/Clones/Header/styles.module.scss +11 -0
  82. package/pages/Instance/Clones/index.tsx +135 -0
  83. package/pages/Instance/ClonesModal/index.tsx +71 -0
  84. package/pages/Instance/ClonesModal/utils.ts +21 -0
  85. package/pages/Instance/InactiveInstance/index.tsx +165 -0
  86. package/pages/Instance/InactiveInstance/utils.ts +9 -0
  87. package/pages/Instance/Info/Connection/ConnectModal/Content/index.tsx +176 -0
  88. package/pages/Instance/Info/Connection/ConnectModal/Content/utils.ts +24 -0
  89. package/pages/Instance/Info/Connection/ConnectModal/index.tsx +36 -0
  90. package/pages/Instance/Info/Connection/index.tsx +81 -0
  91. package/pages/Instance/Info/Details/index.tsx +20 -0
  92. package/pages/Instance/Info/Disks/Disk/ActionsMenu/index.tsx +100 -0
  93. package/pages/Instance/Info/Disks/Disk/Marker/index.tsx +26 -0
  94. package/pages/Instance/Info/Disks/Disk/ProgressBar/PointerIcon.tsx +20 -0
  95. package/pages/Instance/Info/Disks/Disk/ProgressBar/index.tsx +73 -0
  96. package/pages/Instance/Info/Disks/Disk/Status/index.tsx +75 -0
  97. package/pages/Instance/Info/Disks/Disk/index.tsx +168 -0
  98. package/pages/Instance/Info/Disks/index.tsx +65 -0
  99. package/pages/Instance/Info/Icons/index.tsx +39 -0
  100. package/pages/Instance/Info/Retrieval/RefreshFailedAlert/index.tsx +32 -0
  101. package/pages/Instance/Info/Retrieval/RefreshFailedAlert/styles.module.scss +33 -0
  102. package/pages/Instance/Info/Retrieval/RetrievalModal/index.tsx +49 -0
  103. package/pages/Instance/Info/Retrieval/RetrievalModal/styles.module.scss +6 -0
  104. package/pages/Instance/Info/Retrieval/RetrievalTable/index.tsx +53 -0
  105. package/pages/Instance/Info/Retrieval/RetrievalTable/styles.module.scss +29 -0
  106. package/pages/Instance/Info/Retrieval/index.tsx +95 -0
  107. package/pages/Instance/Info/Retrieval/utils.ts +10 -0
  108. package/pages/Instance/Info/Snapshots/Calendar/Day/index.tsx +125 -0
  109. package/pages/Instance/Info/Snapshots/Calendar/index.tsx +133 -0
  110. package/pages/Instance/Info/Snapshots/Calendar/utils.ts +74 -0
  111. package/pages/Instance/Info/Snapshots/TimeLine/Day/index.tsx +79 -0
  112. package/pages/Instance/Info/Snapshots/TimeLine/index.tsx +57 -0
  113. package/pages/Instance/Info/Snapshots/index.tsx +97 -0
  114. package/pages/Instance/Info/Snapshots/utils.ts +18 -0
  115. package/pages/Instance/Info/Status/InstanceResponseModal/index.tsx +32 -0
  116. package/pages/Instance/Info/Status/InstanceResponseModal/styles.module.scss +3 -0
  117. package/pages/Instance/Info/Status/index.tsx +85 -0
  118. package/pages/Instance/Info/Status/styles.module.scss +12 -0
  119. package/pages/Instance/Info/Status/utils.ts +24 -0
  120. package/pages/Instance/Info/components/Property/index.tsx +32 -0
  121. package/pages/Instance/Info/components/Property/styles.module.scss +21 -0
  122. package/pages/Instance/Info/components/Section/index.tsx +50 -0
  123. package/pages/Instance/Info/components/ValueStatus/index.tsx +51 -0
  124. package/pages/Instance/Info/index.tsx +129 -0
  125. package/pages/Instance/SnapshotsModal/index.tsx +169 -0
  126. package/pages/Instance/SnapshotsModal/utils.ts +17 -0
  127. package/pages/Instance/Tabs/index.tsx +98 -0
  128. package/pages/Instance/components/ClonesList/ConnectionModal/index.tsx +196 -0
  129. package/pages/Instance/components/ClonesList/MenuCell/index.tsx +98 -0
  130. package/pages/Instance/components/ClonesList/MenuCell/utils.ts +21 -0
  131. package/pages/Instance/components/ClonesList/index.tsx +189 -0
  132. package/pages/Instance/components/ClonesList/styles.module.scss +32 -0
  133. package/pages/Instance/components/ErrorStub/index.tsx +77 -0
  134. package/pages/Instance/components/ModalReloadButton/index.tsx +43 -0
  135. package/pages/Instance/components/Tags/Tag/index.tsx +60 -0
  136. package/pages/Instance/components/Tags/index.tsx +42 -0
  137. package/pages/Instance/context.ts +39 -0
  138. package/pages/Instance/index.tsx +235 -0
  139. package/pages/Instance/stores/ClonesModal.ts +35 -0
  140. package/pages/Instance/stores/Main.ts +335 -0
  141. package/pages/Instance/stores/SnapshotsModal.ts +35 -0
  142. package/pages/Instance/styles.scss +40 -0
  143. package/pages/Instance/useCreatedStores.ts +14 -0
  144. package/pages/Logs/Icons/PlusIcon.tsx +8 -0
  145. package/pages/Logs/constants/index.ts +7 -0
  146. package/pages/Logs/hooks/useWsScroll.tsx +44 -0
  147. package/pages/Logs/index.tsx +267 -0
  148. package/pages/Logs/utils/index.ts +20 -0
  149. package/pages/Logs/wsLogs.ts +110 -0
  150. package/pages/Logs/wsSnackbar.ts +27 -0
  151. package/postgres.ai-shared-3.5.0.tgz +0 -0
  152. package/react-app-env.d.ts +71 -0
  153. package/scripts/copy-assets.js +30 -0
  154. package/scripts/pack.js +70 -0
  155. package/stores/Snapshots.ts +54 -0
  156. package/styles/colors.ts +67 -0
  157. package/styles/global.scss +29 -0
  158. package/styles/icons.tsx +1917 -0
  159. package/styles/mixins.scss +30 -0
  160. package/styles/styles.ts +87 -0
  161. package/styles/theme.ts +53 -0
  162. package/styles/vars.scss +43 -0
  163. package/styles/vars.ts +40 -0
  164. package/tsconfig.build.json +37 -0
  165. package/tsconfig.json +30 -0
  166. package/types/api/endpoints/createClone.ts +10 -0
  167. package/types/api/endpoints/destroyClone.ts +7 -0
  168. package/types/api/endpoints/getClone.ts +6 -0
  169. package/types/api/endpoints/getConfig.ts +6 -0
  170. package/types/api/endpoints/getEngine.ts +13 -0
  171. package/types/api/endpoints/getFullConfig.ts +4 -0
  172. package/types/api/endpoints/getInstance.ts +6 -0
  173. package/types/api/endpoints/getInstanceRetrieval.ts +6 -0
  174. package/types/api/endpoints/getSeImages.ts +22 -0
  175. package/types/api/endpoints/getSnapshots.ts +6 -0
  176. package/types/api/endpoints/getWSToken.ts +6 -0
  177. package/types/api/endpoints/initWS.ts +1 -0
  178. package/types/api/endpoints/refreshInstance.ts +4 -0
  179. package/types/api/endpoints/resetClone.ts +8 -0
  180. package/types/api/endpoints/testDbSource.ts +48 -0
  181. package/types/api/endpoints/updateClone.ts +10 -0
  182. package/types/api/endpoints/updateConfig.ts +6 -0
  183. package/types/api/entities/clone.ts +42 -0
  184. package/types/api/entities/config.ts +114 -0
  185. package/types/api/entities/dbSource.ts +13 -0
  186. package/types/api/entities/instance.ts +67 -0
  187. package/types/api/entities/instanceRetrieval.ts +46 -0
  188. package/types/api/entities/instanceState.ts +102 -0
  189. package/types/api/entities/pool.ts +27 -0
  190. package/types/api/entities/snapshot.ts +18 -0
  191. package/types/api/entities/wsToken.ts +7 -0
  192. package/types/byte-size/index.d.ts +22 -0
  193. package/utils/api.ts +30 -0
  194. package/utils/clone.ts +31 -0
  195. package/utils/connection.ts +38 -0
  196. package/utils/date.ts +87 -0
  197. package/utils/instance.ts +10 -0
  198. package/utils/numbers.ts +11 -0
  199. package/utils/react.ts +10 -0
  200. package/utils/snapshot.ts +4 -0
  201. package/utils/strings.ts +11 -0
  202. package/utils/units.ts +23 -0
@@ -0,0 +1,7 @@
1
+ export const LOGS_NEW_DATA_MESSAGE =
2
+ 'New data arrived below - scroll down to see it 👇🏻'
3
+
4
+ export const LAPTOP_WIDTH_PX = 982
5
+ export const LOGS_TIME_LIMIT = 20
6
+ export const LOGS_LINE_LIMIT = 1000
7
+ export const LOGS_ENDPOINT = '/instance/logs'
@@ -0,0 +1,44 @@
1
+ import { useState, useEffect } from 'react'
2
+ import { wsSnackbar } from '@postgres.ai/shared/pages/Logs/wsSnackbar'
3
+
4
+ export const useWsScroll = (isLoading: boolean, simpleInstall?: boolean) => {
5
+ const [isNewData, setIsNewData] = useState(false)
6
+ const [isAtBottom, setIsAtBottom] = useState(true)
7
+
8
+ useEffect(() => {
9
+ !isLoading && wsSnackbar(isAtBottom, isNewData)
10
+ const targetNode = simpleInstall
11
+ ? document.getElementById('logs-container')?.parentElement
12
+ : document.getElementById('logs-container')
13
+
14
+ const clientAtBottom = (element: HTMLElement) =>
15
+ element.scrollHeight - element.scrollTop - 50 < element.clientHeight
16
+
17
+ const handleScroll = (e: Event) => {
18
+ if (clientAtBottom(e.target as HTMLElement)) {
19
+ setIsAtBottom(true)
20
+ setIsNewData(false)
21
+ } else {
22
+ setIsAtBottom(false)
23
+ }
24
+ }
25
+
26
+ const handleInsert = (e: Event | any) => {
27
+ if (e.srcElement?.tagName !== 'DIV') {
28
+ isAtBottom &&
29
+ targetNode?.scroll({
30
+ top: targetNode.scrollHeight,
31
+ })
32
+ setIsNewData(true)
33
+ }
34
+ }
35
+
36
+ targetNode?.addEventListener('scroll', handleScroll, false)
37
+ targetNode?.addEventListener('DOMNodeInserted', handleInsert, false)
38
+
39
+ return () => {
40
+ targetNode?.removeEventListener('scroll', handleScroll, false)
41
+ targetNode?.removeEventListener('DOMNodeInserted', handleInsert, false)
42
+ }
43
+ }, [isAtBottom, isNewData, isLoading])
44
+ }
@@ -0,0 +1,267 @@
1
+ import classNames from 'classnames'
2
+ import { makeStyles } from '@material-ui/core'
3
+ import { Alert, AlertTitle } from '@material-ui/lab'
4
+ import React, { useEffect, useReducer } from 'react'
5
+
6
+ import { Spinner } from '@postgres.ai/shared/components/Spinner'
7
+ import { Api } from '@postgres.ai/shared/pages/Instance/stores/Main'
8
+ import { establishConnection } from '@postgres.ai/shared/pages/Logs/wsLogs'
9
+ import { useWsScroll } from '@postgres.ai/shared/pages/Logs/hooks/useWsScroll'
10
+ import { LAPTOP_WIDTH_PX } from '@postgres.ai/shared/pages/Logs/constants'
11
+ import { PlusIcon } from './Icons/PlusIcon'
12
+
13
+ const useStyles = makeStyles(
14
+ () => ({
15
+ spinnerContainer: {
16
+ display: 'flex',
17
+ width: '100%',
18
+ alignItems: 'center',
19
+ justifyContent: 'center',
20
+ },
21
+ filterSection: {
22
+ marginTop: '10px',
23
+ display: 'flex',
24
+ flexDirection: 'row',
25
+ gap: 10,
26
+
27
+ '& > span': {
28
+ display: 'flex',
29
+ flexDirection: 'row',
30
+ gap: '5px',
31
+ alignItems: 'center',
32
+ border: '1px solid #898E9A',
33
+ padding: '3px 8px',
34
+ borderRadius: 5,
35
+ fontSize: '13px',
36
+ textTransform: 'capitalize',
37
+ cursor: 'pointer',
38
+ },
39
+
40
+ '& > span > button': {
41
+ background: 'none',
42
+ outline: 'none',
43
+ border: 0,
44
+ width: '18px',
45
+ height: '18px',
46
+ cursor: 'pointer',
47
+ },
48
+ },
49
+ // we need important since id has higher priority than class
50
+ logsContainer: {
51
+ overflow: 'auto !important',
52
+ margin: '10px 0 0 0 !important',
53
+ maxHeight: 'calc(100vh - 360px)',
54
+ position: 'relative',
55
+
56
+ '& p': {
57
+ fontSize: '10px !important',
58
+ maxWidth: 'calc(100% - 25px)',
59
+
60
+ '@media (max-width: 982px)': {
61
+ maxWidth: '100%',
62
+ },
63
+ },
64
+ },
65
+ activeButton: {
66
+ border: '1px solid #3F51B5 !important',
67
+ color: '#3F51B5 !important',
68
+
69
+ '& svg': {
70
+ '& path': {
71
+ fill: '#3F51B5 !important',
72
+ },
73
+ },
74
+ },
75
+ passiveButton: {
76
+ '& svg': {
77
+ transform: 'rotate(45deg) scale(0.75)',
78
+ },
79
+ },
80
+ activeError: {
81
+ border: '1px solid #F44336 !important',
82
+ color: '#F44336 !important',
83
+
84
+ '& svg': {
85
+ '& path': {
86
+ fill: '#F44336 !important',
87
+ },
88
+ },
89
+ },
90
+ utilFilter: {
91
+ '& > span': {
92
+ textTransform: 'lowercase',
93
+ },
94
+
95
+ '& > span:last-child': {
96
+ textTransform: 'capitalize',
97
+ },
98
+ },
99
+ }),
100
+ { index: 1 },
101
+ )
102
+
103
+ export const Logs = ({ api }: { api: Api }) => {
104
+ const classes = useStyles()
105
+ const [isLoading, setIsLoading] = React.useState(true)
106
+ const targetNode = document.getElementById('logs-container')
107
+ useWsScroll(isLoading)
108
+
109
+ const logsFilterState =
110
+ localStorage?.getItem('logsFilter') &&
111
+ JSON?.parse(localStorage?.getItem('logsFilter') || '')
112
+
113
+ const isEmpty = (obj: Record<string, boolean>) => {
114
+ for (const key in obj) {
115
+ if (obj.hasOwnProperty(key)) return false
116
+ }
117
+ return true
118
+ }
119
+
120
+ const initialState = {
121
+ '[DEBUG]': !isEmpty(logsFilterState) ? logsFilterState?.['[DEBUG]'] : true,
122
+ '[INFO]': !isEmpty(logsFilterState) ? logsFilterState?.['[INFO]'] : true,
123
+ '[ERROR]': !isEmpty(logsFilterState) ? logsFilterState?.['[ERROR]'] : true,
124
+ '[base.go]': !isEmpty(logsFilterState)
125
+ ? logsFilterState?.['[base.go]']
126
+ : true,
127
+ '[runners.go]': !isEmpty(logsFilterState)
128
+ ? logsFilterState?.['[runners.go]']
129
+ : true,
130
+ '[snapshots.go]': !isEmpty(logsFilterState)
131
+ ? logsFilterState?.['[snapshots.go]']
132
+ : true,
133
+ '[util.go]': !isEmpty(logsFilterState)
134
+ ? logsFilterState?.['[util.go]']
135
+ : true,
136
+ '[logging.go]': !isEmpty(logsFilterState)
137
+ ? logsFilterState?.['[logging.go]']
138
+ : false,
139
+ '[ws.go]': !isEmpty(logsFilterState) ? logsFilterState?.['[ws.go]'] : false,
140
+ '[other]': !isEmpty(logsFilterState) ? logsFilterState?.['[other]'] : true,
141
+ }
142
+
143
+ const reducer = (
144
+ state: Record<string, boolean>,
145
+ action: { type: string },
146
+ ) => {
147
+ switch (action.type) {
148
+ case 'DEBUG':
149
+ return { ...state, '[DEBUG]': !state['[DEBUG]'] }
150
+ case 'INFO':
151
+ return { ...state, '[INFO]': !state['[INFO]'] }
152
+ case 'ERROR':
153
+ return { ...state, '[ERROR]': !state['[ERROR]'] }
154
+ case 'base.go':
155
+ return { ...state, '[base.go]': !state['[base.go]'] }
156
+ case 'runners.go':
157
+ return { ...state, '[runners.go]': !state['[runners.go]'] }
158
+ case 'snapshots.go':
159
+ return { ...state, '[snapshots.go]': !state['[snapshots.go]'] }
160
+ case 'logging.go':
161
+ return { ...state, '[logging.go]': !state['[logging.go]'] }
162
+ case 'util.go':
163
+ return { ...state, '[util.go]': !state['[util.go]'] }
164
+ case 'ws.go':
165
+ return { ...state, '[ws.go]': !state['[ws.go]'] }
166
+ case 'other':
167
+ return { ...state, '[other]': !state['[other]'] }
168
+ default:
169
+ throw new Error()
170
+ }
171
+ }
172
+
173
+ const [state, dispatch] = useReducer(reducer, initialState)
174
+
175
+ const FormCheckbox = ({ type }: { type: string }) => {
176
+ const filterType = (state as Record<string, boolean>)[`[${type}]`]
177
+ return (
178
+ <span
179
+ onClick={() => dispatch({ type })}
180
+ className={
181
+ filterType && type !== 'ERROR'
182
+ ? classes.activeButton
183
+ : filterType && type === 'ERROR'
184
+ ? classes.activeError
185
+ : classes.passiveButton
186
+ }
187
+ >
188
+ <span>{type.toLowerCase()}</span>
189
+ <button aria-label="close" type="button">
190
+ <PlusIcon />
191
+ </button>
192
+ </span>
193
+ )
194
+ }
195
+
196
+ useEffect(() => {
197
+ if (api.initWS != undefined) {
198
+ establishConnection(api)
199
+ }
200
+ }, [api])
201
+
202
+ useEffect(() => {
203
+ localStorage.setItem('logsFilter', JSON.stringify(state))
204
+ }, [state])
205
+
206
+ useEffect(() => {
207
+ const config = { attributes: false, childList: true, subtree: true }
208
+
209
+ if (isLoading && targetNode?.querySelectorAll('p').length === 1) {
210
+ setIsLoading(false)
211
+ }
212
+
213
+ const callback = (mutationList: MutationRecord[]) => {
214
+ for (const mutation of mutationList) {
215
+ if (mutation.type === 'childList') {
216
+ setIsLoading(false)
217
+ }
218
+ }
219
+ }
220
+
221
+ const observer = new MutationObserver(callback)
222
+ targetNode && observer.observe(targetNode, config)
223
+ }, [isLoading, targetNode])
224
+
225
+ return (
226
+ <>
227
+ <Alert severity="info">
228
+ <AlertTitle>Sensitive values are masked.</AlertTitle>
229
+ You can see the raw log data connecting to the machine and running{' '}
230
+ <strong>'docker logs --since 5m -f dblab_server'</strong>.
231
+ </Alert>
232
+ {window.innerWidth > LAPTOP_WIDTH_PX && (
233
+ <>
234
+ <section className={classes.filterSection}>
235
+ {Object.keys(state)
236
+ .slice(0, 3)
237
+ .map((key) => (
238
+ <FormCheckbox
239
+ key={key}
240
+ type={key.replace('[', '').replace(']', '')}
241
+ />
242
+ ))}
243
+ </section>
244
+ <section
245
+ className={classNames(classes.filterSection, classes.utilFilter)}
246
+ >
247
+ {Object.keys(state)
248
+ .slice(3, 10)
249
+ .map((key) => (
250
+ <FormCheckbox
251
+ key={key}
252
+ type={key.replace('[', '').replace(']', '')}
253
+ />
254
+ ))}
255
+ </section>
256
+ </>
257
+ )}
258
+ <div id="logs-container" className={classes.logsContainer}>
259
+ {isLoading ? (
260
+ <div className={classes.spinnerContainer}>
261
+ <Spinner />
262
+ </div>
263
+ ) : null}
264
+ </div>
265
+ </>
266
+ )
267
+ }
@@ -0,0 +1,20 @@
1
+ export const stringWithoutBrackets = (val: string | undefined) =>
2
+ String(val).replace(/[\[\]]/g, '')
3
+
4
+ export const stringContainsPattern = (
5
+ target: string,
6
+ pattern = [
7
+ 'base.go',
8
+ 'runners.go',
9
+ 'snapshots.go',
10
+ 'util.go',
11
+ 'logging.go',
12
+ 'ws.go',
13
+ ],
14
+ ) => {
15
+ let value: number = 0
16
+ pattern.forEach(function (word) {
17
+ value = value + Number(target?.includes(word))
18
+ })
19
+ return value === 1
20
+ }
@@ -0,0 +1,110 @@
1
+ import moment from 'moment'
2
+
3
+ import {
4
+ LOGS_ENDPOINT,
5
+ LOGS_LINE_LIMIT,
6
+ LOGS_TIME_LIMIT,
7
+ } from '@postgres.ai/shared/pages/Logs/constants'
8
+ import { Api } from '@postgres.ai/shared/pages/Instance/stores/Main'
9
+ import {
10
+ stringWithoutBrackets,
11
+ stringContainsPattern,
12
+ } from '@postgres.ai/shared/pages/Logs/utils'
13
+
14
+ export const establishConnection = async (api: Api) => {
15
+ const logElement = document.getElementById('logs-container')
16
+
17
+ if (logElement === null) {
18
+ console.log('Not found container element');
19
+ return
20
+ }
21
+
22
+ const appendLogElement = (logEntry: string, logType?: string) => {
23
+ const tag = document.createElement('p')
24
+ const logLevel = logEntry.split(' ')[3]
25
+ const logInitiator = logEntry.split(' ')[2]
26
+ const logsFilterState = JSON.parse(localStorage.getItem('logsFilter') || '')
27
+
28
+ const filterInitiators = Object.keys(logsFilterState).some((state) => {
29
+ if (logsFilterState[state]) {
30
+ if (state === '[other]') {
31
+ return !stringContainsPattern(logInitiator)
32
+ }
33
+ return logInitiator?.includes(stringWithoutBrackets(state))
34
+ }
35
+ })
36
+
37
+ if (
38
+ filterInitiators &&
39
+ (logsFilterState[logInitiator] ||
40
+ logsFilterState[logLevel] ||
41
+ logEntry === 'Connection Error')
42
+ ) {
43
+ tag.appendChild(document.createTextNode(logEntry))
44
+ logElement.appendChild(tag)
45
+ }
46
+
47
+ // we need to check both second and third element of logEntry,
48
+ // since the pattern of the response returned isn't always consistent
49
+ if (logInitiator === '[ERROR]' || logLevel === '[ERROR]') {
50
+ tag.classList.add('error-log')
51
+ }
52
+
53
+ if (logType === 'message') {
54
+ const logEntryTime = logElement.children[1]?.innerHTML
55
+ .split(' ')
56
+ .slice(0, 2)
57
+ .join(' ')
58
+
59
+ const timeDifference =
60
+ moment(logEntryTime).isValid() &&
61
+ moment.duration(moment.utc(Date.now()).diff(logEntryTime)).asMinutes()
62
+
63
+ if (
64
+ logElement.childElementCount > LOGS_LINE_LIMIT &&
65
+ Number(timeDifference) > LOGS_TIME_LIMIT
66
+ ) {
67
+ logElement.removeChild(logElement.children[1])
68
+ }
69
+ }
70
+ }
71
+
72
+ const { response, error } = await api.getWSToken({
73
+ instanceId: '',
74
+ })
75
+
76
+ if (error || response == null) {
77
+ console.log('Not authorized:', error);
78
+ appendLogElement('Not authorized')
79
+ return;
80
+ }
81
+
82
+ if (api.initWS == null) {
83
+ console.log('WebSocket Connection is not configured')
84
+ appendLogElement('WebSocket Connection is not configured')
85
+ return
86
+ }
87
+
88
+ const socket = api.initWS(LOGS_ENDPOINT, response.token)
89
+
90
+ socket.onopen = () => {
91
+ console.log('Successfully Connected');
92
+ }
93
+
94
+ socket.onclose = (event) => {
95
+ console.log('Socket Closed Connection: ', event);
96
+ socket.send('Client Closed')
97
+ appendLogElement('DBLab Connection Closed')
98
+ }
99
+
100
+ socket.onerror = (error) => {
101
+ console.log('Socket Error: ', error);
102
+
103
+ appendLogElement('Connection Error')
104
+ }
105
+
106
+ socket.onmessage = function (event) {
107
+ const logEntry = decodeURIComponent(atob(event.data))
108
+ appendLogElement(logEntry, 'message')
109
+ }
110
+ }
@@ -0,0 +1,27 @@
1
+ import { LOGS_NEW_DATA_MESSAGE } from '@postgres.ai/shared/pages/Logs/constants'
2
+
3
+ export const wsSnackbar = (clientAtBottom: boolean, isNewData: boolean) => {
4
+ const targetNode = document.getElementById('logs-container')
5
+ const snackbarTag = document.createElement('div')
6
+
7
+ if (!clientAtBottom && isNewData) {
8
+ if (!targetNode?.querySelector('.snackbar-tag')) {
9
+ targetNode?.appendChild(snackbarTag)
10
+ snackbarTag.classList.add('snackbar-tag')
11
+ if (
12
+ snackbarTag.childNodes.length === 0 &&
13
+ targetNode?.querySelector('p')?.textContent !== 'Not authorized'
14
+ ) {
15
+ snackbarTag.appendChild(document.createTextNode(LOGS_NEW_DATA_MESSAGE))
16
+ }
17
+ snackbarTag.onclick = () => {
18
+ targetNode?.scroll({
19
+ top: targetNode.scrollHeight,
20
+ behavior: 'smooth',
21
+ })
22
+ }
23
+ }
24
+ } else {
25
+ targetNode?.querySelector('.snackbar-tag')?.remove()
26
+ }
27
+ }
Binary file
@@ -0,0 +1,71 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="react" />
3
+ /// <reference types="react-dom" />
4
+
5
+ declare namespace NodeJS {
6
+ interface ProcessEnv {
7
+ readonly NODE_ENV: 'development' | 'production' | 'test';
8
+ readonly PUBLIC_URL: string;
9
+ }
10
+ }
11
+
12
+ declare module '*.avif' {
13
+ const src: string;
14
+ export default src;
15
+ }
16
+
17
+ declare module '*.bmp' {
18
+ const src: string;
19
+ export default src;
20
+ }
21
+
22
+ declare module '*.gif' {
23
+ const src: string;
24
+ export default src;
25
+ }
26
+
27
+ declare module '*.jpg' {
28
+ const src: string;
29
+ export default src;
30
+ }
31
+
32
+ declare module '*.jpeg' {
33
+ const src: string;
34
+ export default src;
35
+ }
36
+
37
+ declare module '*.png' {
38
+ const src: string;
39
+ export default src;
40
+ }
41
+
42
+ declare module '*.webp' {
43
+ const src: string;
44
+ export default src;
45
+ }
46
+
47
+ declare module '*.svg' {
48
+ import * as React from 'react';
49
+
50
+ export const ReactComponent: React.FunctionComponent<React.SVGProps<
51
+ SVGSVGElement
52
+ > & { title?: string }>;
53
+
54
+ const src: string;
55
+ export default src;
56
+ }
57
+
58
+ declare module '*.module.css' {
59
+ const classes: { readonly [key: string]: string };
60
+ export default classes;
61
+ }
62
+
63
+ declare module '*.module.scss' {
64
+ const classes: { readonly [key: string]: string };
65
+ export default classes;
66
+ }
67
+
68
+ declare module '*.module.sass' {
69
+ const classes: { readonly [key: string]: string };
70
+ export default classes;
71
+ }
@@ -0,0 +1,30 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const glob = require('glob');
4
+
5
+ const OUT_DIR = 'dist';
6
+
7
+ const PATTERNS = [
8
+ '**/*.scss',
9
+ '**/*.module.scss',
10
+ '**/*.json',
11
+ 'react-app-env.d.ts',
12
+ ];
13
+
14
+ const files = PATTERNS.flatMap(pattern =>
15
+ glob.sync(pattern, {
16
+ cwd: '.',
17
+ ignore: ['node_modules/**', 'dist/**'],
18
+ nodir: true,
19
+ })
20
+ );
21
+
22
+ files.forEach((file) => {
23
+ const from = path.resolve(file);
24
+ const to = path.join(OUT_DIR, file);
25
+ const dir = path.dirname(to);
26
+ fs.mkdirSync(dir, { recursive: true });
27
+ fs.copyFileSync(from, to);
28
+ });
29
+
30
+ console.log(`✅ Copied ${files.length} assets to dist`);
@@ -0,0 +1,70 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { execSync } = require('child_process');
4
+
5
+ const TMP_DIR = 'build-tmp';
6
+ const DIST_DIR = 'dist';
7
+ const PACKAGE_JSON = 'package.json';
8
+
9
+ function cleanTmp() {
10
+ if (fs.existsSync(TMP_DIR)) {
11
+ fs.rmSync(TMP_DIR, { recursive: true, force: true });
12
+ }
13
+ }
14
+
15
+ function run(cmd, options = {}) {
16
+ console.log(`$ ${cmd}`);
17
+ execSync(cmd, { stdio: 'inherit', ...options });
18
+ }
19
+
20
+ function copyDistToTmp() {
21
+ run(`rsync -a ${DIST_DIR}/ ${TMP_DIR}/`);
22
+ }
23
+
24
+ function copyExtraFiles() {
25
+ const extras = ['react-app-env.d.ts'];
26
+ extras.forEach((file) => {
27
+ if (fs.existsSync(file)) {
28
+ fs.copyFileSync(file, path.join(TMP_DIR, file));
29
+ }
30
+ });
31
+ }
32
+
33
+ function sanitizePackageJson() {
34
+ const original = JSON.parse(fs.readFileSync(PACKAGE_JSON, 'utf8'));
35
+ const cleaned = {
36
+ name: original.name,
37
+ version: original.version,
38
+ description: original.description,
39
+ author: original.author,
40
+ license: original.license,
41
+ main: original.main || 'index.js',
42
+ types: original.types || 'index.d.ts',
43
+ peerDependencies: original.peerDependencies,
44
+ dependencies: original.dependencies,
45
+ };
46
+
47
+ fs.writeFileSync(
48
+ path.join(TMP_DIR, 'package.json'),
49
+ JSON.stringify(cleaned, null, 2),
50
+ 'utf8'
51
+ );
52
+ }
53
+
54
+ function pack() {
55
+ run('npm pack', { cwd: TMP_DIR });
56
+ const tarball = fs.readdirSync(TMP_DIR).find(f => f.endsWith('.tgz'));
57
+ fs.renameSync(path.join(TMP_DIR, tarball), path.join('.', tarball));
58
+ console.log(`✅ Packed to ./${tarball}`);
59
+ }
60
+
61
+ function buildTmpAndPack() {
62
+ cleanTmp();
63
+ run('pnpm run build');
64
+ copyDistToTmp();
65
+ sanitizePackageJson();
66
+ pack();
67
+ cleanTmp();
68
+ }
69
+
70
+ buildTmpAndPack();