feat: delete instance

This commit is contained in:
Ben Allfree 2023-12-07 04:37:09 -08:00
parent 8f89dd070b
commit 00477b3c11
7 changed files with 214 additions and 0 deletions

View File

@ -1,5 +1,8 @@
import { import {
CreateInstancePayloadSchema, CreateInstancePayloadSchema,
DeleteInstancePayload,
DeleteInstancePayloadSchema,
DeleteInstanceResult,
RestCommands, RestCommands,
RestMethods, RestMethods,
UpdateInstancePayload, UpdateInstancePayload,
@ -104,6 +107,12 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
UpdateInstancePayloadSchema, UpdateInstancePayloadSchema,
) )
const deleteInstance = mkRest<DeleteInstancePayload, DeleteInstanceResult>(
RestCommands.Instance,
RestMethods.Delete,
DeleteInstancePayloadSchema,
)
const getInstanceById = ( const getInstanceById = (
id: InstanceId, id: InstanceId,
): Promise<InstanceFields | undefined> => ): Promise<InstanceFields | undefined> =>
@ -271,5 +280,6 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
getAllInstancesById, getAllInstancesById,
resendVerificationEmail, resendVerificationEmail,
updateInstance, updateInstance,
deleteInstance,
} }
} }

View File

@ -6,6 +6,7 @@
import Code from './Code.svelte' import Code from './Code.svelte'
import AdminSync from './Danger/AdminSync.svelte' import AdminSync from './Danger/AdminSync.svelte'
import DangerZoneTitle from './Danger/DangerZoneTitle.svelte' import DangerZoneTitle from './Danger/DangerZoneTitle.svelte'
import DeleteInstance from './Danger/DeleteInstance.svelte'
import Maintenance from './Danger/Maintenance.svelte' import Maintenance from './Danger/Maintenance.svelte'
import RenameInstance from './Danger/RenameInstance.svelte' import RenameInstance from './Danger/RenameInstance.svelte'
import VersionChange from './Danger/VersionChange/VersionChange.svelte' import VersionChange from './Danger/VersionChange/VersionChange.svelte'
@ -87,4 +88,6 @@
<VersionChange /> <VersionChange />
<AdminSync /> <AdminSync />
<DeleteInstance />
</div> </div>

View File

@ -0,0 +1,108 @@
<script lang="ts">
import { goto } from '$app/navigation'
import Card from '$components/cards/Card.svelte'
import CardHeader from '$components/cards/CardHeader.svelte'
import { DOCS_URL } from '$src/env'
import { client } from '$src/pocketbase-client'
import { globalInstancesStore } from '$util/stores'
import { instance } from '../store'
import ErrorMessage from './ErrorMessage.svelte'
$: ({ id, maintenance, version } = $instance)
// Create a copy of the version
let selectedVersion = version
$: {
selectedVersion = version
}
// Controls the disabled state of the button
let isButtonDisabled = false
// Controls visibility of an error message
let errorMessage = ''
// Update the version number
const handleSave = async (e: Event) => {
e.preventDefault()
// Disable the button to prevent double submissions
isButtonDisabled = true
// Prompt the user to confirm the version change
const confirmVersionChange = confirm(
`LAST CHANCE - Are you sure you want to delete this instance? Your database, all local files, logs, and subdomain will be lost.`,
)
// If they select yes, then update the version in pocketbase
if (confirmVersionChange) {
errorMessage = ''
client()
.deleteInstance({
id,
})
.then(() => {
globalInstancesStore.update((instances) => {
const newInstances = { ...instances }
delete newInstances[id]
return newInstances
})
goto('/')
})
.catch((error) => {
console.error(error)
errorMessage = error.data.message || error.message
})
} else {
// If they hit cancel, reset the version number back to what it was initially
selectedVersion = version
}
// Set the button back to normal
isButtonDisabled = false
}
</script>
<Card>
<CardHeader documentation={DOCS_URL(`/usage/delete`)}>
Delete Instance
</CardHeader>
<div class="mb-8">
Deleting your instance will immediately and permanently delete your
instance:
<ul class="ml-10 text-error">
<li>Your subdomain</li>
<li><pre>pb_data/*</pre></li>
<li><pre>pb_public/*</pre></li>
<li><pre>pb_migrations/*</pre></li>
<li><pre>pb_static/*</pre></li>
</ul>
If you are storing files on S3, you must delete them separately.
</div>
<ErrorMessage message={errorMessage} />
<form
class="flex change-version-form-container-query gap-4"
on:submit={handleSave}
>
<button
type="submit"
class="btn btn-error"
disabled={!maintenance || isButtonDisabled}>Delete Instance</button
>
</form>
</Card>
<style>
.change-version-form-container-query {
flex-direction: column;
}
@container (min-width: 400px) {
.change-version-form-container-query {
flex-direction: row;
}
}
</style>

View File

@ -0,0 +1,66 @@
/// <reference path="../types/types.d.ts" />
/*
{
"id": "kz4ngg77eaw1ho0",
"fields": {
"maintenance": true
"name": '',
"version": ''
}
}
*/
routerAdd(
'DELETE',
'/api/instance/:id',
(c) => {
console.log(`***TOP OF DELETE`)
let data = new DynamicModel({
id: '',
})
c.bind(data)
console.log(`***After bind`)
// This is necessary for destructuring to work correctly
data = JSON.parse(JSON.stringify(data))
const id = c.pathParam('id')
console.log(
`***vars`,
JSON.stringify({
id,
}),
)
const authRecord = c.get('authRecord') // empty if not authenticated as regular auth record
console.log(`***authRecord`, JSON.stringify(authRecord))
if (!authRecord) {
throw new BadRequestError(`Expected authRecord here`)
}
const record = $app.dao().findRecordById('instances', id)
if (!record) {
throw new BadRequestError(`Instance ${id} not found.`)
}
if (record.get('uid') !== authRecord.id) {
throw new BadRequestError(`Not authorized`)
}
if (record.get('status') !== 'idle') {
throw new BadRequestError(`Instance must be shut down first.`)
}
const path = [$os.getenv('DATA_ROOT'), id].join('/')
console.log(`***path ${path}`)
const res = $os.removeAll(path)
console.log(`***res`, res)
$app.dao().deleteRecord(record)
return c.json(200, { status: 'ok' })
},
$apis.requireRecordAuth(),
)

View File

@ -1,4 +1,5 @@
import { import {
DATA_ROOT,
DEBUG, DEBUG,
DefaultSettingsService, DefaultSettingsService,
MOTHERSHIP_ADMIN_PASSWORD, MOTHERSHIP_ADMIN_PASSWORD,
@ -70,6 +71,9 @@ global.EventSource = EventSource
name: MOTHERSHIP_NAME(), name: MOTHERSHIP_NAME(),
slug: MOTHERSHIP_NAME(), slug: MOTHERSHIP_NAME(),
port: MOTHERSHIP_PORT(), port: MOTHERSHIP_PORT(),
env: {
DATA_ROOT: DATA_ROOT(),
},
extraBinds: [ extraBinds: [
`${MOTHERSHIP_HOOKS_DIR()}:/home/pocketbase/pb_hooks`, `${MOTHERSHIP_HOOKS_DIR()}:/home/pocketbase/pb_hooks`,
`${MOTHERSHIP_MIGRATIONS_DIR()}:/home/pocketbase/pb_migrations`, `${MOTHERSHIP_MIGRATIONS_DIR()}:/home/pocketbase/pb_migrations`,

View File

@ -0,0 +1,21 @@
import { JSONSchemaType } from 'ajv'
import { InstanceId } from '../types'
export type DeleteInstancePayload = {
id: InstanceId
}
export type DeleteInstanceResult = {
status: 'ok' | 'error'
message?: string
}
export const DeleteInstancePayloadSchema: JSONSchemaType<DeleteInstancePayload> =
{
type: 'object',
properties: {
id: { type: 'string' },
},
required: ['id'],
additionalProperties: false,
}

View File

@ -5,6 +5,7 @@ export enum RestMethods {
Get = 'GET', Get = 'GET',
Post = 'POST', Post = 'POST',
Put = 'PUT', Put = 'PUT',
Delete = 'DELETE',
} }
export enum RestCommands { export enum RestCommands {
@ -17,5 +18,6 @@ export type RestPayloadBase = JsonObject
export const ajv = new Ajv() export const ajv = new Ajv()
export * from './CreateInstance' export * from './CreateInstance'
export * from './DeleteInstance'
export * from './GetUserTokenInfo' export * from './GetUserTokenInfo'
export * from './UpdateInstance' export * from './UpdateInstance'