@fdm-monster/client-next 0.0.1

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 (205) hide show
  1. package/.all-contributorsrc +57 -0
  2. package/.browserslistrc +4 -0
  3. package/.editorconfig +5 -0
  4. package/.env +1 -0
  5. package/.eslintrc-auto-import.json +73 -0
  6. package/.eslintrc.js +126 -0
  7. package/.github/FUNDING.yml +3 -0
  8. package/.github/workflows/release-client.yml +94 -0
  9. package/.github/workflows/vue-publish.yml +26 -0
  10. package/.prettierignore +15 -0
  11. package/.prettierrc.cjs +7 -0
  12. package/.whitesource +12 -0
  13. package/.yarn/releases/yarn-4.5.1.cjs +934 -0
  14. package/.yarnrc.yml +3 -0
  15. package/CODE_OF_CONDUCT.md +46 -0
  16. package/README.md +93 -0
  17. package/RELEASE_NOTES.MD +11 -0
  18. package/index.html +16 -0
  19. package/package.default.json +42 -0
  20. package/package.json +26 -0
  21. package/public/favicon.ico +0 -0
  22. package/public/img/DavidZwart.jpg +0 -0
  23. package/public/img/OIG.JYDC2RaWdz7g9.jpg +0 -0
  24. package/public/img/OIG.jpg +0 -0
  25. package/public/img/icons/android-chrome-192x192.png +0 -0
  26. package/public/img/icons/android-chrome-256x256.png +0 -0
  27. package/public/img/icons/android-chrome-384x384.png +0 -0
  28. package/public/img/icons/android-chrome-512x512.png +0 -0
  29. package/public/img/icons/favicon.svg +1 -0
  30. package/public/img/logo.png +0 -0
  31. package/public/img/logo.svg +1 -0
  32. package/public/img/manifest.webmanifest +33 -0
  33. package/public/img/octoprint-tentacle.svg +144 -0
  34. package/public/img/thumbail_unknown.jpg +0 -0
  35. package/public/img/vbanner.jpg +0 -0
  36. package/public/index.html +17 -0
  37. package/public/robots.txt +2 -0
  38. package/renovate.json +30 -0
  39. package/src/App.vue +60 -0
  40. package/src/AppLoader.vue +383 -0
  41. package/src/assets/adjectives.json +1468 -0
  42. package/src/assets/android-chrome-192x192.png +0 -0
  43. package/src/assets/logo.png +0 -0
  44. package/src/assets/logo.svg +6 -0
  45. package/src/assets/nouns.json +4309 -0
  46. package/src/auto-imports.d.ts +139 -0
  47. package/src/backend/app.service.ts +39 -0
  48. package/src/backend/auth.service.ts +56 -0
  49. package/src/backend/base.service.ts +57 -0
  50. package/src/backend/batch.service.ts +37 -0
  51. package/src/backend/camera-stream.service.ts +33 -0
  52. package/src/backend/custom-gcode.service.ts +11 -0
  53. package/src/backend/dto/octoprint-settings.dto.ts +168 -0
  54. package/src/backend/first-time-setup.service.ts +17 -0
  55. package/src/backend/floor.service.ts +84 -0
  56. package/src/backend/index.ts +4 -0
  57. package/src/backend/print-completions.service.ts +11 -0
  58. package/src/backend/printer-file.service.ts +91 -0
  59. package/src/backend/printer-group.service.ts +62 -0
  60. package/src/backend/printer-job.service.ts +20 -0
  61. package/src/backend/printer-settings.service.ts +28 -0
  62. package/src/backend/printers.service.ts +136 -0
  63. package/src/backend/server-private.service.ts +55 -0
  64. package/src/backend/server.api.ts +132 -0
  65. package/src/backend/settings.service.ts +85 -0
  66. package/src/backend/user.service.ts +51 -0
  67. package/src/components/AboutHelp/AboutView.vue +164 -0
  68. package/src/components/CameraGrid/CameraGridView.vue +111 -0
  69. package/src/components/FirstTimeSetup/FirstTimeSetupView.vue +354 -0
  70. package/src/components/Generic/Actions/PrinterConnectionAction.vue +56 -0
  71. package/src/components/Generic/Actions/PrinterCreateAction.vue +22 -0
  72. package/src/components/Generic/Actions/PrinterDeleteAction.vue +29 -0
  73. package/src/components/Generic/Actions/PrinterQuickStopAction.vue +35 -0
  74. package/src/components/Generic/Actions/PrinterSettingsAction.vue +35 -0
  75. package/src/components/Generic/Actions/PrinterUrlAction.vue +24 -0
  76. package/src/components/Generic/Actions/RefreshFilesAction.vue +50 -0
  77. package/src/components/Generic/Actions/SyncPrinterNameAction.vue +36 -0
  78. package/src/components/Generic/Dialogs/AddOrUpdateCameraStreamDialog.vue +131 -0
  79. package/src/components/Generic/Dialogs/AddOrUpdateFloorDialog.vue +141 -0
  80. package/src/components/Generic/Dialogs/AddOrUpdatePrinterDialog.vue +303 -0
  81. package/src/components/Generic/Dialogs/BaseDialog.vue +81 -0
  82. package/src/components/Generic/Dialogs/BatchJsonCreateDialog.vue +109 -0
  83. package/src/components/Generic/Dialogs/BatchReprintDialog.vue +190 -0
  84. package/src/components/Generic/Dialogs/PrinterChecksPanel.vue +37 -0
  85. package/src/components/Generic/Dialogs/PrinterControlDialog.vue +202 -0
  86. package/src/components/Generic/Dialogs/PrinterMaintenanceDialog.vue +130 -0
  87. package/src/components/Generic/Dialogs/YamlImportExportDialog.vue +186 -0
  88. package/src/components/Generic/Dialogs/dialog.constants.ts +19 -0
  89. package/src/components/Generic/FileExplorerSideNav.vue +734 -0
  90. package/src/components/Generic/Loaders/GridLoader.vue +68 -0
  91. package/src/components/Generic/NavigationDrawer.vue +69 -0
  92. package/src/components/Generic/PrintJobsMenu.vue +148 -0
  93. package/src/components/Generic/Snackbars/AppErrorSnackbar.vue +64 -0
  94. package/src/components/Generic/Snackbars/AppInfoSnackbar.vue +63 -0
  95. package/src/components/Generic/Snackbars/AppProgressSnackbar.vue +158 -0
  96. package/src/components/Generic/Vuetify/TooltipButton.vue +47 -0
  97. package/src/components/HelpOverlay/HelpOverlay.vue +57 -0
  98. package/src/components/Login/LoginForm.vue +206 -0
  99. package/src/components/Login/LoginView.spec.ts +64 -0
  100. package/src/components/Login/LoginView.vue +65 -0
  101. package/src/components/Login/Logo.vue +13 -0
  102. package/src/components/Login/PermissionDenied.vue +109 -0
  103. package/src/components/Login/RegistrationForm.vue +207 -0
  104. package/src/components/Login/RegistrationView.vue +17 -0
  105. package/src/components/Login/__snapshots__/LoginView.spec.ts.snap +1051 -0
  106. package/src/components/NotFound/NotFoundView.vue +39 -0
  107. package/src/components/PrintStatistics/PrintStatistics.vue +168 -0
  108. package/src/components/PrintStatistics/PrintStatisticsView.vue +15 -0
  109. package/src/components/PrinterGrid/HomeToolbar.vue +90 -0
  110. package/src/components/PrinterGrid/PrinterGrid.vue +164 -0
  111. package/src/components/PrinterGrid/PrinterGridTile.vue +438 -0
  112. package/src/components/PrinterGrid/PrinterGridView.vue +210 -0
  113. package/src/components/PrinterList/FileControlList.vue +40 -0
  114. package/src/components/PrinterList/PrinterDetails.vue +91 -0
  115. package/src/components/PrinterList/PrintersView.vue +492 -0
  116. package/src/components/Settings/AccountSettings.vue +163 -0
  117. package/src/components/Settings/DiagnosticsSettings.vue +137 -0
  118. package/src/components/Settings/EmergencyCommands.vue +265 -0
  119. package/src/components/Settings/FloorSettings.vue +276 -0
  120. package/src/components/Settings/GridSettings.vue +127 -0
  121. package/src/components/Settings/OctoPrintSettings.vue +188 -0
  122. package/src/components/Settings/ServerProtectionSettings.vue +370 -0
  123. package/src/components/Settings/SettingsView.vue +73 -0
  124. package/src/components/Settings/SoftwareUpgradeSettings.vue +297 -0
  125. package/src/components/Settings/UserManagementSettings.vue +257 -0
  126. package/src/components/TopBar.vue +147 -0
  127. package/src/components.d.ts +70 -0
  128. package/src/directives/file-upload.directive.ts +117 -0
  129. package/src/directives/printer-drop-position.directive.ts +92 -0
  130. package/src/env.d.ts +6 -0
  131. package/src/main.ts +76 -0
  132. package/src/models/batch/reprint.dto.ts +79 -0
  133. package/src/models/batch.model.ts +11 -0
  134. package/src/models/camera-streams/camera-stream.ts +19 -0
  135. package/src/models/floors/floor.model.ts +30 -0
  136. package/src/models/octoprint/connection-options.model.ts +8 -0
  137. package/src/models/plugins/firmware-updates/prusa-firmware-release.model.ts +57 -0
  138. package/src/models/print-completions/print-completions.model.ts +49 -0
  139. package/src/models/printers/crud/create-printer.model.ts +26 -0
  140. package/src/models/printers/file-upload-commands.model.ts +4 -0
  141. package/src/models/printers/gcode/gcode-analysis.model.ts +30 -0
  142. package/src/models/printers/printer-current-job.model.ts +90 -0
  143. package/src/models/printers/printer-file.model.ts +48 -0
  144. package/src/models/printers/printer.model.ts +18 -0
  145. package/src/models/server/client-releases.model.ts +27 -0
  146. package/src/models/server/export-yaml.model.ts +11 -0
  147. package/src/models/server/features.model.ts +37 -0
  148. package/src/models/server/github-rate-limit.model.ts +21 -0
  149. package/src/models/server/version.model.ts +14 -0
  150. package/src/models/settings/printer-file-clean-settings.model.ts +5 -0
  151. package/src/models/settings/server-settings.dto.ts +19 -0
  152. package/src/models/settings/settings.model.ts +57 -0
  153. package/src/models/socketio-messages/socketio-message.model.ts +53 -0
  154. package/src/models/uploads/queued-upload.model.ts +12 -0
  155. package/src/models/user.model.ts +15 -0
  156. package/src/plugins/README.md +3 -0
  157. package/src/plugins/index.ts +17 -0
  158. package/src/plugins/vuetify.ts +53 -0
  159. package/src/router/index.ts +192 -0
  160. package/src/router/route-names.ts +14 -0
  161. package/src/router/utils.ts +23 -0
  162. package/src/shared/alert.events.ts +14 -0
  163. package/src/shared/app.constants.ts +23 -0
  164. package/src/shared/auth.constants.ts +34 -0
  165. package/src/shared/dialog.composable.ts +41 -0
  166. package/src/shared/drag.constants.ts +19 -0
  167. package/src/shared/experimental.constants.ts +1 -0
  168. package/src/shared/http-client.ts +162 -0
  169. package/src/shared/noun-adjectives.data.ts +24 -0
  170. package/src/shared/printer-grid.constants.ts +5 -0
  171. package/src/shared/printer-state.constants.ts +194 -0
  172. package/src/shared/snackbar.composable.ts +66 -0
  173. package/src/shared/socketio.service.ts +104 -0
  174. package/src/store/auth.store.ts +255 -0
  175. package/src/store/connection.store.ts +66 -0
  176. package/src/store/dialog.store.ts +114 -0
  177. package/src/store/features.store.ts +57 -0
  178. package/src/store/floor.store.ts +173 -0
  179. package/src/store/grid.store.ts +10 -0
  180. package/src/store/index.ts +4 -0
  181. package/src/store/printer-state.store.ts +246 -0
  182. package/src/store/printer.store.ts +236 -0
  183. package/src/store/profile.store.ts +25 -0
  184. package/src/store/settings.store.ts +64 -0
  185. package/src/store/test-printer.store.ts +70 -0
  186. package/src/store/uploads.store.ts +75 -0
  187. package/src/styles/README.md +3 -0
  188. package/src/styles/settings.scss +10 -0
  189. package/src/types/global.d.ts +15 -0
  190. package/src/utils/array.utils.ts +15 -0
  191. package/src/utils/date.utils.ts +5 -0
  192. package/src/utils/download-file.util.ts +25 -0
  193. package/src/utils/error.utils.ts +3 -0
  194. package/src/utils/file-size.util.ts +11 -0
  195. package/src/utils/id.type.ts +1 -0
  196. package/src/utils/sentry.util.ts +8 -0
  197. package/src/utils/test.util.ts +30 -0
  198. package/src/utils/time.utils.ts +2 -0
  199. package/src/utils/uploads-state.utils.ts +58 -0
  200. package/src/utils/validation.utils.ts +14 -0
  201. package/src/vite-env.d.ts +7 -0
  202. package/test/setup-axios-mock.ts +15 -0
  203. package/tsconfig.json +47 -0
  204. package/tsconfig.node.json +9 -0
  205. package/vite.config.mts +106 -0
@@ -0,0 +1,297 @@
1
+ <template>
2
+ <v-card>
3
+ <v-toolbar color="primary">
4
+ <v-avatar>
5
+ <v-icon>upgrade</v-icon>
6
+ </v-avatar>
7
+ <v-toolbar-title> Software Upgrade </v-toolbar-title>
8
+ </v-toolbar>
9
+ <v-list lines="three">
10
+ <v-list-item>
11
+ <v-list-item-title> Current versions in use: </v-list-item-title>
12
+ <v-list-item-subtitle>
13
+ Your server's version is: {{ serverVersion }}
14
+ </v-list-item-subtitle>
15
+ <v-list-item-subtitle>
16
+ Your client's version is: {{ version }}
17
+ </v-list-item-subtitle>
18
+ <v-list-item-subtitle>
19
+ <div v-if="monsterPiVersion">
20
+ MonsterPi:
21
+ <div>Your MonsterPi version is:</div>
22
+ {{ monsterPiVersion }}
23
+ </div>
24
+ <div v-else>
25
+ MonsterPi:
26
+ <div>No MonsterPi distro was detected.</div>
27
+ </div>
28
+ </v-list-item-subtitle>
29
+ </v-list-item>
30
+ </v-list>
31
+ <v-divider />
32
+ <v-list
33
+ subheader
34
+ lines="three"
35
+ >
36
+ <v-list-item>
37
+ <v-list-item-title> Server upgrade </v-list-item-title>
38
+ <v-list-item-subtitle>
39
+ Please visit
40
+ <a href="https://docs.fdm-monster.net/docs/installing/">
41
+ the installation documentation
42
+ </a>
43
+ for instructions on how to upgrade the server.
44
+ </v-list-item-subtitle>
45
+ </v-list-item>
46
+ </v-list>
47
+ <v-divider />
48
+ <v-list lines="three">
49
+ <v-list-item>
50
+ <v-list-item-title> Client upgrade </v-list-item-title>
51
+ <v-list-item-subtitle>
52
+ Please visit
53
+ <a
54
+ href="https://docs.fdm-monster.net/docs/configuration/updating_client_bundle"
55
+ >
56
+ the installation documentation
57
+ </a>
58
+ for instructions on how to upgrade the client bundle.
59
+ </v-list-item-subtitle>
60
+ <v-list-item-subtitle>
61
+ Upgrade the client webapp for quickly retrieving small fixes and
62
+ features
63
+ </v-list-item-subtitle>
64
+ </v-list-item>
65
+ <v-list-item>
66
+ <v-list-item-title> Select a release to upgrade to: </v-list-item-title>
67
+ <v-list-item-subtitle class="mt-2">
68
+ Minimum required version: {{ minimum?.tag_name }}
69
+ </v-list-item-subtitle>
70
+
71
+ <span v-if="loading">
72
+ <v-alert> Loading releases... </v-alert>
73
+ </span>
74
+ <v-alert v-if="!loading && !filteredReleases?.length">
75
+ No releases to show.
76
+ </v-alert>
77
+ <v-radio-group v-model="selectedRelease">
78
+ <v-radio
79
+ v-for="release in filteredReleases"
80
+ :key="release.tag_name"
81
+ :disabled="isDisabledRelease(release)"
82
+ :label="calculateLabelDisabledReason(release)"
83
+ :value="release.tag_name"
84
+ />
85
+ </v-radio-group>
86
+ <div>
87
+ <v-alert
88
+ v-if="showPrereleases"
89
+ color="primary"
90
+ max-width="500px"
91
+ >
92
+ You are viewing prereleases, please install such versions at your
93
+ own risk!
94
+ </v-alert>
95
+ </div>
96
+ <div>
97
+ <v-checkbox
98
+ v-model="allowDowngrade"
99
+ label="Allow downgrade"
100
+ />
101
+ <v-checkbox
102
+ v-model="showPrereleases"
103
+ :disabled="getIsCurrentUnstable"
104
+ :label="
105
+ getIsCurrentUnstable
106
+ ? 'Show prerelease versions (Currently already on prerelease version)'
107
+ : 'Show prerelease versions'
108
+ "
109
+ />
110
+ </div>
111
+ <v-btn
112
+ class="mt-2 mr-4"
113
+ color="secondary"
114
+ @click="loadReleases()"
115
+ >
116
+ Reload release version list
117
+ </v-btn>
118
+ <v-btn
119
+ :disabled="
120
+ !selectedRelease?.length || selectedRelease === current?.tag_name
121
+ "
122
+ class="mt-2"
123
+ color="primary"
124
+ variant="flat"
125
+ @click="clickUpdateClient(selectedRelease)"
126
+ >
127
+ <v-icon>upgrade</v-icon>
128
+ Upgrade/downgrade client
129
+ </v-btn>
130
+ </v-list-item>
131
+ </v-list>
132
+ </v-card>
133
+ </template>
134
+ <script lang="ts" setup>
135
+ import { AppService } from '@/backend/app.service'
136
+ import { computed, onMounted, ref } from 'vue'
137
+ import { version as packageJsonVersion } from '../../../package.json'
138
+ import { IRelease } from '@/models/server/client-releases.model'
139
+ import { compare, minor } from 'semver'
140
+ import { useFeatureStore } from '@/store/features.store'
141
+
142
+ const errorMessage = ref('')
143
+ const loading = ref(true)
144
+ const rateLimitExceeded = ref(false)
145
+ const allowDowngrade = ref(false)
146
+ const serverVersion = ref('')
147
+ const monsterPiVersion = ref<string>('')
148
+ const version = ref(packageJsonVersion)
149
+ const current = ref<IRelease>()
150
+ const minimum = ref<IRelease>()
151
+ const selectedRelease = ref<string>()
152
+ const showPrereleases = ref<boolean>(false)
153
+ const loadedClientReleases = ref<IRelease[]>([])
154
+ const featureStore = useFeatureStore()
155
+
156
+ onMounted(async () => {
157
+ await loadReleases()
158
+
159
+ const versionSpec = await AppService.getVersion()
160
+ serverVersion.value = versionSpec.version
161
+ monsterPiVersion.value = versionSpec.monsterPi?.trim() || ''
162
+ })
163
+
164
+ async function loadReleases() {
165
+ loading.value = true
166
+ errorMessage.value = ''
167
+ rateLimitExceeded.value = false
168
+
169
+ if (featureStore.hasFeature('githubRateLimitApi')) {
170
+ try {
171
+ const rateLimit = await AppService.getGithubRateLimit()
172
+ if (rateLimit.rate.remaining === 0) {
173
+ const limitResetAt = new Date(rateLimit.rate.reset)
174
+ const time = limitResetAt.toLocaleTimeString()
175
+ const diff = rateLimit.rate.reset * 1000 - Date.now()
176
+ const diffMinutes = Math.ceil(diff / 60000)
177
+ errorMessage.value = `Server has reached a rate limit of the Github API. This limit will be reset at ${time} (in ${diffMinutes} minutes)`
178
+ loading.value = false
179
+ rateLimitExceeded.value = true
180
+ return
181
+ }
182
+ } catch (e) {
183
+ loading.value = false
184
+ return
185
+ }
186
+ }
187
+
188
+ try {
189
+ const clientReleases = await AppService.getClientReleases()
190
+ current.value = clientReleases.current
191
+ minimum.value = clientReleases.minimum
192
+ loadedClientReleases.value = clientReleases.releases
193
+ } catch (e: any) {
194
+ errorMessage.value = 'An error occurred loading the releases: ' + e.message
195
+ } finally {
196
+ loading.value = false
197
+ }
198
+ }
199
+
200
+ const filteredReleases = computed(() => {
201
+ return loadedClientReleases.value.filter((release) => {
202
+ const isMinimumVersionOrHigher =
203
+ minor(release.tag_name) === minor(minimum.value!.tag_name)
204
+ const isReleaseCandidate = isVersionUnstable(release)
205
+ const isDraft = release.draft
206
+
207
+ return (
208
+ isMinimumVersionOrHigher &&
209
+ (isCurrentUnstable() || showPrereleases.value || !isReleaseCandidate) &&
210
+ !isDraft
211
+ )
212
+ })
213
+ })
214
+
215
+ const getIsCurrentUnstable = computed(() => {
216
+ return isCurrentUnstable()
217
+ })
218
+
219
+ function isCurrentUnstable() {
220
+ // Determine if current is rc/unstable, meaning we should ignore prerelease filter checkbox
221
+ const currentRelease = current.value
222
+ return isVersionUnstable(currentRelease)
223
+ }
224
+
225
+ function isDisabledRelease(release: IRelease) {
226
+ return (
227
+ isCurrentRelease(release) ||
228
+ !isUpgradeOrAllowedDowngrade(release, current.value) ||
229
+ isBelowMinimum(release)
230
+ )
231
+ }
232
+
233
+ function calculateLabelDisabledReason(release: IRelease) {
234
+ const prefix = isVersionUnstable(release)
235
+ ? `${release.tag_name}, unstable`
236
+ : release.tag_name
237
+
238
+ if (isCurrentRelease(release)) {
239
+ return `${prefix}, currently installed`
240
+ }
241
+ if (isBelowMinimum(release)) {
242
+ return `${prefix}, below minimum`
243
+ }
244
+ if (!isUpgradeOrAllowedDowngrade(release, current.value)) {
245
+ return `${prefix}, downgrade not allowed`
246
+ }
247
+
248
+ return prefix
249
+ }
250
+
251
+ function isVersionUnstable(release?: IRelease) {
252
+ if (release?.tag_name?.length) {
253
+ return (
254
+ release.prerelease ||
255
+ release.tag_name.includes('rc') ||
256
+ release.tag_name.includes('unstable')
257
+ )
258
+ }
259
+ return false
260
+ }
261
+
262
+ function isBelowMinimum(release: IRelease) {
263
+ return compare(release.tag_name, minimum.value!.tag_name) === -1
264
+ }
265
+
266
+ function isUpgradeOrAllowedDowngrade(release: IRelease, current?: IRelease) {
267
+ // If no current release is known, we need to throw
268
+ if (!current) {
269
+ throw new Error('No current release is known, cannot compare.')
270
+ }
271
+ if (allowDowngrade.value) {
272
+ return true
273
+ }
274
+
275
+ return (
276
+ compare(release.tag_name, current.tag_name) !== -1 &&
277
+ compare(release.tag_name, minimum.value!.tag_name) !== -1
278
+ )
279
+ }
280
+
281
+ function isCurrentRelease(release: IRelease) {
282
+ return release.tag_name === current.value?.tag_name
283
+ }
284
+
285
+ async function clickUpdateClient(version?: string) {
286
+ if (
287
+ !confirm(
288
+ 'Are you sure? This might cause breaking changes, if the server is outdated'
289
+ )
290
+ ) {
291
+ return
292
+ }
293
+
294
+ await AppService.updateClientDistGithub(version, allowDowngrade.value)
295
+ location.reload()
296
+ }
297
+ </script>
@@ -0,0 +1,257 @@
1
+ <template>
2
+ <v-card>
3
+ <v-toolbar color="primary">
4
+ <v-avatar>
5
+ <v-icon>settings</v-icon>
6
+ </v-avatar>
7
+ <v-toolbar-title> Users </v-toolbar-title>
8
+ </v-toolbar>
9
+ <GridLoader
10
+ v-if="loading"
11
+ :size="20"
12
+ color="#a70015"
13
+ style="margin: 250px; position: absolute"
14
+ />
15
+ <v-list lines="three">
16
+ <v-list-subheader> Showing all users </v-list-subheader>
17
+
18
+ <v-list-item
19
+ v-for="(user, index) in users"
20
+ :key="index"
21
+ class="pl-6"
22
+ style="
23
+ max-width: 800px;
24
+ border-top: 1px solid grey;
25
+ border-bottom: 1px solid grey;
26
+ "
27
+ >
28
+ <v-list-item-content
29
+ :class="
30
+ isCurrentAccount(user) ? 'pl-6 grey darken-3' : 'pl-6 grey darken-4'
31
+ "
32
+ >
33
+ <v-list-item-title>
34
+ User '{{ user.username }}'
35
+ <strong
36
+ v-if="isCurrentAccount(user)"
37
+ class="text--primary float-end mr-5 mt-2"
38
+ >
39
+ Your account
40
+ </strong>
41
+ </v-list-item-title>
42
+ <v-list-item-subtitle />
43
+ <span class="text-grey-darken-4">
44
+ <ul>
45
+ <li>
46
+ <span
47
+ v-if="user.isVerified"
48
+ class="text-success"
49
+ >
50
+ Account verified
51
+ </span>
52
+ <span
53
+ v-else
54
+ class="text-error"
55
+ >
56
+ Account not verified
57
+ </span>
58
+ </li>
59
+ <li>Created at {{ formatIntlDate(user.createdAt) }}</li>
60
+ <li v-if="user.isDemoUser">Demo user account</li>
61
+
62
+ <li>
63
+ Role(s)
64
+ <ul>
65
+ <li v-if="user.isRootUser">
66
+ <v-chip
67
+ class="mb-2 mt-2"
68
+ size="small"
69
+ >
70
+ OWNER
71
+ </v-chip>
72
+ </li>
73
+ <li
74
+ v-for="role of convertRoles(user.roles)"
75
+ :key="role"
76
+ >
77
+ <v-chip
78
+ class="mb-2 mt-2"
79
+ size="small"
80
+ >
81
+ {{ role }}
82
+ </v-chip>
83
+ </li>
84
+ </ul>
85
+ </li>
86
+ </ul>
87
+ </span>
88
+ </v-list-item-content>
89
+ <v-list-item-action>
90
+ <v-btn
91
+ :disabled="isCurrentAccount(user) || user.isRootUser"
92
+ :color="user.isVerified ? 'error darken-4' : 'success'"
93
+ @click="verifyUser(user, !user.isVerified)"
94
+ >
95
+ <v-icon class="mr-2">shield</v-icon>
96
+ <span v-if="!user.isVerified"> Verify account </span>
97
+ <span v-if="user.isVerified"> Unverify account </span>
98
+ </v-btn>
99
+ <v-btn
100
+ :color="user.isRootUser ? 'error darken-4' : 'success'"
101
+ :disabled="isCurrentAccount(user) || !profile?.isRootUser"
102
+ class="mt-2"
103
+ @click="setRootUser(user, !user.isRootUser)"
104
+ >
105
+ <v-icon class="mr-2">key</v-icon>
106
+ <span v-if="user.isRootUser"> Remove owner </span>
107
+ <span v-if="!user.isRootUser"> Set owner </span>
108
+ </v-btn>
109
+ <v-btn
110
+ :disabled="isCurrentAccount(user) || user.isRootUser"
111
+ class="mt-2"
112
+ color="error-darken-2"
113
+ @click="deleteUser(user)"
114
+ >
115
+ <v-icon class="mr-2">delete</v-icon>
116
+ Delete
117
+ </v-btn>
118
+ </v-list-item-action>
119
+ </v-list-item>
120
+ </v-list>
121
+ </v-card>
122
+ </template>
123
+
124
+ <script lang="ts" setup>
125
+ import { ref } from 'vue'
126
+ import { UserService } from '@/backend/user.service'
127
+ import { Role, User } from '@/models/user.model'
128
+ import { formatIntlDate } from '@/utils/date.utils'
129
+ import GridLoader from '@/components/Generic/Loaders/GridLoader.vue'
130
+ import { useQuery } from '@tanstack/vue-query'
131
+ import { useSnackbar } from '@/shared/snackbar.composable'
132
+
133
+ const snackbar = useSnackbar()
134
+ const loading = ref<boolean>(false)
135
+ const profile = ref<User>()
136
+ const users = ref<User[]>([])
137
+ const roles = ref<Role[]>([])
138
+
139
+ async function loadData() {
140
+ loading.value = true
141
+ try {
142
+ profile.value = await UserService.getProfile()
143
+ roles.value = await UserService.listRoles()
144
+ users.value = await UserService.listUsers()
145
+ } catch (e) {
146
+ loading.value = false
147
+ console.error(e)
148
+ throw e
149
+ }
150
+
151
+ loading.value = false
152
+
153
+ return {
154
+ users,
155
+ roles,
156
+ profile
157
+ }
158
+ }
159
+
160
+ const userQuery = useQuery({
161
+ queryKey: ['userRolesProfile'],
162
+ queryFn: loadData
163
+ })
164
+
165
+ function convertRoles(roleIds: (string | number)[]): (string | undefined)[] {
166
+ return roleIds.map((roleId) => roles.value.find((r) => r.id == roleId)?.name)
167
+ }
168
+
169
+ function isCurrentAccount(user: User): boolean {
170
+ return user.id == profile.value?.id
171
+ }
172
+
173
+ async function deleteUser(user: User) {
174
+ if (!confirm(`Are you sure you want to delete ${user.username}?`)) {
175
+ return
176
+ }
177
+
178
+ try {
179
+ loading.value = true
180
+ await UserService.deleteUser(user.id)
181
+ await userQuery.refetch()
182
+ } catch (e) {
183
+ loading.value = false
184
+ console.error(e)
185
+ throw e
186
+ }
187
+ loading.value = false
188
+
189
+ snackbar.info(`User ${user.username} deleted`)
190
+ }
191
+
192
+ async function verifyUser(user: User, isVerified: boolean = true) {
193
+ if (user.isRootUser) {
194
+ snackbar.error('You are not allowed to do perform this action on an owner')
195
+ return
196
+ }
197
+ if (
198
+ !confirm(
199
+ `Are you sure you want to ${isVerified ? 'verify' : 'unverify'} ${user.username}?`
200
+ )
201
+ ) {
202
+ return
203
+ }
204
+
205
+ try {
206
+ loading.value = true
207
+ await UserService.setUserVerified(user.id, isVerified)
208
+ await userQuery.refetch()
209
+ } catch (e) {
210
+ loading.value = false
211
+ console.error(e)
212
+ throw e
213
+ }
214
+ loading.value = false
215
+
216
+ snackbar.info(
217
+ isVerified
218
+ ? `User ${user.username} verified`
219
+ : `User ${user.username} unverified`
220
+ )
221
+ }
222
+
223
+ async function setRootUser(user: User, isRootUser: boolean = true) {
224
+ if (!profile.value?.isRootUser) {
225
+ snackbar.error(
226
+ "You are not allowed to do perform this action as you're not an owner"
227
+ )
228
+ }
229
+
230
+ if (
231
+ !confirm(
232
+ `You are about to ${isRootUser ? 'set' : 'remove'} owner rights on ${
233
+ user.username
234
+ }. Are you sure?`
235
+ )
236
+ ) {
237
+ return
238
+ }
239
+
240
+ try {
241
+ loading.value = true
242
+ await UserService.setRootUser(user.id, isRootUser)
243
+ await userQuery.refetch()
244
+ } catch (e) {
245
+ loading.value = false
246
+ console.error(e)
247
+ throw e
248
+ }
249
+ loading.value = false
250
+
251
+ snackbar.info(
252
+ isRootUser
253
+ ? `User ${user.username} set to owner`
254
+ : `User ${user.username} is no longer owner`
255
+ )
256
+ }
257
+ </script>
@@ -0,0 +1,147 @@
1
+ <template>
2
+ <v-app-bar color="primary">
3
+ <v-toolbar-title class="text-uppercase text-white">
4
+ <span class="font-weight-light"> FDM </span>
5
+ <strong> Monster </strong>
6
+ </v-toolbar-title>
7
+
8
+ <v-spacer v-if="isDemoMode" />
9
+ <h2
10
+ v-if="isDemoMode"
11
+ class="text-uppercase text--white"
12
+ >
13
+ DEMO MODE
14
+ </h2>
15
+ <v-spacer />
16
+
17
+ <PrintJobsMenu />
18
+
19
+ <v-menu
20
+ v-if="authStore.hasAuthToken && !authStore.isLoginExpired"
21
+ :close-on-content-click="false"
22
+ location="bottom right"
23
+ open-on-hover
24
+ transition="slide-y-transition"
25
+ >
26
+ <template #activator="{ props }">
27
+ <!--Theme?-->
28
+ <v-btn
29
+ class="ml-2"
30
+ color="secondary"
31
+ theme="dark"
32
+ v-bind="props"
33
+ >
34
+ <v-icon class="mr-2">person</v-icon>
35
+ {{ username }}
36
+ </v-btn>
37
+ </template>
38
+
39
+ <v-list>
40
+ <v-list-item
41
+ v-for="(item, index) in items"
42
+ :key="index"
43
+ :to="item.path"
44
+ :title="item.title"
45
+ :prepend-avatar="item.icon"
46
+ link
47
+ />
48
+ </v-list>
49
+ </v-menu>
50
+
51
+ <span
52
+ v-if="isDevEnv && expiry"
53
+ class="ml-2"
54
+ >
55
+ AuthExp {{ expiry }}
56
+ </span>
57
+
58
+ <span
59
+ v-if="isDevEnv"
60
+ class="ml-2"
61
+ >
62
+ <small>
63
+ S{{ socketState.setup ? 1 : 0 }} C{{ socketState.connected ? 1 : 0 }}
64
+ {{ socketState.id }}
65
+ </small>
66
+ </span>
67
+
68
+ <TooltipButton
69
+ v-if="authStore.loginRequired === true"
70
+ tooltip="Go back to login"
71
+ text="Logout"
72
+ color="secondary"
73
+ icon="logout"
74
+ @click="logout()"
75
+ />
76
+
77
+ <v-btn
78
+ v-if="authStore.loginRequired === true"
79
+ class="ml-2"
80
+ >
81
+ <v-icon class="mr-2">logout</v-icon>
82
+ Logout
83
+ </v-btn>
84
+
85
+ <HelpOverlay />
86
+ </v-app-bar>
87
+ </template>
88
+
89
+ <script lang="ts" setup>
90
+ import { computed, ref } from 'vue'
91
+ import { useRouter } from 'vue-router'
92
+ import { useIntervalFn } from '@vueuse/core'
93
+ import PrintJobsMenu from '@/components/Generic/PrintJobsMenu.vue'
94
+ import { useAuthStore } from '@/store/auth.store'
95
+ import { useProfileStore } from '@/store/profile.store'
96
+ import { routeToLogin } from '@/router/utils'
97
+ import { isDevEnv, isProdEnv } from '@/shared/app.constants'
98
+ import { socketState } from '@/store/connection.store'
99
+
100
+ const profileStore = useProfileStore()
101
+ const authStore = useAuthStore()
102
+ const router = useRouter()
103
+ const items = [
104
+ { title: 'Open Profile', icon: 'person', path: '/settings/account' }
105
+ ]
106
+
107
+ const now = ref(Date.now())
108
+ if (isDevEnv) {
109
+ useIntervalFn(() => {
110
+ now.value = Date.now()
111
+ }, 1000)
112
+ }
113
+
114
+ const expiry = computed(() => {
115
+ if (isProdEnv) {
116
+ return ''
117
+ }
118
+ if (!authStore.tokenClaims?.exp) {
119
+ return ''
120
+ }
121
+ const diffValue = authStore.tokenClaims.exp - now.value / 1000
122
+ return `${Math.round(diffValue)}s`
123
+ })
124
+
125
+ const username = computed(() => {
126
+ return profileStore.username
127
+ })
128
+
129
+ const isDemoMode = computed(() => {
130
+ return authStore.isDemoMode
131
+ })
132
+
133
+ async function logout() {
134
+ await authStore.logout(true)
135
+ await routeToLogin(router)
136
+ }
137
+ </script>
138
+
139
+ <style lang="scss">
140
+ .border-time {
141
+ border: 1px solid white;
142
+
143
+ * {
144
+ border: none;
145
+ }
146
+ }
147
+ </style>