mirror of
https://github.com/pockethost/pockethost.git
synced 2025-11-23 22:15:49 +00:00
linter fixes
This commit is contained in:
parent
60307e6ca7
commit
297a6e45ee
17
.prettierrc
17
.prettierrc
@ -1,11 +1,14 @@
|
||||
{
|
||||
"semi": false,
|
||||
"useTabs": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"trailingComma": "es5",
|
||||
"semi": false,
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"plugins": [
|
||||
"./node_modules/prettier-plugin-organize-imports/index.js",
|
||||
"./node_modules/prettier-plugin-svelte/plugin.js",
|
||||
"./node_modules/prettier-plugin-jsdoc/dist/index.js"
|
||||
]
|
||||
"prettier-plugin-organize-imports",
|
||||
"prettier-plugin-svelte",
|
||||
"prettier-plugin-jsdoc"
|
||||
],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-jsdoc": "^1.3.2",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"prettier-plugin-svelte": "^3.3.2",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"tslib": "^2.8.1",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.7.3"
|
||||
|
||||
@ -58,11 +58,7 @@
|
||||
</script>
|
||||
|
||||
{#if message && !isHidden}
|
||||
<div
|
||||
class="alert mb-4 {alertTypeClass} {additionalClasses} justify-center"
|
||||
transition:slide
|
||||
role="alert"
|
||||
>
|
||||
<div class="alert mb-4 {alertTypeClass} {additionalClasses} justify-center" transition:slide role="alert">
|
||||
<Fa icon={alertTypeIcon} />
|
||||
<span>{@html message}</span>
|
||||
</div>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import CopyButton from '$components/CopyButton.svelte'
|
||||
import { Highlight } from 'svelte-highlight'
|
||||
import { typescript, type LanguageType } from 'svelte-highlight/languages'
|
||||
import a11yDark from "svelte-highlight/styles/a11y-dark";
|
||||
import a11yDark from 'svelte-highlight/styles/a11y-dark'
|
||||
|
||||
export let code: string
|
||||
export let language: LanguageType<'typescript' | 'bash' | 'dns'> = typescript
|
||||
|
||||
@ -13,7 +13,5 @@
|
||||
</script>
|
||||
|
||||
<Clipboard text={code} let:copy on:copy={handleCopy}>
|
||||
<TinyButton click={copy} style={isCopied ? 'success' : 'primary'}
|
||||
>{isCopied ? 'Copied!' : 'Copy'}</TinyButton
|
||||
>
|
||||
<TinyButton click={copy} style={isCopied ? 'success' : 'primary'}>{isCopied ? 'Copied!' : 'Copy'}</TinyButton>
|
||||
</Clipboard>
|
||||
|
||||
@ -4,10 +4,7 @@
|
||||
import PricingCard from '$components/PricingCard.svelte'
|
||||
|
||||
const TOTAL_QTY = 150
|
||||
export let priceMonthly: [number, string?, number?] = [
|
||||
359,
|
||||
'once, use forever',
|
||||
]
|
||||
export let priceMonthly: [number, string?, number?] = [359, 'once, use forever']
|
||||
export let startDate: Date | null = null
|
||||
export let endDate: Date | null = null
|
||||
|
||||
|
||||
@ -38,9 +38,7 @@
|
||||
}
|
||||
|
||||
const days = Math.floor(difference / (1000 * 60 * 60 * 24))
|
||||
const hours = Math.floor(
|
||||
(difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60),
|
||||
)
|
||||
const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
|
||||
const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60))
|
||||
const seconds = Math.floor((difference % (1000 * 60)) / 1000)
|
||||
|
||||
@ -167,9 +165,7 @@
|
||||
${priceAnnually[0]}
|
||||
</span>
|
||||
{/if}
|
||||
<span
|
||||
class={`text-sm font-semibold leading-6 ${qtyRemaining <= 0 ? 'text-gray-600' : 'text-gray-300'}`}
|
||||
>
|
||||
<span class={`text-sm font-semibold leading-6 ${qtyRemaining <= 0 ? 'text-gray-600' : 'text-gray-300'}`}>
|
||||
/ {priceAnnually[1]}
|
||||
</span>
|
||||
</a>
|
||||
@ -194,11 +190,7 @@
|
||||
${priceMonthly[0]}
|
||||
</span>
|
||||
{/if}
|
||||
<span
|
||||
class="text-sm font-semibold leading-6 ${qtyRemaining <= 0
|
||||
? 'text-gray-600'
|
||||
: 'text-gray-300'}"
|
||||
>
|
||||
<span class="text-sm font-semibold leading-6 ${qtyRemaining <= 0 ? 'text-gray-600' : 'text-gray-300'}">
|
||||
/ {priceMonthly[1]}</span
|
||||
>
|
||||
</a>
|
||||
|
||||
@ -32,8 +32,7 @@
|
||||
{
|
||||
name: 'Giovanni Cruz',
|
||||
title: 'Software developer',
|
||||
quote:
|
||||
'Been inlove with this from the first moment I used it. Excellent offering.',
|
||||
quote: 'Been inlove with this from the first moment I used it. Excellent offering.',
|
||||
},
|
||||
{
|
||||
name: 'Anele Mbanga',
|
||||
|
||||
@ -42,11 +42,7 @@
|
||||
<Fa icon={faCheck} /> Sent!
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary btn-sm"
|
||||
on:click={handleClick}>Resend Email</button
|
||||
>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" on:click={handleClick}>Resend Email</button>
|
||||
{/if}
|
||||
|
||||
{#if formError}
|
||||
|
||||
@ -6,9 +6,7 @@
|
||||
|
||||
<!-- Setting the `container-type` allows us to use Container Queries -->
|
||||
<div
|
||||
class="card card-body bg-base-200 {block
|
||||
? 'block'
|
||||
: ''} {height} {marginBottom}"
|
||||
class="card card-body bg-base-200 {block ? 'block' : ''} {height} {marginBottom}"
|
||||
style="container-type: inline-size"
|
||||
>
|
||||
<slot />
|
||||
|
||||
@ -9,14 +9,8 @@
|
||||
<div class="flex items-center justify-between mb-4 flex-wrap gap-2">
|
||||
<h3 class="text-xl font-bold"><slot /></h3>
|
||||
|
||||
<a
|
||||
href={documentation}
|
||||
class="btn btn-sm btn-outline btn-primary"
|
||||
target="_blank"
|
||||
>Full documentation <Fa
|
||||
icon={faArrowUpRightFromSquare}
|
||||
class="opacity-50 text-sm"
|
||||
/></a
|
||||
<a href={documentation} class="btn btn-sm btn-outline btn-primary" target="_blank"
|
||||
>Full documentation <Fa icon={faArrowUpRightFromSquare} class="opacity-50 text-sm" /></a
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@ -1,9 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
isAuthStateInitialized,
|
||||
isUserLoggedIn,
|
||||
userStore,
|
||||
} from '$util/stores'
|
||||
import { isAuthStateInitialized, isUserLoggedIn, userStore } from '$util/stores'
|
||||
|
||||
export let role = ''
|
||||
let hasRole = false
|
||||
|
||||
@ -1,26 +1,21 @@
|
||||
import { type InstanceFields, parseBoolean } from 'pockethost/common'
|
||||
|
||||
/**
|
||||
* These environment variables default to pointing to the production build so
|
||||
* frontend development is easy. If they are specified in .env, those values
|
||||
* will prevail.
|
||||
* These environment variables default to pointing to the production build so frontend development is easy. If they are
|
||||
* specified in .env, those values will prevail.
|
||||
*/
|
||||
|
||||
// The apex domain of this whole operation.
|
||||
export const PUBLIC_APEX_DOMAIN =
|
||||
import.meta.env.PUBLIC_APEX_DOMAIN || `pockethost.io`
|
||||
export const PUBLIC_APEX_DOMAIN = import.meta.env.PUBLIC_APEX_DOMAIN || `pockethost.io`
|
||||
|
||||
export const PUBLIC_APP_URL =
|
||||
import.meta.env.PUBLIC_APP_URL || `https://${PUBLIC_APEX_DOMAIN}`
|
||||
export const PUBLIC_APP_URL = import.meta.env.PUBLIC_APP_URL || `https://${PUBLIC_APEX_DOMAIN}`
|
||||
|
||||
// The protocol to use, almost always will be https
|
||||
export const PUBLIC_HTTP_PROTOCOL =
|
||||
import.meta.env.PUBLIC_HTTP_PROTOCOL || `https:`
|
||||
export const PUBLIC_HTTP_PROTOCOL = import.meta.env.PUBLIC_HTTP_PROTOCOL || `https:`
|
||||
|
||||
// The complete URL to the mothership
|
||||
export const PUBLIC_MOTHERSHIP_URL =
|
||||
import.meta.env.PUBLIC_MOTHERSHIP_URL ||
|
||||
`https://pockethost-central.${PUBLIC_APEX_DOMAIN}`
|
||||
import.meta.env.PUBLIC_MOTHERSHIP_URL || `https://pockethost-central.${PUBLIC_APEX_DOMAIN}`
|
||||
|
||||
// Whether we are in debugging mode - default TRUE
|
||||
export const PUBLIC_DEBUG = parseBoolean(import.meta.env.PUBLIC_DEBUG || 'true')
|
||||
@ -49,13 +44,10 @@ export const INSTANCE_HOST = (instance: InstanceFields) => {
|
||||
* INSTANCE_URL('my-cool-instance', 'dashboard') // https://my-cool-instance.pockethost.io/dashboard
|
||||
*
|
||||
* @param {string} instance This is the unique instance name
|
||||
* @param {string[]} paths This is an optional list of additional paths to
|
||||
* append to the instance URL.
|
||||
* @param {string[]} paths This is an optional list of additional paths to append to the instance URL.
|
||||
*/
|
||||
export const INSTANCE_URL = (instance: InstanceFields, ...paths: string[]) => {
|
||||
return `${PUBLIC_HTTP_PROTOCOL}//${INSTANCE_HOST(instance)}/${mkPath(
|
||||
...paths,
|
||||
)}`
|
||||
return `${PUBLIC_HTTP_PROTOCOL}//${INSTANCE_HOST(instance)}/${mkPath(...paths)}`
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
11
packages/dashboard/src/lemonsqueezy.d.ts
vendored
11
packages/dashboard/src/lemonsqueezy.d.ts
vendored
@ -6,19 +6,16 @@ interface Window {
|
||||
/**
|
||||
* Initialises Lemon.js on your page.
|
||||
*
|
||||
* @param options - An object with a single property, eventHandler, which
|
||||
* is a function that will be called when Lemon.js emits an event.
|
||||
* @param options - An object with a single property, eventHandler, which is a function that will be called when
|
||||
* Lemon.js emits an event.
|
||||
*/
|
||||
Setup: (options: {
|
||||
eventHandler: (event: { event: string }) => void
|
||||
}) => void
|
||||
Setup: (options: { eventHandler: (event: { event: string }) => void }) => void
|
||||
/** Refreshes `lemonsqueezy-button` listeners on the page. */
|
||||
Refresh: () => void
|
||||
|
||||
Url: {
|
||||
/**
|
||||
* Opens a given Lemon Squeezy URL, typically these are Checkout or
|
||||
* Payment Details Update overlays.
|
||||
* Opens a given Lemon Squeezy URL, typically these are Checkout or Payment Details Update overlays.
|
||||
*
|
||||
* @param url - The URL to open.
|
||||
*/
|
||||
|
||||
@ -52,8 +52,7 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
||||
const logOut = () => authStore.clear()
|
||||
|
||||
/**
|
||||
* This will register a new user into Pocketbase, and email them a
|
||||
* verification link
|
||||
* This will register a new user into Pocketbase, and email them a verification link
|
||||
*
|
||||
* @param email {string} The email of the user
|
||||
* @param password {string} The password of the user
|
||||
@ -85,8 +84,8 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* This will reset an unauthenticated user's password by sending a
|
||||
* verification link to their email, and includes an optional error handler
|
||||
* This will reset an unauthenticated user's password by sending a verification link to their email, and includes an
|
||||
* optional error handler
|
||||
*
|
||||
* @param email {string} The email of the user
|
||||
*/
|
||||
@ -95,24 +94,17 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* This will let an unauthenticated user save a new password after verifying
|
||||
* their email
|
||||
* This will let an unauthenticated user save a new password after verifying their email
|
||||
*
|
||||
* @param token {string} The token from the verification email
|
||||
* @param password {string} The new password of the user
|
||||
*/
|
||||
const requestPasswordResetConfirm = async (
|
||||
token: string,
|
||||
password: string,
|
||||
) => {
|
||||
return await client
|
||||
.collection('users')
|
||||
.confirmPasswordReset(token, password, password)
|
||||
const requestPasswordResetConfirm = async (token: string, password: string) => {
|
||||
return await client.collection('users').confirmPasswordReset(token, password, password)
|
||||
}
|
||||
|
||||
/**
|
||||
* This will log a user into Pocketbase, and includes an optional error
|
||||
* handler
|
||||
* This will log a user into Pocketbase, and includes an optional error handler
|
||||
*
|
||||
* @param {string} email The email of the user
|
||||
* @param {string} password The password of the user
|
||||
@ -129,32 +121,26 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
||||
const createInstance = mkRest<CreateInstancePayload, CreateInstanceResult>(
|
||||
RestCommands.Instance,
|
||||
RestMethods.Post,
|
||||
CreateInstancePayloadSchema,
|
||||
CreateInstancePayloadSchema
|
||||
)
|
||||
|
||||
const updateInstance = mkRest<UpdateInstancePayload, UpdateInstanceResult>(
|
||||
RestCommands.Instance,
|
||||
RestMethods.Put,
|
||||
UpdateInstancePayloadSchema,
|
||||
UpdateInstancePayloadSchema
|
||||
)
|
||||
|
||||
const deleteInstance = mkRest<DeleteInstancePayload, DeleteInstanceResult>(
|
||||
RestCommands.Instance,
|
||||
RestMethods.Delete,
|
||||
DeleteInstancePayloadSchema,
|
||||
DeleteInstancePayloadSchema
|
||||
)
|
||||
|
||||
const getInstanceById = (
|
||||
id: InstanceId,
|
||||
): Promise<InstanceFields | undefined> =>
|
||||
const getInstanceById = (id: InstanceId): Promise<InstanceFields | undefined> =>
|
||||
client.collection('instances').getOne<InstanceFields>(id)
|
||||
|
||||
const getInstanceBySubdomain = (
|
||||
subdomain: InstanceFields['subdomain'],
|
||||
): Promise<InstanceFields | undefined> =>
|
||||
client
|
||||
.collection('instances')
|
||||
.getFirstListItem<InstanceFields>(`subdomain='${subdomain}'`)
|
||||
const getInstanceBySubdomain = (subdomain: InstanceFields['subdomain']): Promise<InstanceFields | undefined> =>
|
||||
client.collection('instances').getFirstListItem<InstanceFields>(`subdomain='${subdomain}'`)
|
||||
|
||||
const getAllInstancesById = async () =>
|
||||
(await client.collection('instances').getFullList()).reduce(
|
||||
@ -162,16 +148,13 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
||||
c[v.id] = v as unknown as InstanceFields
|
||||
return c
|
||||
},
|
||||
{} as { [_: InstanceId]: InstanceFields },
|
||||
{} as { [_: InstanceId]: InstanceFields }
|
||||
)
|
||||
|
||||
const parseError = (e: Error): string[] => {
|
||||
if (!(e instanceof ClientResponseError)) return [`${e}`]
|
||||
if (e.data.message && keys(e.data.data).length === 0)
|
||||
return [e.data.message]
|
||||
return map(e.data.data, (v, k) => (v ? v.message : undefined)).filter(
|
||||
(v) => !!v,
|
||||
)
|
||||
if (e.data.message && keys(e.data.data).length === 0) return [e.data.message]
|
||||
return map(e.data.data, (v, k) => (v ? v.message : undefined)).filter((v) => !!v)
|
||||
}
|
||||
|
||||
const resendVerificationEmail = async () => {
|
||||
@ -184,8 +167,7 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
||||
const { isAdmin, model, token, isValid } = client.authStore
|
||||
|
||||
if (isAdmin) throw new Error(`Admin models not supported`)
|
||||
if (model && !model.email)
|
||||
throw new Error(`Expected model to be a user here`)
|
||||
if (model && !model.email) throw new Error(`Expected model to be a user here`)
|
||||
return {
|
||||
token,
|
||||
model,
|
||||
@ -194,8 +176,8 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Use synthetic event for authStore changers, so we can broadcast just the
|
||||
* props we want and not the actual authStore object.
|
||||
* Use synthetic event for authStore changers, so we can broadcast just the props we want and not the actual authStore
|
||||
* object.
|
||||
*/
|
||||
const [onAuthChange, fireAuthChange] = createGenericSyncEvent<BaseAuthStore>()
|
||||
|
||||
@ -207,9 +189,8 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
||||
})
|
||||
|
||||
/**
|
||||
* Refresh the auth token immediately upon creating the client. The auth
|
||||
* token may be out of date, or fields in the user record may have changed
|
||||
* in the backend.
|
||||
* Refresh the auth token immediately upon creating the client. The auth token may be out of date, or fields in the
|
||||
* user record may have changed in the backend.
|
||||
*/
|
||||
if (browser) {
|
||||
refreshAuthToken()
|
||||
@ -222,12 +203,11 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for auth state changes and subscribe to realtime _user events.
|
||||
* This way, when the verified flag is flipped, it will appear that the
|
||||
* authstore model is updated.
|
||||
* Listen for auth state changes and subscribe to realtime _user events. This way, when the verified flag is
|
||||
* flipped, it will appear that the authstore model is updated.
|
||||
*
|
||||
* Polling is a stopgap til v.0.8. Once 0.8 comes along, we can do a
|
||||
* realtime watch on the user record and update auth accordingly.
|
||||
* Polling is a stopgap til v.0.8. Once 0.8 comes along, we can do a realtime watch on the user record and update
|
||||
* auth accordingly.
|
||||
*/
|
||||
const unsub = onAuthChange((authStore) => {
|
||||
const { model, isAdmin } = authStore
|
||||
@ -249,7 +229,7 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
||||
const watchInstanceLog = (
|
||||
instance: InstanceFields,
|
||||
update: (log: UntrustedInstanceLogFields) => void,
|
||||
nInitial = 100,
|
||||
nInitial = 100
|
||||
): (() => void) => {
|
||||
const auth = client.authStore.exportToCookie()
|
||||
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
import { PUBLIC_MOTHERSHIP_URL } from '$src/env'
|
||||
import {
|
||||
createPocketbaseClient,
|
||||
type PocketbaseClient,
|
||||
} from './PocketbaseClient'
|
||||
import { createPocketbaseClient, type PocketbaseClient } from './PocketbaseClient'
|
||||
|
||||
export const client = (() => {
|
||||
let clientInstance: PocketbaseClient | undefined
|
||||
|
||||
@ -12,10 +12,7 @@
|
||||
<div class="flex flex-col space-x-4 items-center justify-center">
|
||||
<div class="prose">
|
||||
<p class="text-2xl text-center text-warning">
|
||||
Instances will not run until you <a
|
||||
href="/access"
|
||||
class="link text-primary">upgrade</a
|
||||
>.
|
||||
Instances will not run until you <a href="/access" class="link text-primary">upgrade</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -12,14 +12,10 @@
|
||||
Your email: <span class="text-success font-bold">{$userStore?.email}</span>
|
||||
</div>
|
||||
<div class="m-4">
|
||||
Your plan: <span class="text-success font-bold"
|
||||
>{PLAN_NAMES[$userSubscriptionType]}</span
|
||||
>
|
||||
Your plan: <span class="text-success font-bold">{PLAN_NAMES[$userSubscriptionType]}</span>
|
||||
</div>
|
||||
<div class="m-4">
|
||||
Allowed instances: {$userStore?.subscription_quantity} (<a
|
||||
href="/support"
|
||||
class="link text-primary">request more</a
|
||||
Allowed instances: {$userStore?.subscription_quantity} (<a href="/support" class="link text-primary">request more</a
|
||||
>)
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@ -43,13 +43,10 @@
|
||||
|
||||
<div class="mt-10">
|
||||
{#if active}
|
||||
<div class="text-success text-center text-2xl">
|
||||
This is your current plan.
|
||||
</div>
|
||||
<div class="text-success text-center text-2xl">This is your current plan.</div>
|
||||
<p class="mt-10 text-neutral-content">
|
||||
To change to a different plan, contact <a
|
||||
class="link"
|
||||
href={`"${DISCORD_URL}"`}><code>.noaxis</code> on Discord</a
|
||||
To change to a different plan, contact <a class="link" href={`"${DISCORD_URL}"`}
|
||||
><code>.noaxis</code> on Discord</a
|
||||
>
|
||||
</p>
|
||||
{:else if prices.length > 0}
|
||||
@ -58,16 +55,12 @@
|
||||
{#if (startLimit > 0 && limit === 0) || !upgradable}
|
||||
<button class="btn btn-primary" disabled>{price.title}</button>
|
||||
{:else}
|
||||
<a class="btn btn-primary" href={price.link} target="_blank"
|
||||
>{price.title}</a
|
||||
>
|
||||
<a class="btn btn-primary" href={price.link} target="_blank">{price.title}</a>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{#if !upgradable}
|
||||
To change to this plan, <a href="/support" class="link"
|
||||
>contact support</a
|
||||
>
|
||||
To change to this plan, <a href="/support" class="link">contact support</a>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@ -3,79 +3,63 @@
|
||||
import FAQItem from '$src/routes/(app)/account/FAQItem.svelte'
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="mx-auto max-w-7xl divide-y divide-gray-500 px-6 py-24 sm:py-32 lg:px-8 lg:py-40"
|
||||
>
|
||||
<h2 class="text-2xl font-bold leading-10 tracking-tight">
|
||||
Frequently asked questions
|
||||
</h2>
|
||||
<div class="mx-auto max-w-7xl divide-y divide-gray-500 px-6 py-24 sm:py-32 lg:px-8 lg:py-40">
|
||||
<h2 class="text-2xl font-bold leading-10 tracking-tight">Frequently asked questions</h2>
|
||||
|
||||
<dl class="mt-10 space-y-8 divide-y divide-gray-500">
|
||||
<FAQItem question={`What is the "Legacy Plan"?`}>
|
||||
<p class="mb-4">
|
||||
Legacy accounts have access to existing projects and features, but
|
||||
cannot create new projects or use new features.
|
||||
Legacy accounts have access to existing projects and features, but cannot create new projects or use new
|
||||
features.
|
||||
</p>
|
||||
<p>
|
||||
If you upgrade to a paid plan and then downgrade again, you will still
|
||||
have access to your Legacy projects and features, but any new projects
|
||||
and features created on a paid plan will no longer work.
|
||||
If you upgrade to a paid plan and then downgrade again, you will still have access to your Legacy projects and
|
||||
features, but any new projects and features created on a paid plan will no longer work.
|
||||
</p>
|
||||
</FAQItem>
|
||||
|
||||
<FAQItem question="Giving Back to the Open Source Community">
|
||||
<p class="mb-4">
|
||||
PocketHost is committed to giving back to the open source community that
|
||||
helps create PocketHost and PocketBase.
|
||||
PocketHost is committed to giving back to the open source community that helps create PocketHost and PocketBase.
|
||||
</p>
|
||||
<p class="mb-4">
|
||||
10% of net proceeds (after expenses) are donated back to the community.
|
||||
Specifically, PocketHost makes donations to the PocketBase project and
|
||||
major contributors to the PocketHost project.
|
||||
</p>
|
||||
<p>
|
||||
In addition, 1% of membership fees is collected by Stripe to reduce
|
||||
carbon footprints around the world.
|
||||
10% of net proceeds (after expenses) are donated back to the community. Specifically, PocketHost makes donations
|
||||
to the PocketBase project and major contributors to the PocketHost project.
|
||||
</p>
|
||||
<p>In addition, 1% of membership fees is collected by Stripe to reduce carbon footprints around the world.</p>
|
||||
</FAQItem>
|
||||
|
||||
<FAQItem question="Fair Use Policy">
|
||||
<p class="mb-4">
|
||||
When we say 'unlimited', we mean it in the Fair Use sense of the word.
|
||||
Obviously, everything has limits. In our study of PocketHost usage
|
||||
patterns, we found that even the busiest and most successful PocketHost
|
||||
instances rarely stress our system.
|
||||
When we say 'unlimited', we mean it in the Fair Use sense of the word. Obviously, everything has limits. In our
|
||||
study of PocketHost usage patterns, we found that even the busiest and most successful PocketHost instances
|
||||
rarely stress our system.
|
||||
</p>
|
||||
<p class="mb-4">
|
||||
PocketHost is a haven for developers who want to launch and iterate
|
||||
quickly on ideas without worrying about metering and infrastructure.
|
||||
PocketHost is a haven for developers who want to launch and iterate quickly on ideas without worrying about
|
||||
metering and infrastructure.
|
||||
</p>
|
||||
<p class="mb-4">
|
||||
If your app gets big and it starts affecting the system, we'll talk
|
||||
about an enterprise plan or a dedicated setup.
|
||||
If your app gets big and it starts affecting the system, we'll talk about an enterprise plan or a dedicated
|
||||
setup.
|
||||
</p>
|
||||
<p>
|
||||
Please enjoy PocketHost knowing that you can use as much storage,
|
||||
bandwidth, and CPU as your application requires under normal operating
|
||||
conditions. Let us handle the hosting so you can get back to work.
|
||||
Please enjoy PocketHost knowing that you can use as much storage, bandwidth, and CPU as your application
|
||||
requires under normal operating conditions. Let us handle the hosting so you can get back to work.
|
||||
</p>
|
||||
</FAQItem>
|
||||
|
||||
<FAQItem question="Cancellation and Refunds">
|
||||
<p class="mb-4">Short version: We only want your money if you are happy.</p>
|
||||
<p class="mb-4">
|
||||
Short version: We only want your money if you are happy.
|
||||
</p>
|
||||
<p class="mb-4">
|
||||
Long version: If you need to cancel your membership for any reason,
|
||||
please <a class="link" href={DISCORD_URL}
|
||||
Long version: If you need to cancel your membership for any reason, please <a class="link" href={DISCORD_URL}
|
||||
>contact <code>.noaxis</code> on Discord</a
|
||||
>. If you cancel within the first 5 days of a signup or renewal, we'll
|
||||
refund the full amount. Otherwise, we'll pro-rate it. Sound good?
|
||||
>. If you cancel within the first 5 days of a signup or renewal, we'll refund the full amount. Otherwise, we'll
|
||||
pro-rate it. Sound good?
|
||||
</p>
|
||||
<p>
|
||||
If you create additional instances and then downgrade to the free plan,
|
||||
the extra instances will remain accessible in your dashboard, but they
|
||||
will not run.
|
||||
If you create additional instances and then downgrade to the free plan, the extra instances will remain
|
||||
accessible in your dashboard, but they will not run.
|
||||
</p>
|
||||
</FAQItem>
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each values($globalInstancesStore).sort( (a, b) => a.subdomain.localeCompare(b.subdomain), ) as instance, index}
|
||||
{#each values($globalInstancesStore).sort((a, b) => a.subdomain.localeCompare(b.subdomain)) as instance, index}
|
||||
<button
|
||||
class={`card min-w-80 lg:max-w-80 flex-1 m-4 transition hover:bg-base-300 ${instance.power ? 'bg-neutral' : 'bg-base-200'}`}
|
||||
on:click={(_) => goto(`/instances/${instance.id}`)}
|
||||
@ -34,9 +34,7 @@
|
||||
<span>{instance.subdomain.length > 15 ? instance.subdomain.slice(0, 15) + '...' : instance.subdomain}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle {instance.power
|
||||
? 'toggle-success'
|
||||
: 'bg-red-500 hover:bg-red-500'}"
|
||||
class="toggle {instance.power ? 'toggle-success' : 'bg-red-500 hover:bg-red-500'}"
|
||||
checked={instance.power}
|
||||
on:click={(e) => e.stopPropagation()}
|
||||
on:change={handlePowerChange(instance.id)}
|
||||
@ -46,8 +44,7 @@
|
||||
<p class="text-left">
|
||||
<span class="text-gray-400"
|
||||
>Version {instance.version}
|
||||
<span class={instance.power ? 'hidden' : ''}>- Powered Off</span
|
||||
></span
|
||||
<span class={instance.power ? 'hidden' : ''}>- Powered Off</span></span
|
||||
>
|
||||
</p>
|
||||
|
||||
@ -63,11 +60,7 @@
|
||||
target="_blank"
|
||||
on:click={(e) => e.stopPropagation()}
|
||||
>
|
||||
<img
|
||||
src="/images/pocketbase-logo.svg"
|
||||
alt="PocketBase Logo"
|
||||
class="w-6"
|
||||
/>
|
||||
<img src="/images/pocketbase-logo.svg" alt="PocketBase Logo" class="w-6" />
|
||||
<span>Admin</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -1,9 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
globalInstancesStore,
|
||||
userSubscriptionType,
|
||||
userStore,
|
||||
} from '$util/stores'
|
||||
import { globalInstancesStore, userSubscriptionType, userStore } from '$util/stores'
|
||||
import { values } from '@s-libs/micro-dash'
|
||||
import InstanceList from './InstanceList.svelte'
|
||||
import { SubscriptionType } from 'pockethost/common'
|
||||
@ -18,29 +14,21 @@
|
||||
<title>Dashboard - PocketHost</title>
|
||||
</svelte:head>
|
||||
|
||||
<div
|
||||
class="flex flex-row items-center justify-between mb-6 gap-4 pl-4 sm:pl-6 lg:pl-8 pr-4"
|
||||
>
|
||||
<div class="flex flex-row items-center justify-between mb-6 gap-4 pl-4 sm:pl-6 lg:pl-8 pr-4">
|
||||
<h2 class="text-4xl text-base-content font-bold capitalize">Dashboard</h2>
|
||||
|
||||
<a href="/instances/new" class="m-3 btn btn-primary">
|
||||
<Fa icon={faPlus} /> New Instance</a
|
||||
>
|
||||
<a href="/instances/new" class="m-3 btn btn-primary"> <Fa icon={faPlus} /> New Instance</a>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col space-x-4 items-center justify-center">
|
||||
{#if maxInstances > 0}
|
||||
{#if instanceCount > maxInstances}
|
||||
<p class="text-center text-error">
|
||||
You have exceeded your instance limit.
|
||||
</p>
|
||||
<p class="text-center text-error">You have exceeded your instance limit.</p>
|
||||
{/if}
|
||||
<div class="flex flex-row space-x-4 items-center justify-center">
|
||||
<div>Instances</div>
|
||||
<progress
|
||||
class="progress {instanceCount > maxInstances
|
||||
? 'progress-error'
|
||||
: 'progress-primary'} w-48 md:w-80"
|
||||
class="progress {instanceCount > maxInstances ? 'progress-error' : 'progress-primary'} w-48 md:w-80"
|
||||
value={instanceCount}
|
||||
max={maxInstances}
|
||||
></progress>
|
||||
|
||||
@ -9,10 +9,7 @@
|
||||
import { type InstanceId } from 'pockethost/common'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import Fa from 'svelte-fa'
|
||||
import {
|
||||
faExternalLinkAlt,
|
||||
faTriangleExclamation,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { faExternalLinkAlt, faTriangleExclamation } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
let isReady = false
|
||||
$: {
|
||||
@ -45,20 +42,14 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title
|
||||
>{isReady ? $instance.subdomain : 'Instance'} overview - PocketHost</title
|
||||
>
|
||||
<title>{isReady ? $instance.subdomain : 'Instance'} overview - PocketHost</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if isReady}
|
||||
<div
|
||||
class="flex flex-row items-center justify-between mb-6 gap-4 pl-4 sm:pl-6 lg:pl-8 pr-2"
|
||||
>
|
||||
<div class="flex flex-row items-center justify-between mb-6 gap-4 pl-4 sm:pl-6 lg:pl-8 pr-2">
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<h2
|
||||
class="text-4xl md:text-left text-base-content font-bold mb-3 break-words"
|
||||
>
|
||||
<h2 class="text-4xl md:text-left text-base-content font-bold mb-3 break-words">
|
||||
{$instance.subdomain}
|
||||
</h2>
|
||||
</div>
|
||||
@ -77,19 +68,13 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Toggle
|
||||
checked={$instance.power}
|
||||
onChange={handlePowerChange($instance.id)}
|
||||
/>
|
||||
<Toggle checked={$instance.power} onChange={handlePowerChange($instance.id)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if !$instance.power}
|
||||
<div class="px-4 mb-8">
|
||||
<AlertBar
|
||||
message="This instance is turned off and will not respond to requests"
|
||||
type="warning"
|
||||
/>
|
||||
<AlertBar message="This instance is turned off and will not respond to requests" type="warning" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@ -101,29 +86,17 @@
|
||||
<a href={`/instances/${id}`} class={activeClass(id)}>Overview</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href={`/instances/${id}/secrets`} class={activeClass(`secrets`)}
|
||||
>Secrets</a
|
||||
>
|
||||
<a href={`/instances/${id}/secrets`} class={activeClass(`secrets`)}>Secrets</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href={`/instances/${id}/logs`} class={activeClass(`logs`)}>Logs</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href={`/instances/${id}/ftp`} class={activeClass(`ftp`)}
|
||||
>FTP Access</a
|
||||
>
|
||||
<a href={`/instances/${id}/ftp`} class={activeClass(`ftp`)}>FTP Access</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={INSTANCE_ADMIN_URL($instance)}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
src="/images/pocketbase-logo.svg"
|
||||
alt="PocketBase Logo"
|
||||
class="w-6 inline-block"
|
||||
/>
|
||||
<a href={INSTANCE_ADMIN_URL($instance)} rel="noreferrer" target="_blank">
|
||||
<img src="/images/pocketbase-logo.svg" alt="PocketBase Logo" class="w-6 inline-block" />
|
||||
Admin
|
||||
<Fa icon={faExternalLinkAlt} class="ml-2 text-xs" />
|
||||
</a>
|
||||
@ -136,36 +109,22 @@
|
||||
</div>
|
||||
<ul class="menu text-base-content">
|
||||
<li>
|
||||
<a href={`/instances/${id}/version`} class={activeClass(`version`)}
|
||||
>Change Version</a
|
||||
>
|
||||
<a href={`/instances/${id}/version`} class={activeClass(`version`)}>Change Version</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href={`/instances/${id}/domain`} class={activeClass(`domain`)}
|
||||
>Custom Domain</a
|
||||
>
|
||||
<a href={`/instances/${id}/domain`} class={activeClass(`domain`)}>Custom Domain</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={`/instances/${id}/admin-sync`}
|
||||
class={activeClass(`admin-sync`)}>Admin Sync</a
|
||||
>
|
||||
<a href={`/instances/${id}/admin-sync`} class={activeClass(`admin-sync`)}>Admin Sync</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href={`/instances/${id}/dev`} class={activeClass(`dev`)}
|
||||
>Dev Mode</a
|
||||
>
|
||||
<a href={`/instances/${id}/dev`} class={activeClass(`dev`)}>Dev Mode</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href={`/instances/${id}/rename`} class={activeClass(`rename`)}
|
||||
>Rename</a
|
||||
>
|
||||
<a href={`/instances/${id}/rename`} class={activeClass(`rename`)}>Rename</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={`/instances/${id}/delete`}
|
||||
class={`text-error ${activeClass(`delete`)}`}>Delete</a
|
||||
>
|
||||
<a href={`/instances/${id}/delete`} class={`text-error ${activeClass(`delete`)}`}>Delete</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -38,9 +38,10 @@
|
||||
<div class="text-accent">Notice: You are in Custom Domain mode</div>
|
||||
{:else}
|
||||
<div class="text-error">
|
||||
Notice: You are in Custom Domain mode but it is not active and will not
|
||||
work. Go find <a href={DISCORD_URL} target="_blank" class="link"
|
||||
><code>.noaxis</code> on Discord</a
|
||||
Notice: You are in Custom Domain mode but it is not active and will not work. Go find <a
|
||||
href={DISCORD_URL}
|
||||
target="_blank"
|
||||
class="link"><code>.noaxis</code> on Discord</a
|
||||
> to get set up.
|
||||
</div>
|
||||
{/if}
|
||||
@ -57,18 +58,10 @@
|
||||
<p>Additional Resources:</p>
|
||||
<ul class="list-disc pl-4">
|
||||
<li>
|
||||
<a
|
||||
href={`https://pocketbase.io/docs/api-records/`}
|
||||
target="_blank"
|
||||
class="link">PocketBase Web APIs</a
|
||||
>
|
||||
<a href={`https://pocketbase.io/docs/api-records/`} target="_blank" class="link">PocketBase Web APIs</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://www.npmjs.com/package/pocketbase"
|
||||
target="_blank"
|
||||
class="link">PocketBase NPM Package</a
|
||||
>
|
||||
<a href="https://www.npmjs.com/package/pocketbase" target="_blank" class="link">PocketBase NPM Package</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -16,17 +16,12 @@
|
||||
|
||||
<div class="form-control w-fit">
|
||||
<label class="label cursor-pointer">
|
||||
<span class="label-text text-lg mr-2"
|
||||
>
|
||||
<span class="font-bold text-{checked ? onClass : offClass}"
|
||||
>{checked ? onText : offText}</span
|
||||
></span
|
||||
<span class="label-text text-lg mr-2">
|
||||
<span class="font-bold text-{checked ? onClass : offClass}">{checked ? onText : offText}</span></span
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle bg-{checked ? onClass : offClass} hover:bg-{checked
|
||||
? onClass
|
||||
: offClass}"
|
||||
class="toggle bg-{checked ? onClass : offClass} hover:bg-{checked ? onClass : offClass}"
|
||||
{checked}
|
||||
on:change={handleChange}
|
||||
/>
|
||||
|
||||
@ -25,7 +25,8 @@
|
||||
<CardHeader documentation={`/docs/admin-sync`}>Admin Sync</CardHeader>
|
||||
|
||||
<p class="mb-8">
|
||||
Admin Sync ensures that your instance always has an admin account that matches the login credentials of your pockethost.io account.
|
||||
Admin Sync ensures that your instance always has an admin account that matches the login credentials of your
|
||||
pockethost.io account.
|
||||
</p>
|
||||
|
||||
<ErrorMessage message={errorMessage} />
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
|
||||
// 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.`,
|
||||
`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
|
||||
@ -66,10 +66,7 @@
|
||||
<CardHeader documentation={`/docs/delete`}>Delete Instance</CardHeader>
|
||||
|
||||
{#if power}
|
||||
<AlertBar
|
||||
message="Instance must be powered off before deleting."
|
||||
type="error"
|
||||
/>
|
||||
<AlertBar message="Instance must be powered off before deleting." type="error" />
|
||||
{/if}
|
||||
|
||||
<div class="mb-8">
|
||||
@ -86,15 +83,8 @@
|
||||
|
||||
<ErrorMessage message={errorMessage} />
|
||||
|
||||
<form
|
||||
class="flex change-version-form-container-query gap-4"
|
||||
on:submit={handleSave}
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-error"
|
||||
disabled={power || isButtonDisabled}>Delete Instance</button
|
||||
>
|
||||
<form class="flex change-version-form-container-query gap-4" on:submit={handleSave}>
|
||||
<button type="submit" class="btn btn-error" disabled={power || isButtonDisabled}>Delete Instance</button>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
|
||||
@ -23,8 +23,8 @@
|
||||
<CardHeader documentation={`/docs/dev-mode`}>Dev Mode</CardHeader>
|
||||
|
||||
<p class="mb-8">
|
||||
Starting with PocketBase v0.20.1, your instance will show debugging output in
|
||||
the instance logs. Performance is degraded while Dev Mode is active.
|
||||
Starting with PocketBase v0.20.1, your instance will show debugging output in the instance logs. Performance is
|
||||
degraded while Dev Mode is active.
|
||||
</p>
|
||||
|
||||
<ErrorMessage message={errorMessage} />
|
||||
|
||||
@ -4,8 +4,7 @@
|
||||
import CardHeader from '$components/cards/CardHeader.svelte'
|
||||
import { INSTANCE_BARE_HOST } from '$src/env'
|
||||
import { client } from '$src/pocketbase-client'
|
||||
import { isUserPaid, userSubscriptionType } from '$util/stores'
|
||||
import { SubscriptionType } from 'pockethost/common'
|
||||
import { isUserPaid } from '$util/stores'
|
||||
import { dns } from 'svelte-highlight/languages'
|
||||
import { instance } from '../store'
|
||||
|
||||
@ -26,13 +25,10 @@
|
||||
let errorMessage = ''
|
||||
let successMessage = ''
|
||||
|
||||
const regex =
|
||||
/^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,24}$/
|
||||
const regex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,24}$/
|
||||
|
||||
$: {
|
||||
isButtonDisabled =
|
||||
(!!formCname.trim() && !regex.test(formCname)) ||
|
||||
(!formCname.trim() && !cname.trim())
|
||||
isButtonDisabled = (!!formCname.trim() && !regex.test(formCname)) || (!formCname.trim() && !cname.trim())
|
||||
}
|
||||
const onRename = (e: Event) => {
|
||||
e.preventDefault()
|
||||
@ -73,20 +69,13 @@
|
||||
</script>
|
||||
|
||||
<div class="max-w-2xl">
|
||||
<CardHeader documentation={`/docs/custom-domains`}>
|
||||
Custom Domain (CNAME)
|
||||
</CardHeader>
|
||||
<CardHeader documentation={`/docs/custom-domains`}>Custom Domain (CNAME)</CardHeader>
|
||||
|
||||
<div class="mb-8">
|
||||
Use a custom domain (CNAME) with your PocketHost instance.
|
||||
</div>
|
||||
<div class="mb-8">Use a custom domain (CNAME) with your PocketHost instance.</div>
|
||||
{#if cname && regex.test(formCname.trim())}
|
||||
<div class="mb-8">Go to your DNS provider and add a CNAME entry.</div>
|
||||
<div class="mb-4">
|
||||
<CodeSample
|
||||
code={`${formCname} CNAME ${INSTANCE_BARE_HOST($instance)}`}
|
||||
language={dns}
|
||||
/>
|
||||
<CodeSample code={`${formCname} CNAME ${INSTANCE_BARE_HOST($instance)}`} language={dns} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@ -100,18 +89,11 @@
|
||||
type="warning"
|
||||
/>
|
||||
{:else}
|
||||
<AlertBar
|
||||
message={`Your custom domain name is active.`}
|
||||
type="success"
|
||||
flash
|
||||
/>
|
||||
<AlertBar message={`Your custom domain name is active.`} type="success" flash />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<form
|
||||
class="flex rename-instance-form-container-query gap-4"
|
||||
on:submit={onRename}
|
||||
>
|
||||
<form class="flex rename-instance-form-container-query gap-4" on:submit={onRename}>
|
||||
<input
|
||||
title="Only valid domain name patterns are allowed"
|
||||
type="text"
|
||||
@ -119,9 +101,7 @@
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
|
||||
<button type="submit" class="btn btn-error" disabled={isButtonDisabled}
|
||||
>Update Custom Domain</button
|
||||
>
|
||||
<button type="submit" class="btn btn-error" disabled={isButtonDisabled}>Update Custom Domain</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
@ -16,10 +16,7 @@
|
||||
</script>
|
||||
|
||||
<CardHeader documentation={`/docs/ftp`}>FTP Access</CardHeader>
|
||||
<div class="mb-8">
|
||||
Securely access your instance files via FTPS. Use your PocketHost account
|
||||
login and password.
|
||||
</div>
|
||||
<div class="mb-8">Securely access your instance files via FTPS. Use your PocketHost account login and password.</div>
|
||||
|
||||
<div class="mb-12">
|
||||
<CodeSample code={`ftp ${ftpUrl}`} language={bash} />
|
||||
@ -36,10 +33,7 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>pb_data</th>
|
||||
<td
|
||||
>The PocketBase data directory, including upload storage and database
|
||||
backups</td
|
||||
>
|
||||
<td>The PocketBase data directory, including upload storage and database backups</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>pb_public</th>
|
||||
|
||||
@ -1,21 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { client } from '$src/pocketbase-client'
|
||||
import { mkCleanup } from '$util/componentCleanup'
|
||||
import {
|
||||
StreamNames,
|
||||
type Unsubscribe,
|
||||
type InstanceLogFields,
|
||||
} from 'pockethost/common'
|
||||
import { StreamNames, type Unsubscribe, type InstanceLogFields } from 'pockethost/common'
|
||||
import { onMount, tick } from 'svelte'
|
||||
import { derived, writable } from 'svelte/store'
|
||||
import { instance } from '../store'
|
||||
import CardHeader from '$src/components/cards/CardHeader.svelte'
|
||||
import Fa from 'svelte-fa'
|
||||
import {
|
||||
faArrowDown,
|
||||
faClose,
|
||||
faUpRightAndDownLeftFromCenter,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { faArrowDown, faClose, faUpRightAndDownLeftFromCenter } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
$: ({ id } = $instance)
|
||||
|
||||
@ -40,9 +32,7 @@
|
||||
|
||||
// This will open the full screen modal
|
||||
const handleFullScreenModal = () => {
|
||||
const modal = document.getElementById(
|
||||
'loggingFullscreenModal',
|
||||
) as HTMLDialogElement
|
||||
const modal = document.getElementById('loggingFullscreenModal') as HTMLDialogElement
|
||||
modal?.showModal()
|
||||
}
|
||||
|
||||
@ -103,34 +93,23 @@
|
||||
<CardHeader>Logs</CardHeader>
|
||||
|
||||
<div class="mb-4">
|
||||
Instance logs appear here in realtime, including <code>console.log</code> from
|
||||
JavaScript hooks.
|
||||
Instance logs appear here in realtime, including <code>console.log</code> from JavaScript hooks.
|
||||
</div>
|
||||
|
||||
<dialog id="loggingFullscreenModal" class="modal backdrop-blur">
|
||||
<div class="modal-box max-w-[90vw] h-[90vh]">
|
||||
<button
|
||||
class="btn btn-sm absolute top-[6px] right-[6px]"
|
||||
on:click={() => (autoScroll = !autoScroll)}
|
||||
<button class="btn btn-sm absolute top-[6px] right-[6px]" on:click={() => (autoScroll = !autoScroll)}
|
||||
>AutoScroll
|
||||
<Fa icon={autoScroll ? faArrowDown : faClose} />
|
||||
</button>
|
||||
<h3 class="font-bold text-lg">Instance Logging</h3>
|
||||
|
||||
<div
|
||||
class="py-4 h-[80vh] overflow-y-scroll flex flex-col"
|
||||
bind:this={logElementPopup}
|
||||
>
|
||||
<div class="py-4 h-[80vh] overflow-y-scroll flex flex-col" bind:this={logElementPopup}>
|
||||
{#each $logs as log}
|
||||
<div
|
||||
class="px-4 text-[16px] font-mono flex align-center"
|
||||
data-prefix=">"
|
||||
>
|
||||
<div class="px-4 text-[16px] font-mono flex align-center" data-prefix=">">
|
||||
<div>
|
||||
<span class="mr-1 text-accent">{log.time}</span>
|
||||
<span class="mr-1 text-base-content {logColor(log.stream)}"
|
||||
>{@html logText(log)}</span
|
||||
>
|
||||
<span class="mr-1 text-base-content {logColor(log.stream)}">{@html logText(log)}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
@ -158,9 +137,7 @@
|
||||
<div>
|
||||
<span class="mr-1 text-accent">{log.time}</span>
|
||||
|
||||
<span class="mr-1 text-base-content {logColor(log.stream)}"
|
||||
>{@html logText(log)}</span
|
||||
>
|
||||
<span class="mr-1 text-base-content {logColor(log.stream)}">{@html logText(log)}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
@ -36,9 +36,7 @@
|
||||
const instanceNameValidation = formSubdomain.trim().replace(/[0-9]/g, '')
|
||||
|
||||
// Prompt the user to confirm the version change
|
||||
const confirmVersionChange = confirm(
|
||||
`Are you sure you want to rename your instance to ${instanceNameValidation}?`,
|
||||
)
|
||||
const confirmVersionChange = confirm(`Are you sure you want to rename your instance to ${instanceNameValidation}?`)
|
||||
|
||||
// If they select yes, then update the version in pocketbase
|
||||
if (confirmVersionChange) {
|
||||
@ -62,24 +60,17 @@
|
||||
</script>
|
||||
|
||||
<div class="max-w-lg">
|
||||
<CardHeader documentation={`/docs/rename-instance`}
|
||||
>Rename Instance</CardHeader
|
||||
>
|
||||
<CardHeader documentation={`/docs/rename-instance`}>Rename Instance</CardHeader>
|
||||
|
||||
<p class="mb-8">
|
||||
Renaming your instance will cause it to become <strong class="text-error"
|
||||
>inaccessible</strong
|
||||
> by the old instance name. You also may not be able to change it back if someone
|
||||
else choose it.
|
||||
Renaming your instance will cause it to become <strong class="text-error">inaccessible</strong> by the old instance name.
|
||||
You also may not be able to change it back if someone else choose it.
|
||||
</p>
|
||||
|
||||
<AlertBar message={successMessage} type="success" flash />
|
||||
<AlertBar message={errorMessage} type="error" />
|
||||
|
||||
<form
|
||||
class="flex rename-instance-form-container-query gap-4"
|
||||
on:submit={onRename}
|
||||
>
|
||||
<form class="flex rename-instance-form-container-query gap-4" on:submit={onRename}>
|
||||
<input
|
||||
title="Only letters and dashes are allowed"
|
||||
required
|
||||
@ -88,9 +79,7 @@
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
|
||||
<button type="submit" class="btn btn-error" disabled={isButtonDisabled}
|
||||
>Rename Instance</button
|
||||
>
|
||||
<button type="submit" class="btn btn-error" disabled={isButtonDisabled}>Rename Instance</button>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import { assertExists } from 'pockethost/common'
|
||||
import { instance } from '../store'
|
||||
import SecretsInner from './SecretsInner.svelte'
|
||||
import CardHeader from '$src/components/cards/CardHeader.svelte';
|
||||
import CardHeader from '$src/components/cards/CardHeader.svelte'
|
||||
|
||||
$: ({ status, version, id } = $instance)
|
||||
|
||||
|
||||
@ -2,10 +2,7 @@
|
||||
import AlertBar from '$components/AlertBar.svelte'
|
||||
import { client } from '$src/pocketbase-client/index.js'
|
||||
import { reduce } from '@s-libs/micro-dash'
|
||||
import {
|
||||
SECRET_KEY_REGEX,
|
||||
type UpdateInstancePayload,
|
||||
} from 'pockethost/common'
|
||||
import { SECRET_KEY_REGEX, type UpdateInstancePayload } from 'pockethost/common'
|
||||
import { instance } from '../store.js'
|
||||
import { items } from './stores.js'
|
||||
import { faFloppyDisk } from '@fortawesome/free-solid-svg-icons'
|
||||
@ -58,7 +55,7 @@
|
||||
c[name] = value
|
||||
return c
|
||||
},
|
||||
{} as NonNullable<UpdateInstancePayload['fields']['secrets']>,
|
||||
{} as NonNullable<UpdateInstancePayload['fields']['secrets']>
|
||||
),
|
||||
},
|
||||
})
|
||||
@ -87,16 +84,12 @@
|
||||
|
||||
<div class="mb-8">
|
||||
{#if successfulSave}
|
||||
<AlertBar
|
||||
message="Your new secret has been saved."
|
||||
type="success"
|
||||
/>
|
||||
<AlertBar message="Your new secret has been saved." type="success" />
|
||||
{/if}
|
||||
|
||||
<AlertBar message={errorMessage} type="error" />
|
||||
|
||||
<form on:submit={handleSubmit} class="mb-4">
|
||||
|
||||
<div class="flex flex-row gap-4 mb-4">
|
||||
<label class="flex-1 form-control">
|
||||
<input
|
||||
@ -104,7 +97,7 @@
|
||||
type="text"
|
||||
bind:value={secretKey}
|
||||
placeholder="Key"
|
||||
class={`input input-bordered ${!isKeyValid && secretKey.length > 0 ? "input-error text-error" : ""}`}
|
||||
class={`input input-bordered ${!isKeyValid && secretKey.length > 0 ? 'input-error text-error' : ''}`}
|
||||
/>
|
||||
{#if !isKeyValid && secretKey.length > 0}
|
||||
<div class="label">
|
||||
@ -113,7 +106,6 @@
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
</label>
|
||||
|
||||
<div class="flex-1 form-control">
|
||||
@ -133,13 +125,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- {#if !isKeyValid && secretKey.length > 0}
|
||||
<AlertBar
|
||||
message="All key names must be upper case, alphanumeric, and may include underscore (_)."
|
||||
type="error"
|
||||
/>
|
||||
{/if} -->
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
c[name] = value
|
||||
return c
|
||||
},
|
||||
{} as NonNullable<UpdateInstancePayload['fields']['secrets']>,
|
||||
{} as NonNullable<UpdateInstancePayload['fields']['secrets']>
|
||||
),
|
||||
},
|
||||
})
|
||||
@ -42,17 +42,13 @@
|
||||
{#each $items as item}
|
||||
<tr transition:fade>
|
||||
<th>{item.name}</th>
|
||||
<td
|
||||
>{item.value.slice(0, 2) +
|
||||
item.value.slice(2).replaceAll(/./g, '*')}</td
|
||||
>
|
||||
<td>{item.value.slice(0, 2) + item.value.slice(2).replaceAll(/./g, '*')}</td>
|
||||
<td class="text-right">
|
||||
<button
|
||||
aria-label="Delete"
|
||||
on:click={handleDelete(item.name)}
|
||||
type="button"
|
||||
class="btn btn-sm btn-square btn-outline btn-warning"
|
||||
><Fa icon={faTrash} /></button
|
||||
class="btn btn-sm btn-square btn-outline btn-warning"><Fa icon={faTrash} /></button
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -21,17 +21,14 @@
|
||||
`// pb_hooks/env-test.pb.js\n\n` +
|
||||
($items.length > 0
|
||||
? $items
|
||||
.map(
|
||||
({ name, value }) =>
|
||||
`const ${name} = process.env.${name}\nconsole.log("${name}: ", ${name})`,
|
||||
)
|
||||
.map(({ name, value }) => `const ${name} = process.env.${name}\nconsole.log("${name}: ", ${name})`)
|
||||
.join('\n')
|
||||
: `const YOUR_KEY = process.env.YOUR_KEY`)
|
||||
</script>
|
||||
|
||||
<div class="mb-4">
|
||||
These secrets are forwarded to your <code>pocketbase</code> as environment
|
||||
variables, which are also accessible from any <code>pb_hooks</code> you have created.
|
||||
These secrets are forwarded to your <code>pocketbase</code> as environment variables, which are also accessible from
|
||||
any <code>pb_hooks</code> you have created.
|
||||
</div>
|
||||
|
||||
<!-- If the user has any secrets, render them in a code block -->
|
||||
|
||||
@ -47,10 +47,7 @@ function createItems(initialItems: SecretsArray) {
|
||||
const { name, value } = sanitize(item)
|
||||
|
||||
return update((n) => {
|
||||
return formatInput([
|
||||
...n.filter((i) => i.name !== name),
|
||||
{ name, value },
|
||||
])
|
||||
return formatInput([...n.filter((i) => i.name !== name), { name, value }])
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
@ -48,9 +48,7 @@
|
||||
isButtonDisabled = true
|
||||
|
||||
// Prompt the user to confirm the version change
|
||||
const confirmVersionChange = confirm(
|
||||
`Are you sure you want to change the version to ${selectedVersion}?`,
|
||||
)
|
||||
const confirmVersionChange = confirm(`Are you sure you want to change the version to ${selectedVersion}?`)
|
||||
|
||||
// If they select yes, then update the version in pocketbase
|
||||
if (confirmVersionChange) {
|
||||
@ -81,27 +79,19 @@
|
||||
|
||||
<div class="max-w-xl">
|
||||
{#if power}
|
||||
<AlertBar
|
||||
message="Your instance must be powered off to change the version."
|
||||
type="error"
|
||||
/>
|
||||
<AlertBar message="Your instance must be powered off to change the version." type="error" />
|
||||
{/if}
|
||||
|
||||
<div class="mb-8">
|
||||
We recommend you <strong>do a full backup</strong>
|
||||
before making a change. We support the latest patch of
|
||||
<a href="https://github.com/pocketbase/pocketbase/releases" class="link"
|
||||
>every minor release</a
|
||||
> of PocketBase.
|
||||
<a href="https://github.com/pocketbase/pocketbase/releases" class="link">every minor release</a> of PocketBase.
|
||||
</div>
|
||||
|
||||
{#if $is23Available || is23OrHigher}
|
||||
<div class="mb-8 bg-info p-4 rounded text-info-content">
|
||||
<p class="font-bold text-xl">Attention v0.23.* users:</p>
|
||||
<p>
|
||||
v0.22.* to v0.23.* is a major migration boundary and requires a manual
|
||||
migration process.
|
||||
</p>
|
||||
<p>v0.22.* to v0.23.* is a major migration boundary and requires a manual migration process.</p>
|
||||
<table class="table">
|
||||
<thead class="text-info-content">
|
||||
<tr>
|
||||
@ -125,8 +115,7 @@
|
||||
<td>0.23.*</td>
|
||||
<td><=v0.22.*</td>
|
||||
<td
|
||||
>Create a new <=v0.22.* instance and migrate your data
|
||||
manually. Refer to the <a
|
||||
>Create a new <=v0.22.* instance and migrate your data manually. Refer to the <a
|
||||
href="https://github.com/pocketbase/pocketbase/releases/tag/v0.23.0"
|
||||
class="link">v0.23.* manual upgrade process</a
|
||||
> and attempt to reverse it.</td
|
||||
@ -140,17 +129,10 @@
|
||||
<AlertBar message={successMessage} type="success" flash />
|
||||
<AlertBar message={errorMessage} type="error" />
|
||||
|
||||
<form
|
||||
class="flex change-version-form-container-query gap-4"
|
||||
on:submit={handleSave}
|
||||
>
|
||||
<form class="flex change-version-form-container-query gap-4" on:submit={handleSave}>
|
||||
<VersionPicker bind:selectedVersion bind:versions />
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-error"
|
||||
disabled={power || isButtonDisabled}>Change Version</button
|
||||
>
|
||||
<button type="submit" class="btn btn-error" disabled={power || isButtonDisabled}>Change Version</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
@ -17,12 +17,7 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<select
|
||||
class="select select-bordered w-full"
|
||||
bind:value={selectedVersion}
|
||||
on:change={handleSelect}
|
||||
{disabled}
|
||||
>
|
||||
<select class="select select-bordered w-full" bind:value={selectedVersion} on:change={handleSelect} {disabled}>
|
||||
<option value="" disabled>Select a version</option>
|
||||
{#each versions as version}
|
||||
<option value={version}>{version}</option>
|
||||
|
||||
@ -1,10 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
globalInstancesStore,
|
||||
userSubscriptionType,
|
||||
versions,
|
||||
userStore,
|
||||
} from '$util/stores'
|
||||
import { globalInstancesStore, userSubscriptionType, versions, userStore } from '$util/stores'
|
||||
import { values } from '@s-libs/micro-dash'
|
||||
import Creator from './Creator.svelte'
|
||||
import Paywall from './Paywall.svelte'
|
||||
@ -24,9 +19,7 @@
|
||||
|
||||
<div class="flex items-center justify-center">
|
||||
<div class="max-w-md">
|
||||
<h2 class="text-4xl text-base-content font-bold capitalize mb-6">
|
||||
Create A New Instance
|
||||
</h2>
|
||||
<h2 class="text-4xl text-base-content font-bold capitalize mb-6">Create A New Instance</h2>
|
||||
|
||||
{#if canCreate}
|
||||
<Creator />
|
||||
|
||||
@ -3,10 +3,7 @@
|
||||
import CardHeader from '$components/cards/CardHeader.svelte'
|
||||
import { client } from '$src/pocketbase-client'
|
||||
import { handleCreateNewInstance } from '$util/database'
|
||||
import {
|
||||
faArrowsRotate,
|
||||
faCircleExclamation,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { faArrowsRotate, faCircleExclamation } from '@fortawesome/free-solid-svg-icons'
|
||||
import Fa from 'svelte-fa'
|
||||
import { writable } from 'svelte/store'
|
||||
import { slide } from 'svelte/transition'
|
||||
@ -38,10 +35,7 @@
|
||||
fetching: true,
|
||||
}))
|
||||
|
||||
await client().client.send(
|
||||
`/api/signup?name=${encodeURIComponent(name)}`,
|
||||
{},
|
||||
)
|
||||
await client().client.send(`/api/signup?name=${encodeURIComponent(name)}`, {})
|
||||
|
||||
instanceInfo.update((info) => ({
|
||||
...info,
|
||||
@ -68,8 +62,7 @@
|
||||
|
||||
// Disable the form button until all fields are filled out
|
||||
let isFormButtonDisabled: boolean = true
|
||||
$: isFormButtonDisabled =
|
||||
$instanceInfo.name.length === 0 || !$instanceInfo.available
|
||||
$: isFormButtonDisabled = $instanceInfo.name.length === 0 || !$instanceInfo.available
|
||||
|
||||
// Generate a unique name for the PocketHost instance
|
||||
const handleInstanceNameRegeneration = () => {
|
||||
@ -95,18 +88,13 @@
|
||||
<CardHeader>Choose a name for your PocketBase instance.</CardHeader>
|
||||
|
||||
<div class="flex rename-instance-form-container-query gap-4">
|
||||
<input
|
||||
type="text"
|
||||
bind:value={$instanceNameField}
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
<input type="text" bind:value={$instanceNameField} class="input input-bordered w-full" />
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline btn-secondary"
|
||||
aria-label="Regenerate Instance Name"
|
||||
on:click={handleInstanceNameRegeneration}
|
||||
><Fa icon={faArrowsRotate} /></button
|
||||
on:click={handleInstanceNameRegeneration}><Fa icon={faArrowsRotate} /></button
|
||||
>
|
||||
</div>
|
||||
|
||||
@ -114,13 +102,9 @@
|
||||
{#if $instanceInfo.fetching}
|
||||
Verifying...
|
||||
{:else if $instanceInfo.available}
|
||||
<span class="text-success">
|
||||
https://{$instanceInfo.name}.pockethost.io ✔︎</span
|
||||
>
|
||||
<span class="text-success"> https://{$instanceInfo.name}.pockethost.io ✔︎</span>
|
||||
{:else}
|
||||
<span class="text-error">
|
||||
https://{$instanceInfo.name}.pockethost.io ❌</span
|
||||
>
|
||||
<span class="text-error"> https://{$instanceInfo.name}.pockethost.io ❌</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@ -134,11 +118,7 @@
|
||||
<div class="flex items-center justify-center gap-4">
|
||||
<a href="/" class="btn">Cancel</a>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary"
|
||||
disabled={isFormButtonDisabled}
|
||||
>
|
||||
<button type="submit" class="btn btn-primary" disabled={isFormButtonDisabled}>
|
||||
{#if isSubmitting}
|
||||
<span class="loading loading-spinner loading-md"></span>
|
||||
{:else}
|
||||
|
||||
@ -7,11 +7,7 @@
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
stats.set(
|
||||
(
|
||||
(await client().client.collection('stats').getFullList()) || []
|
||||
).pop() || {},
|
||||
)
|
||||
stats.set(((await client().client.collection('stats').getFullList()) || []).pop() || {})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
@ -22,29 +22,20 @@
|
||||
<h1 class="text-6xl font-bold mb-8">Amazingly Simple PocketBase Hosting</h1>
|
||||
|
||||
<p class="text-2xl mb-12">
|
||||
Spend <b class="text-green-600 dark:text-green-400">less time</b> on
|
||||
configuring your backend, and more time building
|
||||
<b class="text-green-600 dark:text-green-400">new features</b> for your web
|
||||
app.
|
||||
Spend <b class="text-green-600 dark:text-green-400">less time</b> on configuring your backend, and more time
|
||||
building
|
||||
<b class="text-green-600 dark:text-green-400">new features</b> for your web app.
|
||||
</p>
|
||||
|
||||
<AuthStateGuard>
|
||||
<div slot="loading">
|
||||
<PrimaryButton
|
||||
text="Get Started"
|
||||
url="/get-started"
|
||||
icon={faArrowRight}
|
||||
/>
|
||||
<PrimaryButton text="Get Started" url="/get-started" icon={faArrowRight} />
|
||||
</div>
|
||||
<UserLoggedIn>
|
||||
<PrimaryButton text="Dashboard" url="/dashboard" icon={faArrowRight} />
|
||||
</UserLoggedIn>
|
||||
<UserLoggedOut>
|
||||
<PrimaryButton
|
||||
text="Get Started"
|
||||
url="/get-started"
|
||||
icon={faArrowRight}
|
||||
/>
|
||||
<PrimaryButton text="Get Started" url="/get-started" icon={faArrowRight} />
|
||||
</UserLoggedOut>
|
||||
</AuthStateGuard>
|
||||
</div>
|
||||
@ -84,9 +75,7 @@
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<div
|
||||
class="bg-zinc-900 p-[75px] border-zinc-700 border-t-2 rounded-[75px] flex flex-wrap justify-center"
|
||||
>
|
||||
<div class="bg-zinc-900 p-[75px] border-zinc-700 border-t-2 rounded-[75px] flex flex-wrap justify-center">
|
||||
<SubFeatureBlock
|
||||
icon={faServer}
|
||||
title="Database"
|
||||
|
||||
@ -39,10 +39,7 @@
|
||||
<ul class="menu">
|
||||
<li class="menu-title">Programming Guide</li>
|
||||
<DocLink path="programming" title="Frontends and JS Hooks" />
|
||||
<DocLink
|
||||
path="server-side-pocketbase-antipattern"
|
||||
title="Server-Side PocketBase is an Anti-Pattern"
|
||||
/>
|
||||
<DocLink path="server-side-pocketbase-antipattern" title="Server-Side PocketBase is an Anti-Pattern" />
|
||||
</ul>
|
||||
<ul class="menu">
|
||||
<li class="menu-title">Appendix</li>
|
||||
|
||||
@ -81,11 +81,7 @@
|
||||
<AlertBar message={formError} type="error" />
|
||||
|
||||
<div class="card-actions justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary"
|
||||
disabled={isFormButtonDisabled}
|
||||
>
|
||||
<button type="submit" class="btn btn-primary" disabled={isFormButtonDisabled}>
|
||||
{#if isButtonLoading}
|
||||
<span class="loading loading-spinner"></span>
|
||||
{:else}
|
||||
@ -97,16 +93,12 @@
|
||||
|
||||
<div class="p-4 bg-zinc-800 text-center">
|
||||
<div class="mb-4">
|
||||
Need to Register? <button
|
||||
type="button"
|
||||
class="link font-bold"
|
||||
on:click={handleRegisterClick}>Create A New Account</button
|
||||
Need to Register? <button type="button" class="link font-bold" on:click={handleRegisterClick}
|
||||
>Create A New Account</button
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Forgot Your Password? <a href="login/password-reset" class="link font-bold"
|
||||
>Reset Password</a
|
||||
>
|
||||
Forgot Your Password? <a href="login/password-reset" class="link font-bold">Reset Password</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
// Fun quotes when waiting for the instance to load. This could take up to 10 seconds
|
||||
let processingQuote =
|
||||
'Did you know it takes fourteen sentient robots to create each instance on PocketHost?'
|
||||
let processingQuote = 'Did you know it takes fourteen sentient robots to create each instance on PocketHost?'
|
||||
</script>
|
||||
|
||||
<div class="card-body">
|
||||
@ -9,9 +8,7 @@
|
||||
<span class="loading loading-spinner loading-lg mb-4"></span>
|
||||
|
||||
<div>
|
||||
<h2 class="mb-12 font-bold text-white text-2xl">
|
||||
Creating Your New Instance...
|
||||
</h2>
|
||||
<h2 class="mb-12 font-bold text-white text-2xl">Creating Your New Instance...</h2>
|
||||
|
||||
<p class="italic">{processingQuote}</p>
|
||||
</div>
|
||||
|
||||
@ -39,10 +39,7 @@
|
||||
...info,
|
||||
fetching: true,
|
||||
}))
|
||||
const res = await client().client.send(
|
||||
`/api/signup?name=${encodeURIComponent(name)}`,
|
||||
{},
|
||||
)
|
||||
const res = await client().client.send(`/api/signup?name=${encodeURIComponent(name)}`, {})
|
||||
instanceInfo.update((info) => ({
|
||||
...info,
|
||||
fetching: false,
|
||||
@ -72,10 +69,7 @@
|
||||
// Disable the form button until all fields are filled out
|
||||
let isFormButtonDisabled: boolean = true
|
||||
$: isFormButtonDisabled =
|
||||
email.length === 0 ||
|
||||
password.length === 0 ||
|
||||
$instanceInfo.name.length === 0 ||
|
||||
!$instanceInfo.available
|
||||
email.length === 0 || password.length === 0 || $instanceInfo.name.length === 0 || !$instanceInfo.available
|
||||
|
||||
// Generate a unique name for the PocketHost instance
|
||||
const handleInstanceNameRegeneration = () => {
|
||||
@ -93,14 +87,9 @@
|
||||
isFormButtonDisabled = true
|
||||
isProcessing = true
|
||||
|
||||
await handleInstanceGeneratorWidget(
|
||||
email,
|
||||
password,
|
||||
$instanceInfo.name,
|
||||
(error) => {
|
||||
formError = error
|
||||
},
|
||||
)
|
||||
await handleInstanceGeneratorWidget(email, password, $instanceInfo.name, (error) => {
|
||||
formError = error
|
||||
})
|
||||
|
||||
isFormButtonDisabled = false
|
||||
|
||||
@ -135,10 +124,7 @@
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-square"
|
||||
on:click={handleInstanceNameRegeneration}
|
||||
<button type="button" class="btn btn-square" on:click={handleInstanceNameRegeneration}
|
||||
><Fa icon={faRotate} />
|
||||
</button>
|
||||
</div>
|
||||
@ -149,13 +135,9 @@
|
||||
{:else if $instanceInfo.fetching}
|
||||
Verifying...
|
||||
{:else if $instanceInfo.available}
|
||||
<span class="text-success">
|
||||
https://{$instanceInfo.name}.pockethost.io ✔︎</span
|
||||
>
|
||||
<span class="text-success"> https://{$instanceInfo.name}.pockethost.io ✔︎</span>
|
||||
{:else}
|
||||
<span class="text-error">
|
||||
https://{$instanceInfo.name}.pockethost.io ❌</span
|
||||
>
|
||||
<span class="text-error"> https://{$instanceInfo.name}.pockethost.io ❌</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@ -194,22 +176,14 @@
|
||||
<AlertBar message={formError} type="error" />
|
||||
|
||||
<div class="card-actions justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary"
|
||||
disabled={isFormButtonDisabled}
|
||||
>
|
||||
<button type="submit" class="btn btn-primary" disabled={isFormButtonDisabled}>
|
||||
Create <Fa icon={faArrowRight} />
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="p-4 bg-zinc-800 text-center">
|
||||
Already have an account? <button
|
||||
type="button"
|
||||
class="link font-bold"
|
||||
on:click={handleLoginClick}>Login</button
|
||||
>
|
||||
Already have an account? <button type="button" class="link font-bold" on:click={handleLoginClick}>Login</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@ -67,11 +67,7 @@
|
||||
<AlertBar message={formError} type="error" />
|
||||
|
||||
<div class="mt-4 card-actions justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary w-100"
|
||||
disabled={isFormButtonDisabled}
|
||||
>
|
||||
<button type="submit" class="btn btn-primary w-100" disabled={isFormButtonDisabled}>
|
||||
Reset Password <i class="bi bi-arrow-right-short" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -22,9 +22,7 @@
|
||||
|
||||
// Check for the token and block the request if it doesn't exist
|
||||
if (!token) {
|
||||
formErrors = [
|
||||
'No token was found. Please check your email again for the link.',
|
||||
]
|
||||
formErrors = ['No token was found. Please check your email again for the link.']
|
||||
return
|
||||
}
|
||||
|
||||
@ -39,9 +37,7 @@
|
||||
if (error instanceof Error) {
|
||||
formErrors = client().parseError(error)
|
||||
} else {
|
||||
formErrors = [
|
||||
'Something went wrong with confirming your password change.',
|
||||
]
|
||||
formErrors = ['Something went wrong with confirming your password change.']
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,11 +72,7 @@
|
||||
{/each}
|
||||
|
||||
<div class="mt-4 card-actions justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary w-100"
|
||||
disabled={isFormButtonDisabled}
|
||||
>
|
||||
<button type="submit" class="btn btn-primary w-100" disabled={isFormButtonDisabled}>
|
||||
Save <i class="bi bi-arrow-right-short" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -8,19 +8,14 @@
|
||||
|
||||
<UserLoggedIn>
|
||||
<a
|
||||
class="btn btn-warning btn-lg {fixed
|
||||
? ' btn-block rounded-none fixed bottom-0'
|
||||
: ''}"
|
||||
class="btn btn-warning btn-lg {fixed ? ' btn-block rounded-none fixed bottom-0' : ''}"
|
||||
style="z-index: 1000"
|
||||
href={`https://store.pockethost.io/buy/d4b2d062-429c-49b4-9cdc-853aaeb17e20?checkout[custom][user_id]=${$userStore?.id}&checkout[email]=${$userStore?.email}`}
|
||||
>Unlock Access Now</a
|
||||
>
|
||||
</UserLoggedIn>
|
||||
<UserLoggedOut>
|
||||
<a
|
||||
class="btn btn-warning btn-lg {fixed
|
||||
? ' btn-block rounded-none fixed bottom-0'
|
||||
: ''}"
|
||||
href="/get-started">Get Started</a
|
||||
<a class="btn btn-warning btn-lg {fixed ? ' btn-block rounded-none fixed bottom-0' : ''}" href="/get-started"
|
||||
>Get Started</a
|
||||
>
|
||||
</UserLoggedOut>
|
||||
|
||||
@ -5,24 +5,17 @@
|
||||
export let feature: any
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex flex-col items-center prose bg-neutral text-neutral-content rounded-lg relative w-full"
|
||||
>
|
||||
<div class="flex flex-col items-center prose bg-neutral text-neutral-content rounded-lg relative w-full">
|
||||
<div class="text-2xl text-white pt-2 mb-5">
|
||||
{feature.title}
|
||||
</div>
|
||||
{#if feature.img}
|
||||
<div class="flex justify-center mr-5 ml-5 mb-5 not-prose">
|
||||
<enhanced:img
|
||||
src={feature.img}
|
||||
class="rounded border border-1 border-gray-500 w-fit"
|
||||
/>
|
||||
<enhanced:img src={feature.img} class="rounded border border-1 border-gray-500 w-fit" />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="text-xl p-5 pt-0">{@html feature.description}</div>
|
||||
<div
|
||||
class="absolute top-0 left-0 text-primary-content text-2xl bg-primary p-2 rounded-br-lg rounded-tl-lg"
|
||||
>
|
||||
<div class="absolute top-0 left-0 text-primary-content text-2xl bg-primary p-2 rounded-br-lg rounded-tl-lg">
|
||||
<Fa icon={faCheck} class="" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -19,19 +19,14 @@
|
||||
<span class="badge badge-primary ml-2">new</span>
|
||||
{/if}
|
||||
{#if item.info}
|
||||
<button
|
||||
class="badge badge-secondary badge-sm ml-2"
|
||||
on:click={handleInfoClick}
|
||||
>
|
||||
<button class="badge badge-secondary badge-sm ml-2" on:click={handleInfoClick}>
|
||||
<Fa icon={faInfo} />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if $showInfo}
|
||||
<div
|
||||
class={`text-neutral-content font-normal ${enlarge ? 'text-md' : 'text-sm'}`}
|
||||
>
|
||||
<div class={`text-neutral-content font-normal ${enlarge ? 'text-md' : 'text-sm'}`}>
|
||||
{item.info}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@ -57,9 +57,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative flex flex-col items-center mb-10 p-2 bg-neutral rounded-2xl w-fit mx-auto max-w-96"
|
||||
>
|
||||
<div class="relative flex flex-col items-center mb-10 p-2 bg-neutral rounded-2xl w-fit mx-auto max-w-96">
|
||||
<a
|
||||
href="https://www.youtube.com/watch?v=Xe0FrGzlcVM"
|
||||
target="_blank"
|
||||
@ -67,11 +65,7 @@
|
||||
>
|
||||
<Fa icon={faPlay} class="text-white text-4xl" />
|
||||
</a>
|
||||
<a
|
||||
href="https://www.youtube.com/watch?v=Xe0FrGzlcVM"
|
||||
target="_blank"
|
||||
aria-label="PocketHost Demo"
|
||||
>
|
||||
<a href="https://www.youtube.com/watch?v=Xe0FrGzlcVM" target="_blank" aria-label="PocketHost Demo">
|
||||
<enhanced:img src={thumb} alt="PocketHost Demo" class="rounded" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
export let selected: boolean = false
|
||||
export let buttonText: string = 'Subscribe Now'
|
||||
export let price: string
|
||||
export let priceDetail: string=''
|
||||
export let priceDetail: string = ''
|
||||
export let bestDeal: boolean = false
|
||||
export let title: string
|
||||
export let cta: string
|
||||
@ -38,7 +38,7 @@
|
||||
{/each}
|
||||
</div>
|
||||
<div class="h-12 flex justify-start items-center flex-col">
|
||||
<div class="text-3xl text-white text-center ">{price}</div>
|
||||
<div class="text-3xl text-white text-center">{price}</div>
|
||||
<div class="text-xs text-gray-400 text-center">{priceDetail}</div>
|
||||
</div>
|
||||
<div class="h-16 flex justify-center items-end">
|
||||
@ -46,9 +46,7 @@
|
||||
href={$isUserLoggedIn
|
||||
? `https://store.pockethost.io/buy/d4b2d062-429c-49b4-9cdc-853aaeb17e20?checkout[custom][user_id]=${$userStore?.id}&checkout[email]=${$userStore?.email}`
|
||||
: `javascript:alert('You must be logged in to subscribe')`}
|
||||
class="btn {selected
|
||||
? 'btn-warning'
|
||||
: 'btn-neutral bg-neutral-700 text-white'} rounded-full"
|
||||
class="btn {selected ? 'btn-warning' : 'btn-neutral bg-neutral-700 text-white'} rounded-full"
|
||||
>
|
||||
{buttonText}
|
||||
</a>
|
||||
|
||||
@ -33,8 +33,7 @@ export const features = [
|
||||
},
|
||||
{
|
||||
title: 'Secure',
|
||||
description:
|
||||
'Infrastructure secured with RSA-2048 encryption and industry-standard security protocols.',
|
||||
description: 'Infrastructure secured with RSA-2048 encryption and industry-standard security protocols.',
|
||||
},
|
||||
|
||||
{
|
||||
@ -47,13 +46,11 @@ export const features = [
|
||||
},
|
||||
{
|
||||
title: 'Custom Domains',
|
||||
description:
|
||||
'Seamless custom domain integration for your PocketHost instances.',
|
||||
description: 'Seamless custom domain integration for your PocketHost instances.',
|
||||
},
|
||||
{
|
||||
title: 'Lifetime Option',
|
||||
description:
|
||||
'Limited-time opportunity for lifetime Pro tier access (up to 250 instances) with a single payment.',
|
||||
description: 'Limited-time opportunity for lifetime Pro tier access (up to 250 instances) with a single payment.',
|
||||
},
|
||||
{
|
||||
title: 'Early Access',
|
||||
@ -61,13 +58,11 @@ export const features = [
|
||||
},
|
||||
{
|
||||
title: 'Reliable',
|
||||
description:
|
||||
'Industry-leading 99.95% uptime guarantee ensures consistent application availability.',
|
||||
description: 'Industry-leading 99.95% uptime guarantee ensures consistent application availability.',
|
||||
},
|
||||
{
|
||||
title: 'Managed',
|
||||
description:
|
||||
'Comprehensive infrastructure management including scaling, backups, and maintenance operations.',
|
||||
description: 'Comprehensive infrastructure management including scaling, backups, and maintenance operations.',
|
||||
},
|
||||
{
|
||||
title: 'Affordable',
|
||||
|
||||
@ -7,17 +7,14 @@
|
||||
|
||||
<h2 class="font-bold text-2xl mb-4 mt-10">Community Support</h2>
|
||||
<p>
|
||||
Community support is handled through <a
|
||||
class="link"
|
||||
href="https://github.com/pockethost/pockethost/discussions"
|
||||
Community support is handled through <a class="link" href="https://github.com/pockethost/pockethost/discussions"
|
||||
>Github Discussions</a
|
||||
>.
|
||||
</p>
|
||||
|
||||
<h2 class="font-bold text-2xl mb-4 mt-10">Discord</h2>
|
||||
<p>
|
||||
We also have a <a class="link" href={DISCORD_URL}>Discord server</a> with over
|
||||
1,500 community members ready to help.
|
||||
We also have a <a class="link" href={DISCORD_URL}>Discord server</a> with over 1,500 community members ready to help.
|
||||
</p>
|
||||
|
||||
<h2 class="font-bold text-2xl mb-4 mt-10">Email Support</h2>
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
import PromoBanner from './PromoBanner.svelte'
|
||||
import MothershipStatus from './MothershipStatus.svelte'
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
const currentYear = new Date().getFullYear()
|
||||
|
||||
onMount(() => {
|
||||
init()
|
||||
@ -30,9 +30,7 @@
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
<div
|
||||
class="p-10 text-xs text-neutral-content flex flex-col text-center items-center"
|
||||
>
|
||||
<div class="p-10 text-xs text-neutral-content flex flex-col text-center items-center">
|
||||
<div class="flex flex-row space-x-5">
|
||||
<a href="/privacy">Privacy</a>
|
||||
<a href="/terms">Terms</a>
|
||||
@ -48,10 +46,7 @@
|
||||
/>
|
||||
<div>
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script
|
||||
async
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-5Q6CM5HPCX"
|
||||
></script>
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-5Q6CM5HPCX"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || []
|
||||
function gtag() {
|
||||
|
||||
@ -7,8 +7,7 @@
|
||||
<div class="text-center">
|
||||
<h2 class="text-xl font-bold mb-2">Connection Lost</h2>
|
||||
<p>
|
||||
The PocketHost mothership is temporarily unreachable. Your instances are
|
||||
safe and still reachable. Go to the <a
|
||||
The PocketHost mothership is temporarily unreachable. Your instances are safe and still reachable. Go to the <a
|
||||
href="https://status.pockethost.io"
|
||||
class="link font-semibold underline">status page</a
|
||||
> for more information.
|
||||
|
||||
@ -17,9 +17,7 @@
|
||||
|
||||
// Convert the hash to a hex stringc
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
||||
const hashHex = hashArray
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
|
||||
|
||||
return hashHex
|
||||
}
|
||||
|
||||
@ -3,12 +3,6 @@
|
||||
</script>
|
||||
|
||||
<div class="flex items-center justify-center gap-4">
|
||||
<enhanced:img
|
||||
src="./pockethost-cloud-logo.jpg"
|
||||
class="mix-blend-lighten h-12 w-12"
|
||||
alt="PocketHost Logo"
|
||||
/>
|
||||
<h1 class="text-white font-bold text-xl hidden md:block ml-1 {hideLogoText && 'sr-only'}">
|
||||
PocketHost
|
||||
</h1>
|
||||
<enhanced:img src="./pockethost-cloud-logo.jpg" class="mix-blend-lighten h-12 w-12" alt="PocketHost Logo" />
|
||||
<h1 class="text-white font-bold text-xl hidden md:block ml-1 {hideLogoText && 'sr-only'}">PocketHost</h1>
|
||||
</div>
|
||||
|
||||
@ -2,31 +2,15 @@
|
||||
import Logo from '$src/routes/Navbar/Logo.svelte'
|
||||
import Fa from 'svelte-fa'
|
||||
import NavbarMenu from './NavbarMenu.svelte'
|
||||
import {
|
||||
faDiscord,
|
||||
faGithub,
|
||||
faProductHunt,
|
||||
faYoutube,
|
||||
} from '@fortawesome/free-brands-svg-icons'
|
||||
import { faDiscord, faGithub, faProductHunt, faYoutube } from '@fortawesome/free-brands-svg-icons'
|
||||
</script>
|
||||
|
||||
<div class="navbar bg-base-100 lg:pl-6">
|
||||
<div class="flex-1 min-w-10">
|
||||
<div class="dropdown mr-4">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost lg:hidden">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 6h16M4 12h8m-8 6h16"
|
||||
/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16" />
|
||||
</svg>
|
||||
</div>
|
||||
<NavbarMenu isCollapsed />
|
||||
@ -36,36 +20,16 @@
|
||||
<!-- Redundant with the Logo component -->
|
||||
<!-- <div class="hidden md:block ml-1 text-sm">PocketHost</div> -->
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/pockethost/pockethost"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
class="m-2"
|
||||
>
|
||||
<a href="https://github.com/pockethost/pockethost" rel="noreferrer" target="_blank" class="m-2">
|
||||
<Fa icon={faGithub} />
|
||||
</a>
|
||||
<a
|
||||
href="https://www.producthunt.com/products/pockethost"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
class="m-2"
|
||||
>
|
||||
<a href="https://www.producthunt.com/products/pockethost" rel="noreferrer" target="_blank" class="m-2">
|
||||
<Fa icon={faProductHunt} />
|
||||
</a>
|
||||
<a
|
||||
href="https://discord.gg/nVTxCMEcGT"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
class="m-2"
|
||||
>
|
||||
<a href="https://discord.gg/nVTxCMEcGT" rel="noreferrer" target="_blank" class="m-2">
|
||||
<Fa icon={faDiscord} />
|
||||
</a>
|
||||
<a
|
||||
href="https://www.youtube.com/@pocketba5ed"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
class="m-2"
|
||||
>
|
||||
<a href="https://www.youtube.com/@pocketba5ed" rel="noreferrer" target="_blank" class="m-2">
|
||||
<Fa icon={faYoutube} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -3,11 +3,7 @@
|
||||
import UserHasRole from '$components/guards/UserHasRole.svelte'
|
||||
import UserLoggedIn from '$components/guards/UserLoggedIn.svelte'
|
||||
import UserLoggedOut from '$components/guards/UserLoggedOut.svelte'
|
||||
import {
|
||||
faDiscord,
|
||||
faGithub,
|
||||
faProductHunt,
|
||||
} from '@fortawesome/free-brands-svg-icons'
|
||||
import { faDiscord, faGithub, faProductHunt } from '@fortawesome/free-brands-svg-icons'
|
||||
import Avatar from './Avatar.svelte'
|
||||
import Fa from 'svelte-fa'
|
||||
|
||||
@ -66,10 +62,7 @@
|
||||
<Avatar />
|
||||
</div>
|
||||
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="dropdown-content menu bg-base-300 rounded-box z-[1] mt-36 md:mt-24 w-52 p-2 shadow"
|
||||
>
|
||||
<ul tabindex="0" class="dropdown-content menu bg-base-300 rounded-box z-[1] mt-36 md:mt-24 w-52 p-2 shadow">
|
||||
<li><a href="/account">Settings</a></li>
|
||||
<li>
|
||||
<button on:click={handleLogoutAndRedirect}>Logout</button>
|
||||
|
||||
@ -16,8 +16,7 @@
|
||||
<div class="alert alert-info bg-yellow-300 rounded-none mb-10 relative">
|
||||
<div class="text-info-content flex-1">
|
||||
{latestPost.title}
|
||||
<a href={latestPost.path} class="btn btn-sm btn-neutral m-2">Learn more</a
|
||||
>
|
||||
<a href={latestPost.path} class="btn btn-sm btn-neutral m-2">Learn more</a>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-ghost btn-circle btn-xs absolute top-0 right-0"
|
||||
|
||||
@ -14,10 +14,7 @@ export const handleFormError = (e: Error, setError?: FormErrorHandler) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const handleCreateNewInstance = async (
|
||||
instanceName: string,
|
||||
setError?: FormErrorHandler,
|
||||
) => {
|
||||
export const handleCreateNewInstance = async (instanceName: string, setError?: FormErrorHandler) => {
|
||||
const { user, createInstance } = client()
|
||||
// Get the newly created user id
|
||||
const { id } = user() || {}
|
||||
@ -42,7 +39,7 @@ export const handleInstanceGeneratorWidget = async (
|
||||
email: string,
|
||||
password: string,
|
||||
instanceName: string,
|
||||
setError = (value: string) => {},
|
||||
setError = (value: string) => {}
|
||||
) => {
|
||||
const { authViaEmail } = client()
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ try {
|
||||
`logger`,
|
||||
ConsoleLogger({
|
||||
level: PUBLIC_DEBUG ? LogLevelName.Debug : LogLevelName.Info,
|
||||
}),
|
||||
})
|
||||
)
|
||||
} catch (e) {
|
||||
console.warn(e)
|
||||
@ -68,10 +68,7 @@ const continuouslyCheckMothershipReachability = () => {
|
||||
}
|
||||
|
||||
async function fetchVersions(): Promise<string[]> {
|
||||
const { versions } = await client().client.send<{ versions: string[] }>(
|
||||
`/api/versions`,
|
||||
{},
|
||||
)
|
||||
const { versions } = await client().client.send<{ versions: string[] }>(`/api/versions`, {})
|
||||
|
||||
return versions
|
||||
}
|
||||
@ -104,11 +101,9 @@ export const init = () => {
|
||||
|
||||
userStore.subscribe((user) => {
|
||||
console.log(`userStore.subscribe`, { user })
|
||||
const isPaid = [
|
||||
SubscriptionType.Founder,
|
||||
SubscriptionType.Premium,
|
||||
SubscriptionType.Flounder,
|
||||
].includes(user?.subscription || SubscriptionType.Free)
|
||||
const isPaid = [SubscriptionType.Founder, SubscriptionType.Premium, SubscriptionType.Flounder].includes(
|
||||
user?.subscription || SubscriptionType.Free
|
||||
)
|
||||
isUserPaid.set(isPaid)
|
||||
isUserLegacy.set(!!user?.isLegacy)
|
||||
userSubscriptionType.set(user?.subscription || SubscriptionType.Free)
|
||||
|
||||
@ -28,10 +28,7 @@ export async function daemon() {
|
||||
info(`Found ${containers.length} containers`)
|
||||
await Promise.all(
|
||||
containers.map(async (container) => {
|
||||
if (
|
||||
container.State === 'running' &&
|
||||
container.Image === DOCKER_INSTANCE_IMAGE_NAME
|
||||
) {
|
||||
if (container.State === 'running' && container.Image === DOCKER_INSTANCE_IMAGE_NAME) {
|
||||
try {
|
||||
info(`Stopping ${container.Id}`)
|
||||
await docker.getContainer(container.Id).stop()
|
||||
@ -40,7 +37,7 @@ export async function daemon() {
|
||||
warn(`Failed to stop ${container.Id}, but that's probably OK`, e)
|
||||
}
|
||||
}
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
await PocketbaseService({})
|
||||
|
||||
@ -7,11 +7,9 @@ type Options = {
|
||||
}
|
||||
|
||||
export const ServeCommand = () => {
|
||||
const cmd = new Command(`serve`)
|
||||
.description(`Run an edge daemon server`)
|
||||
.action(async (options: Options) => {
|
||||
logger().context({ cli: 'edge:daemon:serve' })
|
||||
await daemon()
|
||||
})
|
||||
const cmd = new Command(`serve`).description(`Run an edge daemon server`).action(async (options: Options) => {
|
||||
logger().context({ cli: 'edge:daemon:serve' })
|
||||
await daemon()
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -1,23 +1,8 @@
|
||||
import {
|
||||
InstanceFields,
|
||||
InstanceLogWriter,
|
||||
InstanceLogWriterApi,
|
||||
Logger,
|
||||
PocketBase,
|
||||
assert,
|
||||
seqid,
|
||||
} from '@'
|
||||
import { InstanceFields, InstanceLogWriter, InstanceLogWriterApi, Logger, PocketBase, assert, seqid } from '@'
|
||||
import { compact, forEach, map } from '@s-libs/micro-dash'
|
||||
import Bottleneck from 'bottleneck'
|
||||
import { spawn } from 'child_process'
|
||||
import {
|
||||
Mode,
|
||||
constants,
|
||||
createReadStream,
|
||||
createWriteStream,
|
||||
readFileSync,
|
||||
writeFileSync,
|
||||
} from 'fs'
|
||||
import { Mode, constants, createReadStream, createWriteStream, readFileSync, writeFileSync } from 'fs'
|
||||
import { FileStat, FileSystem, FtpConnection } from 'ftp-srv'
|
||||
import { dirname, isAbsolute, join, normalize, resolve, sep } from 'path'
|
||||
import { DATA_ROOT } from '../../../../..'
|
||||
@ -41,19 +26,12 @@ export type PathError = {
|
||||
const UNIX_SEP_REGEX = /\//g
|
||||
const WIN_SEP_REGEX = /\\/g
|
||||
|
||||
const checkBun = (
|
||||
instance: InstanceFields,
|
||||
virtualPath: string,
|
||||
cwd: string,
|
||||
) => {
|
||||
const [subdomain, maybeImportant, ...rest] = virtualPath
|
||||
.split('/')
|
||||
.filter((p) => !!p)
|
||||
const checkBun = (instance: InstanceFields, virtualPath: string, cwd: string) => {
|
||||
const [subdomain, maybeImportant, ...rest] = virtualPath.split('/').filter((p) => !!p)
|
||||
|
||||
const isImportant =
|
||||
maybeImportant === 'patches' ||
|
||||
(rest.length === 0 &&
|
||||
[`bun.lock`, `bun.lockb`, `package.json`].includes(maybeImportant || ''))
|
||||
(rest.length === 0 && [`bun.lock`, `bun.lockb`, `package.json`].includes(maybeImportant || ''))
|
||||
|
||||
if (isImportant) {
|
||||
const logger = InstanceLogWriter(instance.id, instance.volume, `exec`)
|
||||
@ -96,14 +74,8 @@ const runBun = (() => {
|
||||
new Promise<number | null>((resolve) => {
|
||||
const proc = spawn(
|
||||
'/root/.bun/bin/bun',
|
||||
[
|
||||
'install',
|
||||
`--no-save`,
|
||||
`--production`,
|
||||
`--ignore-scripts`,
|
||||
`--frozen-lockfile`,
|
||||
],
|
||||
{ cwd },
|
||||
['install', `--no-save`, `--production`, `--ignore-scripts`, `--frozen-lockfile`],
|
||||
{ cwd }
|
||||
)
|
||||
const tid = setTimeout(() => {
|
||||
logger.error(`bun timeout after 10s`)
|
||||
@ -120,7 +92,7 @@ const runBun = (() => {
|
||||
clearTimeout(tid)
|
||||
resolve(code)
|
||||
})
|
||||
}),
|
||||
})
|
||||
)
|
||||
})()
|
||||
|
||||
@ -197,11 +169,7 @@ export class PhFs implements FileSystem {
|
||||
throw new Error(`${subdomain} not found.`)
|
||||
}
|
||||
const instanceRootDir = restOfVirtualPath[0]
|
||||
if (
|
||||
instanceRootDir &&
|
||||
POWERED_OFF_ONLY.includes(instanceRootDir) &&
|
||||
instance.power
|
||||
) {
|
||||
if (instanceRootDir && POWERED_OFF_ONLY.includes(instanceRootDir) && instance.power) {
|
||||
throw new Error(`Instance must be powered off first`)
|
||||
}
|
||||
if (instance.volume) {
|
||||
@ -219,7 +187,7 @@ export class PhFs implements FileSystem {
|
||||
const fsPath = resolve(
|
||||
join(...fsPathParts, ...restOfVirtualPath)
|
||||
.replace(UNIX_SEP_REGEX, sep)
|
||||
.replace(WIN_SEP_REGEX, sep),
|
||||
.replace(WIN_SEP_REGEX, sep)
|
||||
)
|
||||
|
||||
// Create FTP client path using unix separator
|
||||
@ -261,9 +229,7 @@ export class PhFs implements FileSystem {
|
||||
}
|
||||
|
||||
async list(path = '.') {
|
||||
const { dbg, error } = this.log
|
||||
.create(`list`)
|
||||
.breadcrumb({ cwd: this.cwd, path })
|
||||
const { dbg, error } = this.log.create(`list`).breadcrumb({ cwd: this.cwd, path })
|
||||
|
||||
const { fsPath, instance } = await this._resolvePath(path)
|
||||
|
||||
@ -303,7 +269,7 @@ export class PhFs implements FileSystem {
|
||||
})
|
||||
})
|
||||
.catch(() => null)
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
.then(compact)
|
||||
@ -312,9 +278,7 @@ export class PhFs implements FileSystem {
|
||||
async get(fileName: string): Promise<FileStat> {
|
||||
const { fsPath, instance, clientPath } = await this._resolvePath(fileName)
|
||||
|
||||
const { dbg, error } = this.log
|
||||
.create(`get`)
|
||||
.breadcrumb({ cwd: this.cwd, fileName, fsPath })
|
||||
const { dbg, error } = this.log.create(`get`).breadcrumb({ cwd: this.cwd, fileName, fsPath })
|
||||
dbg(`get`)
|
||||
|
||||
/*
|
||||
@ -354,13 +318,8 @@ export class PhFs implements FileSystem {
|
||||
})
|
||||
}
|
||||
|
||||
async write(
|
||||
fileName: string,
|
||||
options?: { append?: boolean | undefined; start?: any } | undefined,
|
||||
) {
|
||||
const { dbg, error } = this.log
|
||||
.create(`write`)
|
||||
.breadcrumb({ cwd: this.cwd, fileName })
|
||||
async write(fileName: string, options?: { append?: boolean | undefined; start?: any } | undefined) {
|
||||
const { dbg, error } = this.log.create(`write`).breadcrumb({ cwd: this.cwd, fileName })
|
||||
dbg(`write`)
|
||||
|
||||
const { fsPath, clientPath, instance } = await this._resolvePath(fileName)
|
||||
@ -391,13 +350,8 @@ export class PhFs implements FileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
async read(
|
||||
fileName: string,
|
||||
options: { start?: any } | undefined,
|
||||
): Promise<any> {
|
||||
const { dbg, error } = this.log
|
||||
.create(`read`)
|
||||
.breadcrumb({ cwd: this.cwd, fileName })
|
||||
async read(fileName: string, options: { start?: any } | undefined): Promise<any> {
|
||||
const { dbg, error } = this.log.create(`read`).breadcrumb({ cwd: this.cwd, fileName })
|
||||
dbg(`read`)
|
||||
|
||||
const { fsPath, clientPath } = await this._resolvePath(fileName)
|
||||
@ -419,9 +373,7 @@ export class PhFs implements FileSystem {
|
||||
}
|
||||
|
||||
async delete(path: string) {
|
||||
const { dbg, error } = this.log
|
||||
.create(`delete`)
|
||||
.breadcrumb({ cwd: this.cwd, path })
|
||||
const { dbg, error } = this.log.create(`delete`).breadcrumb({ cwd: this.cwd, path })
|
||||
dbg(`delete`)
|
||||
|
||||
const { fsPath, instance } = await this._resolvePath(path)
|
||||
@ -436,9 +388,7 @@ export class PhFs implements FileSystem {
|
||||
}
|
||||
|
||||
async mkdir(path: string) {
|
||||
const { dbg, error } = this.log
|
||||
.create(`mkdir`)
|
||||
.breadcrumb({ cwd: this.cwd, path })
|
||||
const { dbg, error } = this.log.create(`mkdir`).breadcrumb({ cwd: this.cwd, path })
|
||||
dbg(`mkdir`)
|
||||
|
||||
const { fsPath } = await this._resolvePath(path)
|
||||
@ -447,9 +397,7 @@ export class PhFs implements FileSystem {
|
||||
}
|
||||
|
||||
async rename(from: string, to: string) {
|
||||
const { dbg, error } = this.log
|
||||
.create(`rename`)
|
||||
.breadcrumb({ cwd: this.cwd, from, to })
|
||||
const { dbg, error } = this.log.create(`rename`).breadcrumb({ cwd: this.cwd, from, to })
|
||||
dbg(`rename`)
|
||||
|
||||
const { fsPath: fromPath, instance } = await this._resolvePath(from)
|
||||
@ -462,9 +410,7 @@ export class PhFs implements FileSystem {
|
||||
}
|
||||
|
||||
async chmod(path: string, mode: Mode) {
|
||||
const { dbg, error } = this.log
|
||||
.create(`chmod`)
|
||||
.breadcrumb({ cwd: this.cwd, path, mode })
|
||||
const { dbg, error } = this.log.create(`chmod`).breadcrumb({ cwd: this.cwd, path, mode })
|
||||
dbg(`chmod`)
|
||||
|
||||
const { fsPath } = await this._resolvePath(path)
|
||||
|
||||
@ -30,15 +30,11 @@ export const FolderNamesMap: {
|
||||
export const INSTANCE_ROOT_VIRTUAL_FOLDER_NAMES = keys(FolderNamesMap)
|
||||
export const INSTANCE_ROOT_PHYSICAL_FOLDER_NAMES = values(FolderNamesMap)
|
||||
|
||||
export function isInstanceRootVirtualFolder(
|
||||
name: string,
|
||||
): name is VirtualFolderNames {
|
||||
export function isInstanceRootVirtualFolder(name: string): name is VirtualFolderNames {
|
||||
return INSTANCE_ROOT_VIRTUAL_FOLDER_NAMES.includes(name as VirtualFolderNames)
|
||||
}
|
||||
|
||||
export function virtualFolderGuard(
|
||||
name: string,
|
||||
): asserts name is VirtualFolderNames {
|
||||
export function virtualFolderGuard(name: string): asserts name is VirtualFolderNames {
|
||||
if (!isInstanceRootVirtualFolder(name)) {
|
||||
// throw new Error(`Accessing ${name} is not allowed.`)
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ export const ftpService = mkSingleton((config: Partial<FtpConfig> = {}) => {
|
||||
{
|
||||
mothershipUrl: MOTHERSHIP_URL(),
|
||||
},
|
||||
config,
|
||||
config
|
||||
)
|
||||
const tls = {
|
||||
key: readFileSync(SSL_KEY()),
|
||||
@ -42,34 +42,27 @@ export const ftpService = mkSingleton((config: Partial<FtpConfig> = {}) => {
|
||||
pasv_min: PH_FTP_PASV_PORT_MIN(),
|
||||
})
|
||||
|
||||
ftpServer.on(
|
||||
'login',
|
||||
async ({ connection, username, password }, resolve, reject) => {
|
||||
dbg(`Got a connection with credentials ${username}:${password}`)
|
||||
dbg(`Finding ${mothershipUrl}`)
|
||||
const client = new PocketBase(mothershipUrl)
|
||||
try {
|
||||
if (username === `__auth__`) {
|
||||
client.authStore.loadFromCookie(password)
|
||||
if (!client.authStore.isValid) {
|
||||
throw new Error(`Invalid cookie`)
|
||||
}
|
||||
} else {
|
||||
await client.collection('users').authWithPassword(username, password)
|
||||
ftpServer.on('login', async ({ connection, username, password }, resolve, reject) => {
|
||||
dbg(`Got a connection with credentials ${username}:${password}`)
|
||||
dbg(`Finding ${mothershipUrl}`)
|
||||
const client = new PocketBase(mothershipUrl)
|
||||
try {
|
||||
if (username === `__auth__`) {
|
||||
client.authStore.loadFromCookie(password)
|
||||
if (!client.authStore.isValid) {
|
||||
throw new Error(`Invalid cookie`)
|
||||
}
|
||||
dbg(`Logged in`)
|
||||
const fs = new PhFs(
|
||||
connection,
|
||||
client,
|
||||
_ftpServiceLogger.child(username).context({ ftpSession: Date.now() }),
|
||||
)
|
||||
resolve({ fs })
|
||||
} catch (e) {
|
||||
reject(new Error(`Invalid username or password`))
|
||||
return
|
||||
} else {
|
||||
await client.collection('users').authWithPassword(username, password)
|
||||
}
|
||||
},
|
||||
)
|
||||
dbg(`Logged in`)
|
||||
const fs = new PhFs(connection, client, _ftpServiceLogger.child(username).context({ ftpSession: Date.now() }))
|
||||
resolve({ fs })
|
||||
} catch (e) {
|
||||
reject(new Error(`Invalid username or password`))
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
ftpServer.listen().then(() => {
|
||||
dbg('Ftp server started...')
|
||||
|
||||
@ -7,11 +7,9 @@ type Options = {
|
||||
}
|
||||
|
||||
export const ServeCommand = () => {
|
||||
const cmd = new Command(`serve`)
|
||||
.description(`Run an edge FTP server`)
|
||||
.action(async (options: Options) => {
|
||||
logger().context({ cli: 'edge:ftp:serve' })
|
||||
await ftp()
|
||||
})
|
||||
const cmd = new Command(`serve`).description(`Run an edge FTP server`).action(async (options: Options) => {
|
||||
logger().context({ cli: 'edge:ftp:serve' })
|
||||
await ftp()
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -16,21 +16,14 @@ export const MigrateCommand = () => {
|
||||
return new Command(`migrate`)
|
||||
.description(`Migrate instance(s)`)
|
||||
.option(`-i, --instance <instanceId>`, `The instance to migrate`)
|
||||
.option(
|
||||
`-m, --mount-point <mountPoint>`,
|
||||
`The mount point`,
|
||||
`cloud-storage`,
|
||||
)
|
||||
.option(`-m, --mount-point <mountPoint>`, `The mount point`, `cloud-storage`)
|
||||
.action(async (options) => {
|
||||
console.log({ options })
|
||||
const { instance: instanceId, mountPoint } = options
|
||||
|
||||
const pb = new PocketBase(MOTHERSHIP_URL())
|
||||
|
||||
await pb.admins.authWithPassword(
|
||||
MOTHERSHIP_ADMIN_USERNAME(),
|
||||
MOTHERSHIP_ADMIN_PASSWORD(),
|
||||
)
|
||||
await pb.admins.authWithPassword(MOTHERSHIP_ADMIN_USERNAME(), MOTHERSHIP_ADMIN_PASSWORD())
|
||||
|
||||
const filter = [`status='idle'`, `volume=''`]
|
||||
if (instanceId) {
|
||||
@ -49,9 +42,7 @@ export const MigrateCommand = () => {
|
||||
}
|
||||
|
||||
async function migrate(mountPoint: string, pb: PocketBase, filter: string) {
|
||||
const instance = await pb
|
||||
.collection<InstanceFields>('instances')
|
||||
.getFirstListItem(filter)
|
||||
const instance = await pb.collection<InstanceFields>('instances').getFirstListItem(filter)
|
||||
|
||||
const oldSuspension = instance.suspension
|
||||
try {
|
||||
@ -68,7 +59,7 @@ async function migrate(mountPoint: string, pb: PocketBase, filter: string) {
|
||||
moveSync(instanceDataSrc, instanceDataDst)
|
||||
} else {
|
||||
console.warn(
|
||||
`Skipping ${instanceDataSrc} to ${instanceDataDst} because source does not exist or destination already exists`,
|
||||
`Skipping ${instanceDataSrc} to ${instanceDataDst} because source does not exist or destination already exists`
|
||||
)
|
||||
}
|
||||
|
||||
@ -81,9 +72,7 @@ async function migrate(mountPoint: string, pb: PocketBase, filter: string) {
|
||||
} catch (e) {
|
||||
console.error(`${e}`, JSON.stringify(e, null, 2))
|
||||
} finally {
|
||||
console.log(
|
||||
`Restoring previous suspension value of instance ${instance.id}`,
|
||||
)
|
||||
console.log(`Restoring previous suspension value of instance ${instance.id}`)
|
||||
await pb.collection<InstanceFields>('instances').update(instance.id, {
|
||||
suspension: oldSuspension === 'migrating' ? '' : oldSuspension,
|
||||
})
|
||||
|
||||
@ -51,11 +51,7 @@ const cleanup = async (mountPoint: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
const mount = async (
|
||||
remoteName: string,
|
||||
bucketName: string,
|
||||
optionsIn: Partial<MountOptions> = {},
|
||||
) => {
|
||||
const mount = async (remoteName: string, bucketName: string, optionsIn: Partial<MountOptions> = {}) => {
|
||||
const options: MountOptions = {
|
||||
mountPoint: VOLUME_MOUNT_POINT(),
|
||||
cacheDir: VOLUME_CACHE_DIR(),
|
||||
|
||||
@ -5,11 +5,7 @@ import IPCIDR from 'ip-cidr'
|
||||
export const createIpWhitelistMiddleware = (blockedCIDRs: string[]) => {
|
||||
const blockedCIDRObjects = blockedCIDRs.map((cidr) => new IPCIDR(cidr))
|
||||
|
||||
return (
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
next: express.NextFunction,
|
||||
) => {
|
||||
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
const ip = req.ip // or req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
||||
if (
|
||||
blockedCIDRs.length === 0 ||
|
||||
|
||||
@ -3,11 +3,7 @@ import { Handler, Request } from 'express'
|
||||
import { createProxyMiddleware } from 'http-proxy-middleware'
|
||||
import vhost from 'vhost'
|
||||
|
||||
export function createVhostProxyMiddleware(
|
||||
host: string,
|
||||
target: string,
|
||||
ws = false,
|
||||
): Handler {
|
||||
export function createVhostProxyMiddleware(host: string, target: string, ws = false): Handler {
|
||||
const { dbg } = logger()
|
||||
dbg(`Creating ${host}->${target}`)
|
||||
const handler = createProxyMiddleware({ target, ws, changeOrigin: ws })
|
||||
|
||||
@ -7,11 +7,9 @@ type Options = {
|
||||
}
|
||||
|
||||
export const ServeCommand = () => {
|
||||
const cmd = new Command(`serve`)
|
||||
.description(`Serve the root firewall`)
|
||||
.action(async (options: Options) => {
|
||||
logger().context({ cli: 'firewall:serve' })
|
||||
await firewall()
|
||||
})
|
||||
const cmd = new Command(`serve`).description(`Serve the root firewall`).action(async (options: Options) => {
|
||||
logger().context({ cli: 'firewall:serve' })
|
||||
await firewall()
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -3,13 +3,7 @@ import { execSync } from 'child_process'
|
||||
import fetch from 'node-fetch'
|
||||
import { default as osu } from 'node-os-utils'
|
||||
import { freemem } from 'os'
|
||||
import {
|
||||
DAEMON_PORT,
|
||||
DISCORD_HEALTH_CHANNEL_URL,
|
||||
LoggerService,
|
||||
MOTHERSHIP_PORT,
|
||||
stringify,
|
||||
} from '../../..'
|
||||
import { DAEMON_PORT, DISCORD_HEALTH_CHANNEL_URL, LoggerService, MOTHERSHIP_PORT, stringify } from '../../..'
|
||||
|
||||
export const checkHealth = async () => {
|
||||
const { cpu, drive } = osu
|
||||
@ -52,8 +46,7 @@ export const checkHealth = async () => {
|
||||
Image: 'pockethost-instance',
|
||||
Labels: '',
|
||||
LocalVolumes: '0',
|
||||
Mounts:
|
||||
'/home/pocketho…,/home/pocketho…,/home/pocketho…,/home/pocketho…,/home/pocketho…',
|
||||
Mounts: '/home/pocketho…,/home/pocketho…,/home/pocketho…,/home/pocketho…,/home/pocketho…',
|
||||
Names: 'kekbase-1705984569777',
|
||||
Networks: 'bridge',
|
||||
Ports: '0.0.0.0:44447-\u003e8090/tcp, :::44447-\u003e8090/tcp',
|
||||
@ -150,9 +143,9 @@ export const checkHealth = async () => {
|
||||
.catch((e) => {
|
||||
console.error({ e })
|
||||
throw e
|
||||
}),
|
||||
),
|
||||
),
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
const openFiles = _exec(`cat /proc/sys/fs/file-nr`)[0]?.trim() || `Unknown`
|
||||
@ -164,9 +157,7 @@ export const checkHealth = async () => {
|
||||
`CPU Usage: ${await cpu.usage()}%`,
|
||||
`Free RAM: ${getFreeMemoryInGB()}GB`,
|
||||
`Free Storage /: ${(await drive.info(`/`)).freeGb}GB`,
|
||||
`Free Storage /mnt/sfo_data: ${
|
||||
(await drive.info(`/mnt/sfo_data`)).freeGb
|
||||
}GB`,
|
||||
`Free Storage /mnt/sfo_data: ${(await drive.info(`/mnt/sfo_data`)).freeGb}GB`,
|
||||
`Open files: ${openFiles}`,
|
||||
`Containers: ${containers.length}`,
|
||||
]
|
||||
@ -210,7 +201,7 @@ export const checkHealth = async () => {
|
||||
dbg(`${url}: ${e}`)
|
||||
check.isHealthy = false
|
||||
}
|
||||
}),
|
||||
})
|
||||
)
|
||||
dbg({ checks })
|
||||
await send([
|
||||
@ -229,13 +220,9 @@ export const checkHealth = async () => {
|
||||
if (isInstance) {
|
||||
return `${
|
||||
isHealthy ? ':white_check_mark:' : ':face_vomiting: '
|
||||
} \`${name.padStart(30)} ${(mem || '').padStart(10)} ${(
|
||||
ago || ''
|
||||
).padStart(20)}\``
|
||||
} \`${name.padStart(30)} ${(mem || '').padStart(10)} ${(ago || '').padStart(20)}\``
|
||||
} else {
|
||||
return `${
|
||||
isHealthy ? ':white_check_mark:' : ':face_vomiting: '
|
||||
} ${name}`
|
||||
return `${isHealthy ? ':white_check_mark:' : ':face_vomiting: '} ${name}`
|
||||
}
|
||||
}),
|
||||
])
|
||||
|
||||
@ -8,10 +8,8 @@ export const compact = async () => {
|
||||
const files = [
|
||||
...new Set(
|
||||
[`data`, `logs`].flatMap((db) =>
|
||||
globSync(`${DATA_ROOT()}/*/pb_data/${db}.db{-shm,-wal}`).map((f) =>
|
||||
f.replace(/-(?:shm|wal)$/, ''),
|
||||
),
|
||||
),
|
||||
globSync(`${DATA_ROOT()}/*/pb_data/${db}.db{-shm,-wal}`).map((f) => f.replace(/-(?:shm|wal)$/, ''))
|
||||
)
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@ -16,20 +16,18 @@ export const HealthCommand = () => {
|
||||
info(`Starting`)
|
||||
const { checkHealth } = await import(`./checkHealth`)
|
||||
await checkHealth()
|
||||
}),
|
||||
})
|
||||
)
|
||||
.addCommand(
|
||||
new Command(`compact`)
|
||||
.description(
|
||||
`Compact SQLite databases by removing old SHM and WAL files`,
|
||||
)
|
||||
.description(`Compact SQLite databases by removing old SHM and WAL files`)
|
||||
.action(async (options: Options) => {
|
||||
logger().context({ cli: 'health:compact' })
|
||||
const { dbg, error, info, warn } = logger()
|
||||
info(`Starting`)
|
||||
const { compact } = await import(`./compact`)
|
||||
await compact()
|
||||
}),
|
||||
})
|
||||
)
|
||||
.action(() => {
|
||||
cmd.help()
|
||||
|
||||
@ -1,21 +1,13 @@
|
||||
import {
|
||||
GobotService,
|
||||
LoggerService,
|
||||
MOTHERSHIP_HOOKS_DIR,
|
||||
PH_ALLOWED_POCKETBASE_SEMVER,
|
||||
stringify,
|
||||
} from '@'
|
||||
import { GobotService, LoggerService, MOTHERSHIP_HOOKS_DIR, PH_ALLOWED_POCKETBASE_SEMVER, stringify } from '@'
|
||||
import { uniq } from '@s-libs/micro-dash'
|
||||
import { Command } from 'commander'
|
||||
import { writeFileSync } from 'fs'
|
||||
import { compare, parse, prerelease, satisfies } from 'semver'
|
||||
|
||||
export const UpdateVersionsCommand = () => {
|
||||
const cmd = new Command(`update-versions`)
|
||||
.description(`Update pocketbase versions`)
|
||||
.action(async () => {
|
||||
await freshenPocketbaseVersions()
|
||||
})
|
||||
const cmd = new Command(`update-versions`).description(`Update pocketbase versions`).action(async () => {
|
||||
await freshenPocketbaseVersions()
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -30,14 +22,10 @@ export async function freshenPocketbaseVersions() {
|
||||
(await bot.versions())
|
||||
.map((v) => parse(v))
|
||||
.filter((v) => !!v)
|
||||
.filter(
|
||||
(v) =>
|
||||
satisfies(v, PH_ALLOWED_POCKETBASE_SEMVER()) &&
|
||||
prerelease(v) === null,
|
||||
)
|
||||
.filter((v) => satisfies(v, PH_ALLOWED_POCKETBASE_SEMVER()) && prerelease(v) === null)
|
||||
.sort(compare)
|
||||
.reverse()
|
||||
.map((v) => `${v.major}.${v.minor}.*`),
|
||||
.map((v) => `${v.major}.${v.minor}.*`)
|
||||
)
|
||||
const cjs = `module.exports = ${stringify(versions, null, 2)}`
|
||||
|
||||
|
||||
@ -3,13 +3,11 @@ import { Command } from 'commander'
|
||||
import { freshenPocketbaseVersions } from './freshenPocketbaseVersions'
|
||||
|
||||
export const UpdateCommand = () => {
|
||||
const cmd = new Command(`update`)
|
||||
.description(`Update PocketBase versions`)
|
||||
.action(async (options) => {
|
||||
logger().context({ cli: 'pocketbase:update' })
|
||||
const { info } = logger()
|
||||
await freshenPocketbaseVersions()
|
||||
info('PocketBase versions updated')
|
||||
})
|
||||
const cmd = new Command(`update`).description(`Update PocketBase versions`).action(async (options) => {
|
||||
logger().context({ cli: 'pocketbase:update' })
|
||||
const { info } = logger()
|
||||
await freshenPocketbaseVersions()
|
||||
info('PocketBase versions updated')
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -6,9 +6,7 @@ type Options = {
|
||||
}
|
||||
|
||||
export const MailCommand = () => {
|
||||
const cmd = new Command(`mail`)
|
||||
.description(`PocketHost bulk mail`)
|
||||
.addCommand(SendMailCommand())
|
||||
const cmd = new Command(`mail`).description(`PocketHost bulk mail`).addCommand(SendMailCommand())
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -33,10 +33,7 @@ export const SendMailCommand = () =>
|
||||
logger().context({ cli: 'sendmail' })
|
||||
const { dbg, info } = logger()
|
||||
|
||||
function interpolateString(
|
||||
template: string,
|
||||
dict: { [key: string]: string },
|
||||
): string {
|
||||
function interpolateString(template: string, dict: { [key: string]: string }): string {
|
||||
return template.replace(/\{\$(\w+)\}/g, (match, key) => {
|
||||
dbg({ match, key })
|
||||
const lowerKey = key.toLowerCase()
|
||||
@ -51,14 +48,9 @@ export const SendMailCommand = () =>
|
||||
info(MOTHERSHIP_URL())
|
||||
|
||||
const client = new PocketBase(MOTHERSHIP_URL())
|
||||
await client.admins.authWithPassword(
|
||||
MOTHERSHIP_ADMIN_USERNAME(),
|
||||
MOTHERSHIP_ADMIN_PASSWORD(),
|
||||
)
|
||||
await client.admins.authWithPassword(MOTHERSHIP_ADMIN_USERNAME(), MOTHERSHIP_ADMIN_PASSWORD())
|
||||
|
||||
const message = await client
|
||||
.collection(`campaign_messages`)
|
||||
.getOne(messageId, { expand: 'campaign' })
|
||||
const message = await client.collection(`campaign_messages`).getOne(messageId, { expand: 'campaign' })
|
||||
const { campaign } = message.expand || {}
|
||||
dbg({ messageId, limit, message, campaign })
|
||||
|
||||
@ -69,7 +61,7 @@ export const SendMailCommand = () =>
|
||||
map(campaign.vars, async (sql, k) => {
|
||||
const result = db.prepare(sql).get() as { value: string }
|
||||
vars[k.toLocaleLowerCase()] = result.value
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
dbg({ vars })
|
||||
@ -114,7 +106,7 @@ export const SendMailCommand = () =>
|
||||
})
|
||||
}
|
||||
})
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
db.close()
|
||||
|
||||
@ -9,16 +9,14 @@ type Options = {
|
||||
}
|
||||
|
||||
export const ServeCommand = () => {
|
||||
const cmd = new Command(`serve`)
|
||||
.description(`Run the entire PocketHost stack`)
|
||||
.action(async (options: Options) => {
|
||||
logger().context({ cli: 'serve' })
|
||||
const { dbg, error, info, warn } = logger()
|
||||
info(`Starting`)
|
||||
const cmd = new Command(`serve`).description(`Run the entire PocketHost stack`).action(async (options: Options) => {
|
||||
logger().context({ cli: 'serve' })
|
||||
const { dbg, error, info, warn } = logger()
|
||||
info(`Starting`)
|
||||
|
||||
await Promise.all([mothership(options), daemon(), firewall()])
|
||||
await Promise.all([mothership(options), daemon(), firewall()])
|
||||
|
||||
await neverendingPromise()
|
||||
})
|
||||
await neverendingPromise()
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -1,9 +1,4 @@
|
||||
import {
|
||||
GobotService,
|
||||
ioc,
|
||||
RegisterEnvSettingsService,
|
||||
WinstonLoggerService,
|
||||
} from '@'
|
||||
import { GobotService, ioc, RegisterEnvSettingsService, WinstonLoggerService } from '@'
|
||||
import { version } from '../../package.json'
|
||||
|
||||
export const initIoc = async () => {
|
||||
|
||||
@ -52,10 +52,10 @@ export const createCleanupManager = (slug?: string) => {
|
||||
(c, v) => {
|
||||
return c.then(() => v())
|
||||
},
|
||||
Promise.resolve(),
|
||||
Promise.resolve()
|
||||
).catch((e) => {
|
||||
error(
|
||||
`Cleanup functions are failing. This should never happen, check all cleanup functions to make sure they are trapping their exceptions.`,
|
||||
`Cleanup functions are failing. This should never happen, check all cleanup functions to make sure they are trapping their exceptions.`
|
||||
)
|
||||
throw e
|
||||
})
|
||||
|
||||
@ -10,9 +10,7 @@ const CONSOLE_METHODS = {
|
||||
[LogLevelName.Abort]: console.error,
|
||||
}
|
||||
|
||||
export function ConsoleLogger(
|
||||
initialConfig: Partial<LoggerConfig> = {},
|
||||
): Logger {
|
||||
export function ConsoleLogger(initialConfig: Partial<LoggerConfig> = {}): Logger {
|
||||
let config: LoggerConfig = {
|
||||
level: LogLevelName.Info,
|
||||
pfx: [],
|
||||
|
||||
@ -5,10 +5,7 @@ export type Allocator<T> = {
|
||||
alloc: () => [T, Deallocator]
|
||||
}
|
||||
|
||||
export function ResourceAllocator<T>(
|
||||
initialPoolSize: number,
|
||||
initializer: () => T,
|
||||
): Allocator<T> {
|
||||
export function ResourceAllocator<T>(initialPoolSize: number, initializer: () => T): Allocator<T> {
|
||||
// Available queue to hold resolved items of type T
|
||||
const available: T[] = []
|
||||
|
||||
|
||||
@ -43,11 +43,7 @@ export const createTimerManager = (config?: Partial<TimeManagerConfig>) => {
|
||||
dbg(`done`, cleanups)
|
||||
}
|
||||
|
||||
const repeat = (
|
||||
cb: RepeatableTimerCallback,
|
||||
ms: UnixTimestampMs,
|
||||
initial = true,
|
||||
) => {
|
||||
const repeat = (cb: RepeatableTimerCallback, ms: UnixTimestampMs, initial = true) => {
|
||||
let _unsub: TimerCanceler | undefined = undefined
|
||||
const _again = async () => {
|
||||
const shouldRepeat = await cb()
|
||||
|
||||
@ -1,25 +1,16 @@
|
||||
export function assertExists<TType>(
|
||||
v: TType,
|
||||
message = `Value does not exist`,
|
||||
): asserts v is NonNullable<TType> {
|
||||
export function assertExists<TType>(v: TType, message = `Value does not exist`): asserts v is NonNullable<TType> {
|
||||
if (typeof v === 'undefined') {
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
||||
|
||||
export function assertTruthy<TType>(
|
||||
v: unknown,
|
||||
message = `Value should be truthy`,
|
||||
): asserts v is NonNullable<TType> {
|
||||
export function assertTruthy<TType>(v: unknown, message = `Value should be truthy`): asserts v is NonNullable<TType> {
|
||||
if (!v) {
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
||||
|
||||
export function assert<T>(
|
||||
v: T | undefined | void | null,
|
||||
msg?: string,
|
||||
): asserts v is T {
|
||||
export function assert<T>(v: T | undefined | void | null, msg?: string): asserts v is T {
|
||||
if (!v) {
|
||||
throw new Error(msg || `Assertion failure`)
|
||||
}
|
||||
|
||||
@ -2,25 +2,16 @@ import { values } from '@s-libs/micro-dash'
|
||||
|
||||
export type Unsubscribe = () => void
|
||||
|
||||
export type EventSubscriber<TPayload> = (
|
||||
cb: EventHandler<TPayload>,
|
||||
) => Unsubscribe
|
||||
export type EventEmitter<TPayload> = (
|
||||
payload: TPayload,
|
||||
stopOnHandled?: boolean,
|
||||
) => Promise<boolean>
|
||||
export type EventHandler<TPayload> = (
|
||||
payload: TPayload,
|
||||
isHandled: boolean,
|
||||
) => boolean | void | Promise<boolean | void>
|
||||
export type EventSubscriber<TPayload> = (cb: EventHandler<TPayload>) => Unsubscribe
|
||||
export type EventEmitter<TPayload> = (payload: TPayload, stopOnHandled?: boolean) => Promise<boolean>
|
||||
export type EventHandler<TPayload> = (payload: TPayload, isHandled: boolean) => boolean | void | Promise<boolean | void>
|
||||
|
||||
/**
|
||||
* @param defaultHandler Optional handler to call if no handler calls
|
||||
* `handled()`
|
||||
* @param defaultHandler Optional handler to call if no handler calls `handled()`
|
||||
* @returns Void
|
||||
*/
|
||||
export const createEvent = <TPayload>(
|
||||
defaultHandler?: EventHandler<TPayload>,
|
||||
defaultHandler?: EventHandler<TPayload>
|
||||
): [EventSubscriber<TPayload>, EventEmitter<TPayload>] => {
|
||||
let i = 0
|
||||
const callbacks: any = {}
|
||||
|
||||
@ -11,7 +11,7 @@ export const ioc = <T = unknown>(serviceName: string, register?: T): T => {
|
||||
if (register) {
|
||||
if (serviceName in services) {
|
||||
throw new Error(
|
||||
`Service with key '${serviceName}' already registered. First registered: ${services[serviceName]!.stack}`,
|
||||
`Service with key '${serviceName}' already registered. First registered: ${services[serviceName]!.stack}`
|
||||
)
|
||||
} else {
|
||||
services[serviceName] = { service: register, stack: new Error().stack }
|
||||
|
||||
@ -1,15 +1,11 @@
|
||||
/**
|
||||
* Merges a config object with defaults. Shallow copies root descendants,
|
||||
* skipping`undefined` values.
|
||||
* Merges a config object with defaults. Shallow copies root descendants, skipping`undefined` values.
|
||||
*
|
||||
* @param defaultConfig The default config values
|
||||
* @param partialConfigs A partial config to merge with defaults
|
||||
* @returns
|
||||
*/
|
||||
export function mergeConfig<T>(
|
||||
defaultConfig: T,
|
||||
...partialConfigs: Partial<T>[]
|
||||
): T {
|
||||
export function mergeConfig<T>(defaultConfig: T, ...partialConfigs: Partial<T>[]): T {
|
||||
const result: T = { ...defaultConfig }
|
||||
|
||||
for (const partialConfig of partialConfigs) {
|
||||
|
||||
@ -2,10 +2,9 @@ export type SingletonApi = Object
|
||||
|
||||
export type SingletonBaseConfig = {}
|
||||
|
||||
export function mkSingleton<
|
||||
TConfig extends SingletonBaseConfig,
|
||||
TInstance extends SingletonApi,
|
||||
>(factory: (config: TConfig) => TInstance): (config?: TConfig) => TInstance {
|
||||
export function mkSingleton<TConfig extends SingletonBaseConfig, TInstance extends SingletonApi>(
|
||||
factory: (config: TConfig) => TInstance
|
||||
): (config?: TConfig) => TInstance {
|
||||
let instance: TInstance | undefined
|
||||
|
||||
return (config?: TConfig): TInstance => {
|
||||
|
||||
@ -15,7 +15,7 @@ export const createRestHelper = (config: RestHelperConfig) => {
|
||||
const mkRest = <TPayload extends JsonObject, TResult extends JsonObject>(
|
||||
cmd: RestCommands,
|
||||
method: RestMethods,
|
||||
schema: JSONSchemaType<TPayload>,
|
||||
schema: JSONSchemaType<TPayload>
|
||||
) => {
|
||||
const validator = new Ajv().compile(schema)
|
||||
return async (payload: TPayload): Promise<TResult> => {
|
||||
@ -28,9 +28,7 @@ export const createRestHelper = (config: RestHelperConfig) => {
|
||||
}
|
||||
const _payload = { ...payload }
|
||||
|
||||
const url = `/api/${cmd}${
|
||||
method === RestMethods.Post ? '' : '/:id'
|
||||
}`.replace(/:([a-zA-Z]+)/g, (_, key) => {
|
||||
const url = `/api/${cmd}${method === RestMethods.Post ? '' : '/:id'}`.replace(/:([a-zA-Z]+)/g, (_, key) => {
|
||||
if (!(key in _payload)) {
|
||||
throw new Error(`Payload must include '${key}`)
|
||||
}
|
||||
|
||||
@ -1,14 +1,3 @@
|
||||
import PocketBase, {
|
||||
BaseAuthStore,
|
||||
ClientResponseError,
|
||||
type AuthModel,
|
||||
type UnsubscribeFunc,
|
||||
} from 'pocketbase'
|
||||
import PocketBase, { BaseAuthStore, ClientResponseError, type AuthModel, type UnsubscribeFunc } from 'pocketbase'
|
||||
|
||||
export {
|
||||
AuthModel,
|
||||
BaseAuthStore,
|
||||
ClientResponseError,
|
||||
PocketBase,
|
||||
UnsubscribeFunc,
|
||||
}
|
||||
export { AuthModel, BaseAuthStore, ClientResponseError, PocketBase, UnsubscribeFunc }
|
||||
|
||||
@ -9,12 +9,11 @@ export type CreateInstanceResult = {
|
||||
instance: InstanceFields
|
||||
}
|
||||
|
||||
export const CreateInstancePayloadSchema: JSONSchemaType<CreateInstancePayload> =
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
subdomain: { type: 'string' },
|
||||
},
|
||||
required: ['subdomain'],
|
||||
additionalProperties: false,
|
||||
}
|
||||
export const CreateInstancePayloadSchema: JSONSchemaType<CreateInstancePayload> = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
subdomain: { type: 'string' },
|
||||
},
|
||||
required: ['subdomain'],
|
||||
additionalProperties: false,
|
||||
}
|
||||
|
||||
@ -10,12 +10,11 @@ export type DeleteInstanceResult = {
|
||||
message?: string
|
||||
}
|
||||
|
||||
export const DeleteInstancePayloadSchema: JSONSchemaType<DeleteInstancePayload> =
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
},
|
||||
required: ['id'],
|
||||
additionalProperties: false,
|
||||
}
|
||||
export const DeleteInstancePayloadSchema: JSONSchemaType<DeleteInstancePayload> = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
},
|
||||
required: ['id'],
|
||||
additionalProperties: false,
|
||||
}
|
||||
|
||||
@ -3,18 +3,7 @@ import { InstanceFields, InstanceId } from '..'
|
||||
|
||||
export type UpdateInstancePayload = {
|
||||
id: InstanceId
|
||||
fields: Partial<
|
||||
Pick<
|
||||
InstanceFields,
|
||||
| 'power'
|
||||
| 'secrets'
|
||||
| 'subdomain'
|
||||
| 'syncAdmin'
|
||||
| 'version'
|
||||
| 'dev'
|
||||
| 'cname'
|
||||
>
|
||||
>
|
||||
fields: Partial<Pick<InstanceFields, 'power' | 'secrets' | 'subdomain' | 'syncAdmin' | 'version' | 'dev' | 'cname'>>
|
||||
}
|
||||
|
||||
export const SECRET_KEY_REGEX = /^[A-Z][A-Z0-9_]*$/
|
||||
@ -24,36 +13,35 @@ export type UpdateInstanceResult = {
|
||||
message?: string
|
||||
}
|
||||
|
||||
export const UpdateInstancePayloadSchema: JSONSchemaType<UpdateInstancePayload> =
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
fields: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
syncAdmin: { type: 'boolean', nullable: true },
|
||||
subdomain: { type: 'string', nullable: true },
|
||||
power: { type: 'boolean', nullable: true },
|
||||
version: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
},
|
||||
secrets: {
|
||||
type: 'object',
|
||||
nullable: true,
|
||||
patternProperties: {
|
||||
[SECRET_KEY_REGEX.source]: {
|
||||
anyOf: [{ type: 'string' }],
|
||||
},
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
dev: { type: 'boolean', nullable: true },
|
||||
cname: { type: 'string', nullable: true },
|
||||
export const UpdateInstancePayloadSchema: JSONSchemaType<UpdateInstancePayload> = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
fields: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
syncAdmin: { type: 'boolean', nullable: true },
|
||||
subdomain: { type: 'string', nullable: true },
|
||||
power: { type: 'boolean', nullable: true },
|
||||
version: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
},
|
||||
secrets: {
|
||||
type: 'object',
|
||||
nullable: true,
|
||||
patternProperties: {
|
||||
[SECRET_KEY_REGEX.source]: {
|
||||
anyOf: [{ type: 'string' }],
|
||||
},
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
dev: { type: 'boolean', nullable: true },
|
||||
cname: { type: 'string', nullable: true },
|
||||
},
|
||||
},
|
||||
required: ['id', 'fields'],
|
||||
additionalProperties: false,
|
||||
}
|
||||
},
|
||||
required: ['id', 'fields'],
|
||||
additionalProperties: false,
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
export * from './Instance'
|
||||
export * from './InstanceLog'
|
||||
export * from './Rest'
|
||||
export * from './User'
|
||||
export * from './types'
|
||||
export * from './User'
|
||||
export * from './util'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user