mirror of
https://github.com/pockethost/pockethost.git
synced 2026-03-17 13:54:52 +00:00
chore: remove invocations
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
console.log(instanceId)
|
||||
let isReady = false
|
||||
|
||||
$: ({ status, version, secondsThisMonth } = $instance)
|
||||
$: ({ status, version } = $instance)
|
||||
|
||||
assertExists($instance, `Expected instance here`)
|
||||
const { subdomain } = $instance
|
||||
@@ -41,9 +41,7 @@
|
||||
<div class="badge badge-accent badge-outline">
|
||||
Status: <span class="capitalize">{status}</span>
|
||||
</div>
|
||||
<div class="badge badge-accent badge-outline">
|
||||
Usage: {Math.ceil(secondsThisMonth / 60)} mins
|
||||
</div>
|
||||
|
||||
<div class="badge badge-accent badge-outline">Version: {version}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<script lang="ts">
|
||||
import ProvisioningStatus from '$components/ProvisioningStatus.svelte'
|
||||
import AccordionItem from '../../../../components/AccordionItem.svelte'
|
||||
import { instance } from './store'
|
||||
|
||||
$: ({ status, version, secondsThisMonth } = $instance)
|
||||
$: ({ status, version } = $instance)
|
||||
</script>
|
||||
|
||||
<div class="card card-body bg-base-200">
|
||||
@@ -13,10 +12,6 @@
|
||||
Status: <ProvisioningStatus {status} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Usage: {Math.ceil(secondsThisMonth / 60)} mins
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Version: {version}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { format, subMonths } from 'date-fns'
|
||||
import { Line } from 'svelte-chartjs'
|
||||
import { instance } from './store'
|
||||
|
||||
import Card from '$components/cards/Card.svelte'
|
||||
import CardHeader from '$components/cards/CardHeader.svelte'
|
||||
@@ -26,8 +25,6 @@
|
||||
CategoryScale,
|
||||
)
|
||||
|
||||
$: ({ secondsThisMonth } = $instance)
|
||||
|
||||
// Calculate the last six months
|
||||
const getLastSixMonths = () => {
|
||||
let currentDate = new Date()
|
||||
@@ -67,7 +64,7 @@
|
||||
pointHoverBorderWidth: 2,
|
||||
pointRadius: 1,
|
||||
pointHitRadius: 25,
|
||||
data: [24, 3, 16, 56, 55, Math.ceil(secondsThisMonth / 60)],
|
||||
data: [24, 3, 16, 56, 55, Math.ceil(0 / 60)],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -27,9 +27,6 @@
|
||||
<div class="badge badge-accent badge-outline">
|
||||
Status: <span class="capitalize">{instance.status}</span>
|
||||
</div>
|
||||
<div class="badge badge-accent badge-outline">
|
||||
Usage: {Math.ceil(instance.secondsThisMonth / 60)} mins
|
||||
</div>
|
||||
<div class="badge badge-accent badge-outline">
|
||||
Version: {instance.version}
|
||||
</div>
|
||||
|
||||
@@ -96,17 +96,14 @@
|
||||
getRandomNumber(),
|
||||
getRandomNumber(),
|
||||
getRandomNumber(),
|
||||
Math.ceil(instance.secondsThisMonth / 60),
|
||||
Math.ceil(0 / 60),
|
||||
],
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Loop through the instance list again, and create a "total usage" entry
|
||||
const totalUsageAmount = allInstancesArray.reduce(
|
||||
(total, instance) => total + instance.secondsThisMonth,
|
||||
0,
|
||||
)
|
||||
const totalUsageAmount = allInstancesArray.reduce((total) => total + 0, 0)
|
||||
|
||||
// Add up the individual instance usages and the total usage
|
||||
const allChartData: any = [
|
||||
|
||||
@@ -68,7 +68,7 @@ Use the FTP feature to transfer all your data.
|
||||
|
||||
The PocketHost service is free until we reach v1.0.
|
||||
|
||||
At that point, we will likely introduce a free tier based on "run time" minutes per month.
|
||||
At that point, we will likely introduce a paid tier while keeping a generous free tier.
|
||||
|
||||
### What is the pockethost.io free tier and restrictions?
|
||||
|
||||
@@ -78,11 +78,6 @@ pockethost.io offers a free tier to everyone. The free tier includes:
|
||||
- Unlimited storage
|
||||
- 1 project
|
||||
- Connect to your instance from `your-instance.pockethost.io`
|
||||
- 100 CPU minutes per month
|
||||
|
||||
A "CPU minute" is one minute of your `pocketbase` instance running on our system. PocketHost shuts down idle instances to conserve resources. In practice, about 90% of projects use fewer than 100 minutes of actual CPU time per month.
|
||||
|
||||
The free tier is less suitable for realtime applications that require the `pocketbase` instance to run continuously.
|
||||
|
||||
### What paid plans are there?
|
||||
|
||||
@@ -90,15 +85,9 @@ pockethost.io offers a one-size-fits-all paid plan. The paid plan includes:
|
||||
|
||||
- Unlimited bandwidth
|
||||
- Unlimited storage
|
||||
- Unlimited CPU minutes
|
||||
- Unlimited projects
|
||||
- Connect to your instance from `your-instance.pockethost.io` or a custom domain of your choice
|
||||
- Priority support
|
||||
|
||||
### What happens when I reach my minutes / rate / something?
|
||||
|
||||
Your instance will be placed into [Maintenance Mode](/docs/usage/maintenance/) until the problem is corrected.
|
||||
|
||||
### Are we allowed to have multiple projects running on Pockethost? How many instances can I create?
|
||||
|
||||
YES! That is exactly the point of PocketHost. Provision as many PocketBase instances as you desire.
|
||||
|
||||
@@ -25,12 +25,6 @@ Instances are placed in hibernation after 5 seconds of idle time.
|
||||
|
||||
_Note: Usage Metering is not active until PocketHost reaches v1.0. There is no planned timeline for when or if PocketHost will reach v1.0_
|
||||
|
||||
The free tier of PocketHost provides 100 _active minutes_ per month.
|
||||
|
||||

|
||||
|
||||
> Because an instance stays active for a minimum of only 5 seconds per activation, 100 minutes of real, active usage is actually quite a bit. After you use your 100 minutes, you can either pay for more usage minutes, or PocketHost will move your instance to the pool of stand-by instances that get served after everyone else. Again, in practice, you will likely not even notice the difference. But if you do, there is always a paid option.
|
||||
|
||||
## Instance Versioning
|
||||
|
||||
By default, your instance will use the latest major+minor release of PocketBase. The PocketBase version is locked when your instance is created. We use [semver](https://semver.org/) ([npm package](https://docs.npmjs.com/cli/v6/using-npm/semver)) to determine the version range that should be allowed for your instance. When your instance is launched, it will use the latest matching version.
|
||||
|
||||
@@ -30,7 +30,7 @@ layout: layouts/home.njk
|
||||
<ul role="list" class="mt-8 space-y-3 text-sm leading-6 sm:mt-10 text-gray-600">
|
||||
<li class="flex items-center gap-x-3">
|
||||
<i class="fa-regular fa-check text-primary"></i>
|
||||
5 instances
|
||||
2 projects (instances)
|
||||
</li>
|
||||
<li class="flex items-center gap-x-3">
|
||||
<i class="fa-regular fa-check text-primary"></i>
|
||||
@@ -38,7 +38,7 @@ layout: layouts/home.njk
|
||||
</li>
|
||||
<li class="flex items-center gap-x-3">
|
||||
<i class="fa-regular fa-check text-primary"></i>
|
||||
100 minutes / month
|
||||
1 gb transfer / month
|
||||
</li>
|
||||
<li class="flex items-center gap-x-3">
|
||||
<i class="fa-regular fa-check text-primary"></i>
|
||||
@@ -72,15 +72,15 @@ layout: layouts/home.njk
|
||||
</li>
|
||||
<li class="flex items-center gap-x-3">
|
||||
<i class="fa-regular fa-check text-primary"></i>
|
||||
10gb of storage
|
||||
Unlimited storage
|
||||
</li>
|
||||
<li class="flex items-center gap-x-3">
|
||||
<i class="fa-regular fa-check text-primary"></i>
|
||||
Unlimited minutes
|
||||
Unlimited transfer
|
||||
</li>
|
||||
<li class="flex items-center gap-x-3">
|
||||
<i class="fa-regular fa-check text-primary"></i>
|
||||
Priority help on the discord channel
|
||||
Dedicated Discord channel for support
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
InstanceFields,
|
||||
InstanceId,
|
||||
InvocationFields,
|
||||
IoCManager,
|
||||
mkSingleton,
|
||||
UserFields,
|
||||
@@ -117,8 +116,6 @@ export type MothershipProvider = {
|
||||
subdomain: InstanceFields['subdomain'],
|
||||
): Promise<[InstanceFields, UserFields] | []>
|
||||
updateInstance(id: InstanceId, fields: Partial<InstanceFields>): Promise<void>
|
||||
createInvocation(instance: InstanceFields): Promise<InvocationFields>
|
||||
finalizeInvocation(invocation: InvocationFields): Promise<void>
|
||||
}
|
||||
|
||||
type UnsubFunc = () => void
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
/// <reference path="../types/types.d.ts" />
|
||||
|
||||
// onAfterBootstrap((e) => {
|
||||
// $app
|
||||
// .dao()
|
||||
// .db()
|
||||
// .newQuery(`update invocations set endedAt=datetime('now') where endedAt=''`)
|
||||
// .execute()
|
||||
// })
|
||||
@@ -203,9 +203,6 @@ export const instanceService = mkSingleton(
|
||||
client.updateInstanceStatus,
|
||||
)
|
||||
const updateInstance = clientLimiter.wrap(client.updateInstance)
|
||||
const createInvocation = clientLimiter.wrap(client.createInvocation)
|
||||
const pingInvocation = clientLimiter.wrap(client.pingInvocation)
|
||||
const finalizeInvocation = clientLimiter.wrap(client.finalizeInvocation)
|
||||
|
||||
/*
|
||||
Handle async setup
|
||||
@@ -297,15 +294,6 @@ export const instanceService = mkSingleton(
|
||||
dbg(`killed ${id}`)
|
||||
})
|
||||
|
||||
/*
|
||||
Create the invocation record
|
||||
*/
|
||||
healthyGuard()
|
||||
const invocation = await createInvocation(instance, pid)
|
||||
shutdownManager.add(async () => {
|
||||
await finalizeInvocation(invocation).catch(error)
|
||||
})
|
||||
|
||||
/*
|
||||
API state, timers, etc
|
||||
*/
|
||||
@@ -343,19 +331,6 @@ export const instanceService = mkSingleton(
|
||||
}, RECHECK_TTL)
|
||||
}
|
||||
|
||||
{
|
||||
tm.repeat(
|
||||
() =>
|
||||
pingInvocation(invocation)
|
||||
.then(() => true)
|
||||
.catch((e) => {
|
||||
warn(`_pingInvocation failed with ${e}`)
|
||||
return true
|
||||
}),
|
||||
1000,
|
||||
)
|
||||
}
|
||||
|
||||
dbg(`${internalUrl} is running`)
|
||||
status = InstanceApiStatus.Healthy
|
||||
healthyGuard()
|
||||
|
||||
@@ -8,7 +8,6 @@ import { InstanceLogger, PortService } from '$services'
|
||||
import {
|
||||
createCleanupManager,
|
||||
createTimerManager,
|
||||
InvocationPid,
|
||||
LoggerService,
|
||||
mkSingleton,
|
||||
SingletonBaseConfig,
|
||||
@@ -44,7 +43,7 @@ export type PocketbaseServiceConfig = SingletonBaseConfig & {}
|
||||
|
||||
export type PocketbaseProcess = {
|
||||
url: string
|
||||
pid: () => InvocationPid
|
||||
pid: () => string
|
||||
kill: () => Promise<void>
|
||||
exitCode: Promise<number | null>
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import {
|
||||
InstanceFields,
|
||||
InvocationFields,
|
||||
InvocationPid,
|
||||
pocketNow,
|
||||
} from '$shared'
|
||||
import { InstanceApi } from './InstanceMIxin'
|
||||
import { MixinContext } from './PbClient'
|
||||
|
||||
export const createInvocationMixin = (
|
||||
context: MixinContext,
|
||||
instanceApi: InstanceApi,
|
||||
) => {
|
||||
const { logger } = context
|
||||
const { dbg } = logger.create('InvocationMixin')
|
||||
|
||||
const { client } = context
|
||||
|
||||
const createInvocation = async (
|
||||
instance: InstanceFields,
|
||||
pid: InvocationPid,
|
||||
) => {
|
||||
const init: Partial<InvocationFields> = {
|
||||
uid: instance.uid,
|
||||
startedAt: pocketNow(),
|
||||
instanceId: instance.id,
|
||||
totalSeconds: 0,
|
||||
}
|
||||
const _inv = await client
|
||||
.collection('invocations')
|
||||
.create<InvocationFields>(init)
|
||||
return _inv
|
||||
}
|
||||
|
||||
const pingInvocation = async (invocation: InvocationFields) => {
|
||||
const totalSeconds = (+new Date() - Date.parse(invocation.startedAt)) / 1000
|
||||
const toUpdate: Partial<InvocationFields> = {
|
||||
totalSeconds,
|
||||
}
|
||||
const _inv = await client
|
||||
.collection('invocations')
|
||||
.update<InvocationFields>(invocation.id, toUpdate)
|
||||
return _inv
|
||||
}
|
||||
|
||||
const finalizeInvocation = async (invocation: InvocationFields) => {
|
||||
dbg('finalizing')
|
||||
const totalSeconds = (+new Date() - Date.parse(invocation.startedAt)) / 1000
|
||||
const toUpdate: Partial<InvocationFields> = {
|
||||
endedAt: pocketNow(),
|
||||
totalSeconds,
|
||||
}
|
||||
dbg({ toUpdate })
|
||||
const _inv = await client
|
||||
.collection('invocations')
|
||||
.update<InvocationFields>(invocation.id, toUpdate)
|
||||
return _inv
|
||||
}
|
||||
|
||||
return { finalizeInvocation, pingInvocation, createInvocation }
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import { Logger, LoggerService } from '$shared'
|
||||
import { Knex } from 'knex'
|
||||
import { default as PocketBase, default as pocketbaseEs } from 'pocketbase'
|
||||
import { createInstanceMixin } from './InstanceMIxin'
|
||||
import { createInvocationMixin } from './InvocationMixin'
|
||||
import { createRawPbClient } from './RawPbClient'
|
||||
|
||||
export type PocketbaseClientApi = ReturnType<typeof createPbClient>
|
||||
@@ -37,7 +36,6 @@ export const createPbClient = (url: string) => {
|
||||
|
||||
const context: MixinContext = { client, rawDb, logger: _clientLogger }
|
||||
const instanceApi = createInstanceMixin(context)
|
||||
const invocationApi = createInvocationMixin(context, instanceApi)
|
||||
|
||||
const api = {
|
||||
client,
|
||||
@@ -46,7 +44,6 @@ export const createPbClient = (url: string) => {
|
||||
createFirstAdmin,
|
||||
adminAuthViaEmail,
|
||||
...instanceApi,
|
||||
...invocationApi,
|
||||
}
|
||||
|
||||
return api
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { BaseFields, InstanceId, RecordId } from './types'
|
||||
|
||||
export enum BackupStatus {
|
||||
Queued = 'queued',
|
||||
Running = 'running',
|
||||
FinishedSuccess = 'finished-success',
|
||||
FinishedError = 'finished-error',
|
||||
}
|
||||
|
||||
export type BackupRecordId = RecordId
|
||||
|
||||
export type BackupFields = BaseFields & {
|
||||
instanceId: InstanceId
|
||||
status: BackupStatus
|
||||
message: string
|
||||
bytes: number
|
||||
version: string
|
||||
progress: {
|
||||
[_: string]: number
|
||||
}
|
||||
}
|
||||
|
||||
export type BackupFields_Create = Pick<
|
||||
BackupFields,
|
||||
'instanceId' | 'status' | 'version'
|
||||
>
|
||||
|
||||
export type BackupFields_Update = Partial<
|
||||
Pick<
|
||||
BackupFields,
|
||||
'instanceId' | 'status' | 'bytes' | 'message' | 'version' | 'progress'
|
||||
>
|
||||
>
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BaseFields, RecordId, Seconds, Subdomain, UserId } from './types'
|
||||
import { BaseFields, RecordId, Subdomain, UserId } from './types'
|
||||
|
||||
export type VersionId = string
|
||||
|
||||
@@ -24,7 +24,6 @@ export type InstanceFields = BaseFields & {
|
||||
uid: UserId
|
||||
status: InstanceStatus
|
||||
version: VersionId
|
||||
secondsThisMonth: Seconds
|
||||
secrets: InstanceSecretCollection | null
|
||||
maintenance: boolean
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { BaseFields, IsoDate, RecordId } from './types'
|
||||
|
||||
export const INVOCATION_COLLECTION = 'invocations'
|
||||
|
||||
export type InvocationPid = string
|
||||
export type InvocationFields = BaseFields & {
|
||||
uid: RecordId
|
||||
instanceId: RecordId
|
||||
startedAt: IsoDate
|
||||
endedAt: IsoDate
|
||||
totalSeconds: number
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
export * from './Backup'
|
||||
export * from './Instance'
|
||||
export * from './InstanceLog'
|
||||
export * from './Invocation'
|
||||
export * from './Rest'
|
||||
export * from './User'
|
||||
export * from './types'
|
||||
|
||||
@@ -1,68 +1,15 @@
|
||||
import { clientService } from '$services'
|
||||
import {
|
||||
INSTANCE_COLLECTION,
|
||||
INVOCATION_COLLECTION,
|
||||
InstanceFields,
|
||||
InvocationFields,
|
||||
LoggerService,
|
||||
singletonAsyncExecutionGuard,
|
||||
} from '$shared'
|
||||
import Bottleneck from 'bottleneck'
|
||||
import { ClientResponseError } from 'pocketbase'
|
||||
|
||||
export const deleteInvocation = singletonAsyncExecutionGuard(
|
||||
async (invocation: InvocationFields) => {
|
||||
const { client } = await clientService()
|
||||
await client.client.collection(INVOCATION_COLLECTION).delete(invocation.id)
|
||||
},
|
||||
(invocation) => `deleteInvocation:${invocation.id}`,
|
||||
)
|
||||
|
||||
export const deleteInvocationsForInstance = singletonAsyncExecutionGuard(
|
||||
async (instance: InstanceFields) => {
|
||||
const { client } = await clientService()
|
||||
const { dbg, error } = LoggerService()
|
||||
.create(`deleteInvocationsForInstance`)
|
||||
.breadcrumb(instance.id)
|
||||
const { id } = instance
|
||||
while (true) {
|
||||
try {
|
||||
console.log(`Deleting invocations for ${id}`)
|
||||
const invocation = await client.client
|
||||
.collection(INVOCATION_COLLECTION)
|
||||
.getFirstListItem<InvocationFields>(`instanceId = '${id}'`)
|
||||
console.log(`Deleting invocation ${invocation.id}`)
|
||||
await client.client
|
||||
.collection(INVOCATION_COLLECTION)
|
||||
.delete(invocation.id)
|
||||
console.log(`Invocation deleted ${id}`)
|
||||
} catch (e) {
|
||||
if (e instanceof ClientResponseError) {
|
||||
if (e.status === 404) {
|
||||
dbg(`No more invocations`)
|
||||
return
|
||||
}
|
||||
}
|
||||
error(e)
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
(instance) => `deleteInvocationsForInstance:${instance.id}`,
|
||||
)
|
||||
|
||||
export const deleteInstance = singletonAsyncExecutionGuard(
|
||||
async (instance: InstanceFields) => {
|
||||
const { client } = await clientService()
|
||||
const { id } = instance
|
||||
await deleteInvocationsForInstance(instance).catch((e) => {
|
||||
console.error(
|
||||
`deleteInvocationsForInstance error`,
|
||||
JSON.stringify(e, null, 2),
|
||||
)
|
||||
throw e
|
||||
})
|
||||
console.log(`Invocations deleted for ${id}`)
|
||||
|
||||
await client.client
|
||||
.collection(INSTANCE_COLLECTION)
|
||||
|
||||
@@ -23,7 +23,6 @@ const _unsafe_createInstance = async (context: ContextBase) => {
|
||||
uid: shuffle(users).pop()!.id,
|
||||
status: InstanceStatus.Idle,
|
||||
version: `~0.${random(1, 16)}.0`,
|
||||
secondsThisMonth: 0,
|
||||
secrets: {},
|
||||
maintenance: false,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user