Skip to content

Commit f78fbe3

Browse files
authored
fix: disable start button on backup restore/create (#4582)
* fix: CLAUDE.md * fix: allowing start server on backup create/restore --------- Signed-off-by: Calum H. <contact@cal.engineer>
1 parent f375913 commit f78fbe3

File tree

4 files changed

+105
-20
lines changed

4 files changed

+105
-20
lines changed

CLAUDE.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,35 @@
11
# Architecture
22

3+
Use TAB instead of spaces.
4+
5+
## Frontend
6+
7+
There are two similar frontends in the Modrinth monorepo, the website (apps/frontend) and the app frontend (apps/app-frontend).
8+
9+
Both use Tailwind v3, and their respective configs can be seen at `tailwind.config.ts` and `tailwind.config.js` respectively.
10+
11+
Both utilize shared and common components from `@modrinth/ui` which can be found at `packages/ui`, and stylings from `@modrinth/assets` which can be found at `packages/assets`.
12+
13+
Both can utilize icons from `@modrinth/assets`, which are automatically generated based on what's available within the `icons` folder of the `packages/assets` directory. You can see the generated icons list in `generated-icons.ts`.
14+
15+
Both have access to our dependency injection framework, examples as seen in `packages/ui/src/providers/`. Ideally any state which is shared between a page and it's subpages should be shared using this dependency injection framework.
16+
17+
### Website (apps/frontend)
18+
19+
Before a pull request can be opened for the website, `pnpm web:fix` and `pnpm web:intl:extract` must be run, otherwise CI will fail.
20+
21+
To run a development version of the frontend, you must first copy over the relevant `.env` template file (prod, staging or local, usually prod) within the `apps/frontend` folder into `apps/frontend/.env`. Then you can run the frontend by running `pnpm web:dev` in the root folder.
22+
23+
### App Frontend (apps/app-frontend)
24+
25+
Before a pull request can be opened for the website, you must CD into the `app-frontend` folder; `pnpm fix` and `pnpm intl:extract` must be run, otherwise CI will fail.
26+
27+
To run a development version of the app frontend, you must first copy over the relevant `.env` template file (prod, staging or local, usually prod) within `packages/app-lib` into `packages/app-lib/.env`. Then you must run the app itself by running `pnpm app:dev` in the root folder.
28+
29+
### Localization
30+
31+
Refer to `.github/instructions/i18n-convert.instructions.md` if the user asks you to perform any i18n conversion work on a component, set of components, pages or sets of pages.
32+
333
## Labrinth
434

535
Labrinth is the backend API service for Modrinth.
@@ -15,6 +45,7 @@ To prepare the sqlx cache, cd into `apps/labrinth` and run `cargo sqlx prepare`.
1545
Read the root `docker-compose.yml` to see what running services are available while developing. Use `docker exec` to access these services.
1646

1747
When the user refers to "performing pre-PR checks", do the following:
48+
1849
- Run clippy as described above
1950
- DO NOT run tests unless explicitly requested (they take a long time)
2051
- Prepare the sqlx cache

apps/frontend/src/components/ui/servers/PanelServerActionButton.vue

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,11 @@
6868
</ButtonStyled>
6969

7070
<ButtonStyled type="standard" color="brand">
71-
<button :disabled="!canTakeAction" @click="handlePrimaryAction">
71+
<button
72+
v-tooltip="backupInProgress ? formatMessage(backupInProgress.tooltip) : undefined"
73+
:disabled="!canTakeAction"
74+
@click="handlePrimaryAction"
75+
>
7276
<div v-if="isTransitionState" class="grid place-content-center">
7377
<LoadingIcon />
7478
</div>
@@ -122,12 +126,15 @@ import { useStorage } from '@vueuse/core'
122126
import { computed, ref } from 'vue'
123127
import { useRouter } from 'vue-router'
124128
129+
import type { BackupInProgressReason } from '~/pages/servers/manage/[id].vue'
130+
125131
import LoadingIcon from './icons/LoadingIcon.vue'
126132
import PanelSpinner from './PanelSpinner.vue'
127133
import ServerInfoLabels from './ServerInfoLabels.vue'
128134
import TeleportOverflowMenu from './TeleportOverflowMenu.vue'
129135
130136
const flags = useFeatureFlags()
137+
const { formatMessage } = useVIntl()
131138
132139
interface PowerAction {
133140
action: ServerPowerAction
@@ -142,6 +149,7 @@ const props = defineProps<{
142149
serverName?: string
143150
serverData: object
144151
uptimeSeconds: number
152+
backupInProgress?: BackupInProgressReason
145153
}>()
146154
147155
const emit = defineEmits<{
@@ -163,7 +171,11 @@ const dontAskAgain = ref(false)
163171
const startingDelay = ref(false)
164172
165173
const canTakeAction = computed(
166-
() => !props.isActioning && !startingDelay.value && !isTransitionState.value,
174+
() =>
175+
!props.isActioning &&
176+
!startingDelay.value &&
177+
!isTransitionState.value &&
178+
!props.backupInProgress,
167179
)
168180
const isRunning = computed(() => serverState.value === 'running')
169181
const isTransitionState = computed(() =>

apps/frontend/src/composables/servers/modules/backups.ts

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,48 +11,83 @@ export class BackupsModule extends ServerModule {
1111
}
1212

1313
async create(backupName: string): Promise<string> {
14-
const response = await useServersFetch<{ id: string }>(`servers/${this.serverId}/backups`, {
15-
method: 'POST',
16-
body: { name: backupName },
17-
})
18-
await this.fetch() // Refresh this module
19-
return response.id
14+
const tempId = `temp-${Date.now()}-${Math.random().toString(36).substring(7)}`
15+
const tempBackup: Backup = {
16+
id: tempId,
17+
name: backupName,
18+
created_at: new Date().toISOString(),
19+
locked: false,
20+
automated: false,
21+
interrupted: false,
22+
ongoing: true,
23+
task: { create: { progress: 0, state: 'ongoing' } },
24+
}
25+
this.data.push(tempBackup)
26+
27+
try {
28+
const response = await useServersFetch<{ id: string }>(`servers/${this.serverId}/backups`, {
29+
method: 'POST',
30+
body: { name: backupName },
31+
})
32+
33+
const backup = this.data.find((b) => b.id === tempId)
34+
if (backup) {
35+
backup.id = response.id
36+
}
37+
38+
return response.id
39+
} catch (error) {
40+
this.data = this.data.filter((b) => b.id !== tempId)
41+
throw error
42+
}
2043
}
2144

2245
async rename(backupId: string, newName: string): Promise<void> {
2346
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/rename`, {
2447
method: 'POST',
2548
body: { name: newName },
2649
})
27-
await this.fetch() // Refresh this module
50+
await this.fetch()
2851
}
2952

3053
async delete(backupId: string): Promise<void> {
3154
await useServersFetch(`servers/${this.serverId}/backups/${backupId}`, {
3255
method: 'DELETE',
3356
})
34-
await this.fetch() // Refresh this module
57+
await this.fetch()
3558
}
3659

3760
async restore(backupId: string): Promise<void> {
38-
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/restore`, {
39-
method: 'POST',
40-
})
41-
await this.fetch() // Refresh this module
61+
const backup = this.data.find((b) => b.id === backupId)
62+
if (backup) {
63+
if (!backup.task) backup.task = {}
64+
backup.task.restore = { progress: 0, state: 'ongoing' }
65+
}
66+
67+
try {
68+
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/restore`, {
69+
method: 'POST',
70+
})
71+
} catch (error) {
72+
if (backup?.task?.restore) {
73+
delete backup.task.restore
74+
}
75+
throw error
76+
}
4277
}
4378

4479
async lock(backupId: string): Promise<void> {
4580
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/lock`, {
4681
method: 'POST',
4782
})
48-
await this.fetch() // Refresh this module
83+
await this.fetch()
4984
}
5085

5186
async unlock(backupId: string): Promise<void> {
5287
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/unlock`, {
5388
method: 'POST',
5489
})
55-
await this.fetch() // Refresh this module
90+
await this.fetch()
5691
}
5792

5893
async retry(backupId: string): Promise<void> {

apps/frontend/src/pages/servers/manage/[id].vue

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@
151151
:server-name="serverData.name"
152152
:server-data="serverData"
153153
:uptime-seconds="uptimeSeconds"
154+
:backup-in-progress="backupInProgress"
154155
@action="sendPowerAction"
155156
/>
156157
</div>
@@ -354,7 +355,7 @@
354355
>
355356
<h2 class="m-0 text-lg font-extrabold text-contrast">Server data</h2>
356357
<pre class="markdown-body w-full overflow-auto rounded-2xl bg-bg-raised p-4 text-sm">{{
357-
JSON.stringify(server, null, ' ')
358+
JSON.stringify(server, null, ' ')
358359
}}</pre>
359360
</div>
360361
</template>
@@ -759,9 +760,14 @@ const handleWebSocketMessage = (data: WSEvent) => {
759760
curBackup.task = {}
760761
}
761762
762-
curBackup.task[data.task] = {
763-
progress: data.progress,
764-
state: data.state,
763+
const currentState = curBackup.task[data.task]?.state
764+
const shouldUpdate = !(currentState === 'ongoing' && data.state === 'unchanged')
765+
766+
if (shouldUpdate) {
767+
curBackup.task[data.task] = {
768+
progress: data.progress,
769+
state: data.state,
770+
}
765771
}
766772
767773
curBackup.ongoing = data.task === 'create' && data.state === 'ongoing'
@@ -1277,6 +1283,7 @@ useHead({
12771283
opacity: 0;
12781284
transform: translateX(1rem);
12791285
}
1286+
12801287
100% {
12811288
opacity: 1;
12821289
transform: none;

0 commit comments

Comments
 (0)