You've already forked AstralRinth
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>
This commit is contained in:
31
CLAUDE.md
31
CLAUDE.md
@@ -1,5 +1,35 @@
|
||||
# Architecture
|
||||
|
||||
Use TAB instead of spaces.
|
||||
|
||||
## Frontend
|
||||
|
||||
There are two similar frontends in the Modrinth monorepo, the website (apps/frontend) and the app frontend (apps/app-frontend).
|
||||
|
||||
Both use Tailwind v3, and their respective configs can be seen at `tailwind.config.ts` and `tailwind.config.js` respectively.
|
||||
|
||||
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`.
|
||||
|
||||
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`.
|
||||
|
||||
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.
|
||||
|
||||
### Website (apps/frontend)
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
### App Frontend (apps/app-frontend)
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
### Localization
|
||||
|
||||
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.
|
||||
|
||||
## Labrinth
|
||||
|
||||
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`.
|
||||
Read the root `docker-compose.yml` to see what running services are available while developing. Use `docker exec` to access these services.
|
||||
|
||||
When the user refers to "performing pre-PR checks", do the following:
|
||||
|
||||
- Run clippy as described above
|
||||
- DO NOT run tests unless explicitly requested (they take a long time)
|
||||
- Prepare the sqlx cache
|
||||
|
||||
@@ -68,7 +68,11 @@
|
||||
</ButtonStyled>
|
||||
|
||||
<ButtonStyled type="standard" color="brand">
|
||||
<button :disabled="!canTakeAction" @click="handlePrimaryAction">
|
||||
<button
|
||||
v-tooltip="backupInProgress ? formatMessage(backupInProgress.tooltip) : undefined"
|
||||
:disabled="!canTakeAction"
|
||||
@click="handlePrimaryAction"
|
||||
>
|
||||
<div v-if="isTransitionState" class="grid place-content-center">
|
||||
<LoadingIcon />
|
||||
</div>
|
||||
@@ -122,12 +126,15 @@ import { useStorage } from '@vueuse/core'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import type { BackupInProgressReason } from '~/pages/servers/manage/[id].vue'
|
||||
|
||||
import LoadingIcon from './icons/LoadingIcon.vue'
|
||||
import PanelSpinner from './PanelSpinner.vue'
|
||||
import ServerInfoLabels from './ServerInfoLabels.vue'
|
||||
import TeleportOverflowMenu from './TeleportOverflowMenu.vue'
|
||||
|
||||
const flags = useFeatureFlags()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
interface PowerAction {
|
||||
action: ServerPowerAction
|
||||
@@ -142,6 +149,7 @@ const props = defineProps<{
|
||||
serverName?: string
|
||||
serverData: object
|
||||
uptimeSeconds: number
|
||||
backupInProgress?: BackupInProgressReason
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -163,7 +171,11 @@ const dontAskAgain = ref(false)
|
||||
const startingDelay = ref(false)
|
||||
|
||||
const canTakeAction = computed(
|
||||
() => !props.isActioning && !startingDelay.value && !isTransitionState.value,
|
||||
() =>
|
||||
!props.isActioning &&
|
||||
!startingDelay.value &&
|
||||
!isTransitionState.value &&
|
||||
!props.backupInProgress,
|
||||
)
|
||||
const isRunning = computed(() => serverState.value === 'running')
|
||||
const isTransitionState = computed(() =>
|
||||
|
||||
@@ -11,12 +11,35 @@ export class BackupsModule extends ServerModule {
|
||||
}
|
||||
|
||||
async create(backupName: string): Promise<string> {
|
||||
const response = await useServersFetch<{ id: string }>(`servers/${this.serverId}/backups`, {
|
||||
method: 'POST',
|
||||
body: { name: backupName },
|
||||
})
|
||||
await this.fetch() // Refresh this module
|
||||
return response.id
|
||||
const tempId = `temp-${Date.now()}-${Math.random().toString(36).substring(7)}`
|
||||
const tempBackup: Backup = {
|
||||
id: tempId,
|
||||
name: backupName,
|
||||
created_at: new Date().toISOString(),
|
||||
locked: false,
|
||||
automated: false,
|
||||
interrupted: false,
|
||||
ongoing: true,
|
||||
task: { create: { progress: 0, state: 'ongoing' } },
|
||||
}
|
||||
this.data.push(tempBackup)
|
||||
|
||||
try {
|
||||
const response = await useServersFetch<{ id: string }>(`servers/${this.serverId}/backups`, {
|
||||
method: 'POST',
|
||||
body: { name: backupName },
|
||||
})
|
||||
|
||||
const backup = this.data.find((b) => b.id === tempId)
|
||||
if (backup) {
|
||||
backup.id = response.id
|
||||
}
|
||||
|
||||
return response.id
|
||||
} catch (error) {
|
||||
this.data = this.data.filter((b) => b.id !== tempId)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async rename(backupId: string, newName: string): Promise<void> {
|
||||
@@ -24,35 +47,47 @@ export class BackupsModule extends ServerModule {
|
||||
method: 'POST',
|
||||
body: { name: newName },
|
||||
})
|
||||
await this.fetch() // Refresh this module
|
||||
await this.fetch()
|
||||
}
|
||||
|
||||
async delete(backupId: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/backups/${backupId}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
await this.fetch() // Refresh this module
|
||||
await this.fetch()
|
||||
}
|
||||
|
||||
async restore(backupId: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/restore`, {
|
||||
method: 'POST',
|
||||
})
|
||||
await this.fetch() // Refresh this module
|
||||
const backup = this.data.find((b) => b.id === backupId)
|
||||
if (backup) {
|
||||
if (!backup.task) backup.task = {}
|
||||
backup.task.restore = { progress: 0, state: 'ongoing' }
|
||||
}
|
||||
|
||||
try {
|
||||
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/restore`, {
|
||||
method: 'POST',
|
||||
})
|
||||
} catch (error) {
|
||||
if (backup?.task?.restore) {
|
||||
delete backup.task.restore
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async lock(backupId: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/lock`, {
|
||||
method: 'POST',
|
||||
})
|
||||
await this.fetch() // Refresh this module
|
||||
await this.fetch()
|
||||
}
|
||||
|
||||
async unlock(backupId: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/unlock`, {
|
||||
method: 'POST',
|
||||
})
|
||||
await this.fetch() // Refresh this module
|
||||
await this.fetch()
|
||||
}
|
||||
|
||||
async retry(backupId: string): Promise<void> {
|
||||
|
||||
@@ -151,6 +151,7 @@
|
||||
:server-name="serverData.name"
|
||||
:server-data="serverData"
|
||||
:uptime-seconds="uptimeSeconds"
|
||||
:backup-in-progress="backupInProgress"
|
||||
@action="sendPowerAction"
|
||||
/>
|
||||
</div>
|
||||
@@ -354,7 +355,7 @@
|
||||
>
|
||||
<h2 class="m-0 text-lg font-extrabold text-contrast">Server data</h2>
|
||||
<pre class="markdown-body w-full overflow-auto rounded-2xl bg-bg-raised p-4 text-sm">{{
|
||||
JSON.stringify(server, null, ' ')
|
||||
JSON.stringify(server, null, ' ')
|
||||
}}</pre>
|
||||
</div>
|
||||
</template>
|
||||
@@ -759,9 +760,14 @@ const handleWebSocketMessage = (data: WSEvent) => {
|
||||
curBackup.task = {}
|
||||
}
|
||||
|
||||
curBackup.task[data.task] = {
|
||||
progress: data.progress,
|
||||
state: data.state,
|
||||
const currentState = curBackup.task[data.task]?.state
|
||||
const shouldUpdate = !(currentState === 'ongoing' && data.state === 'unchanged')
|
||||
|
||||
if (shouldUpdate) {
|
||||
curBackup.task[data.task] = {
|
||||
progress: data.progress,
|
||||
state: data.state,
|
||||
}
|
||||
}
|
||||
|
||||
curBackup.ongoing = data.task === 'create' && data.state === 'ongoing'
|
||||
@@ -1277,6 +1283,7 @@ useHead({
|
||||
opacity: 0;
|
||||
transform: translateX(1rem);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
|
||||
Reference in New Issue
Block a user