[#31] replaced the initial admin create interactive cli with Installer web page

This commit is contained in:
Gani Georgiev
2022-07-10 11:46:21 +03:00
parent 460c684caa
commit 0739e90ff2
28 changed files with 812 additions and 1064 deletions
+6 -6
View File
@@ -16,11 +16,15 @@
let oldLocation = undefined;
let showAppSidebar = false;
function handleRouteLoading(e) {
if (e?.detail?.location === oldLocation) {
return; // not an actual change
}
showAppSidebar = !!e?.detail?.userData?.showAppSidebar;
oldLocation = e?.detail?.location;
// resets
@@ -30,11 +34,7 @@
}
function handleRouteFailure() {
if (ApiClient.AuthStore.isValid) {
replace("/");
} else {
ApiClient.logout();
}
replace("/");
}
function logout() {
@@ -43,7 +43,7 @@
</script>
<div class="app-layout">
{#if $admin?.id}
{#if $admin?.id && showAppSidebar}
<aside class="app-sidebar">
<a href="/" class="logo logo-sm" use:link>
<img
-487
View File
@@ -1,487 +0,0 @@
<script>
import tooltip from "@/actions/tooltip";
import ObjectSelect from "@/components/base/ObjectSelect.svelte";
import OverlayPanel from "@/components/base/OverlayPanel.svelte";
import { addInfoToast } from "@/stores/toasts";
let popupActive = true;
setTimeout(function () {
addInfoToast("Hello world");
}, 500);
</script>
<div class="form-field">
<label for="">EXAMPLE</label>
<ObjectSelect multiple searchable items={["test1", "test2"]} />
</div>
<hr />
<div class="form-field">
<label for="">EXAMPLE</label>
<ObjectSelect searchable items={["test1", "test2"]} />
</div>
<hr />
<div class="form-field disabled">
<label for="">EXAMPLE</label>
<ObjectSelect disabled searchable items={["test1", "test2"]} />
</div>
<hr />
<div class="alert">
<div class="icon">
<i class="ri-information-line" />
</div>
<div class="content">Hello world!</div>
<div class="close">
<i class="ri-close-line" />
</div>
</div>
<div class="alert alert-info">
<div class="icon">
<i class="ri-information-line" />
</div>
<div class="content">Hello world!</div>
<div class="close">
<i class="ri-close-line" />
</div>
</div>
<div class="alert alert-danger">
<div class="icon">
<i class="ri-information-line" />
</div>
<div class="content">Hello world!</div>
<div class="close">
<i class="ri-close-line" />
</div>
</div>
<div class="alert alert-warning">
<div class="icon">
<i class="ri-error-warning-line" />
</div>
<div class="content">Hello world!</div>
<div class="close">
<i class="ri-close-line" />
</div>
</div>
<div class="alert alert-success">
<div class="icon">
<i class="ri-checkbox-circle-line" />
</div>
<div class="content">Hello world!</div>
<div class="close">
<i class="ri-close-line" />
</div>
</div>
<hr />
<h1>H1 title</h1>
<p>Lorem Ipsum dolor sit amet...</p>
<h2>H2 title</h2>
<p>Lorem Ipsum dolor sit amet...</p>
<h3>H3 title</h3>
<p>Lorem Ipsum dolor sit amet...</p>
<h4>H4 title</h4>
<p>Lorem Ipsum dolor sit amet...</p>
<h5>H5 title</h5>
<p>Lorem Ipsum dolor sit amet...</p>
<h6>H6 title</h6>
<p>Lorem Ipsum dolor sit amet...</p>
<hr />
<div class="grid">
<div class="col-6">COL1</div>
<div class="col-6">COL2</div>
</div>
<p>
Lorem Ipsum is <a href="/">simply dummy</a> text of the printing and typesetting industry. Lorem Ipsum has
been the industry's
<strong>standard</strong> dummy text ever since the 1500s, when an unknown printer took a galley of type
and <em>scrambled</em> it to make a type specimen book. It has survived not only five centuries, but also
the leap into electronic typesetting, remaining<sup>1</sup> essentially<sub>2</sub> unchanged.
</p>
<p>
It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and
more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum
</p>
<ul>
<li><small>Option 1</small></li>
<li>Option 2</li>
<li>Option 3</li>
</ul>
<ol>
<li>Option 1</li>
<li>Option 2</li>
<li>Option 3</li>
</ol>
<hr />
<span use:tooltip={"My tooltip"}>Lorem Ipsum</span>
<hr />
<button class="btn">Button default</button>
<button class="btn btn-danger">Button danger</button>
<button class="btn btn-warning">Button warning</button>
<button class="btn btn-success">Button success</button>
<button class="btn btn-info">Button info</button>
<button class="btn btn-hint">Button hint</button>
<hr />
<button class="btn btn-secondary">Button default</button>
<button class="btn btn-secondary btn-danger">Button danger</button>
<button class="btn btn-secondary btn-warning">Button danger</button>
<button class="btn btn-secondary btn-success">Button success</button>
<button class="btn btn-secondary btn-info">Button info</button>
<button class="btn btn-secondary btn-hint">Button hint</button>
<hr />
<button class="btn btn-outline">Button default</button>
<button class="btn btn-outline btn-danger">Button danger</button>
<button class="btn btn-outline btn-warning">Button danger</button>
<button class="btn btn-outline btn-success">Button success</button>
<button class="btn btn-outline btn-info">Button info</button>
<button class="btn btn-outline btn-hint">Button hint</button>
<hr />
<button disabled class="btn">Button default</button>
<button disabled class="btn btn-danger">Button danger</button>
<button disabled class="btn btn-warning">Button warning</button>
<button disabled class="btn btn-success">Button success</button>
<button disabled class="btn btn-info">Button info</button>
<button disabled class="btn btn-hint">Button hint</button>
<button disabled class="btn btn-secondary">Button default</button>
<button disabled class="btn btn-secondary btn-danger">Button danger</button>
<button disabled class="btn btn-secondary btn-warning">Button danger</button>
<button disabled class="btn btn-secondary btn-success">Button success</button>
<button disabled class="btn btn-secondary btn-info">Button info</button>
<button disabled class="btn btn-secondary btn-hint">Button hint</button>
<hr />
<button class="btn">
<i class="ri-mail-line" />
<span class="txt">Button default</span>
</button>
<button class="btn btn-danger">
<i class="ri-mail-line" />
<span class="txt">Button danger</span>
</button>
<button class="btn btn-warning">
<i class="ri-mail-line" />
<span class="txt">Button warning</span>
</button>
<button class="btn btn-success">
<i class="ri-mail-line" />
<span class="txt">Button success</span>
</button>
<button class="btn btn-hint">
<i class="ri-mail-line" />
<span class="txt">Button hint</span>
</button>
<hr />
<button class="btn btn-sm">
<i class="ri-mail-line" />
<span class="txt">Button default</span>
</button>
<button class="btn btn-danger btn-sm">
<i class="ri-mail-line" />
<span class="txt">Button danger</span>
</button>
<button class="btn btn-warning btn-sm">
<i class="ri-mail-line" />
<span class="txt">Button warning</span>
</button>
<button class="btn btn-success btn-sm">
<i class="ri-mail-line" />
<span class="txt">Button success</span>
</button>
<button class="btn btn-hint btn-sm">
<i class="ri-mail-line" />
<span class="txt">Button hint</span>
</button>
<hr />
<button class="btn btn-lg">
<i class="ri-mail-line" />
<span class="txt">Button default</span>
</button>
<button class="btn btn-danger btn-lg">
<i class="ri-mail-line" />
<span class="txt">Button danger</span>
</button>
<button class="btn btn-warning btn-lg">
<i class="ri-mail-line" />
<span class="txt">Button warning</span>
</button>
<button class="btn btn-success btn-lg">
<i class="ri-mail-line" />
<span class="txt">Button success</span>
</button>
<button class="btn btn-hint btn-lg">
<i class="ri-mail-line" />
<span class="txt">Button hint</span>
</button>
<hr />
<button class="btn btn-circle">
<i class="ri-mail-line" />
</button>
<button class="btn btn-sm btn-circle">
<i class="ri-mail-line" />
</button>
<button class="btn btn-lg btn-circle">
<i class="ri-mail-line" />
</button>
<button class="btn btn-secondary btn-circle">
<i class="ri-mail-line" />
</button>
<button class="btn btn-secondary btn-sm btn-circle">
<i class="ri-mail-line" />
</button>
<button class="btn btn-secondary btn-lg btn-circle">
<i class="ri-mail-line" />
</button>
<hr />
<button class="btn btn-loading">
<i class="ri-mail-line" />
<span class="txt">Button Loading</span>
</button>
<button class="btn btn-loading btn-primary btn-sm">
<i class="ri-mail-line" />
<span class="txt">Button Loading</span>
</button>
<button class="btn btn-loading btn-danger btn-lg">
<i class="ri-mail-line" />
<span class="txt">Button Loading</span>
</button>
<button class="btn btn-loading btn-circle">
<i class="ri-mail-line" />
</button>
<button class="btn btn-loading btn-primary btn-sm btn-circle">
<i class="ri-mail-line" />
</button>
<button class="btn btn-loading btn-danger btn-lg btn-circle">
<i class="ri-mail-line" />
</button>
<hr />
<button disabled class="btn btn-loading">
<i class="ri-mail-line" />
<span class="txt">Button Loading</span>
</button>
<button disabled class="btn btn-loading btn-primary btn-sm">
<i class="ri-mail-line" />
<span class="txt">Button Loading</span>
</button>
<button disabled class="btn btn-loading btn-danger btn-lg">
<i class="ri-mail-line" />
<span class="txt">Button Loading</span>
</button>
<button disabled class="btn btn-loading btn-circle">
<i class="ri-mail-line" />
</button>
<button disabled class="btn btn-loading btn-primary btn-sm btn-circle">
<i class="ri-mail-line" />
</button>
<button disabled class="btn btn-loading btn-danger btn-lg btn-circle">
<i class="ri-mail-line" />
</button>
<hr />
<input type="text" />
<hr />
<select>
<option value="1" selected>Option 1</option>
<option value="">Option 2</option>
<option value="">Option 3</option>
</select>
<hr />
<textarea cols="30" rows="10" />
<hr />
<div class="form-field required">
<label for="field_1">Name</label>
<input type="text" id="field_1" placeholder="Name 123" />
</div>
<div class="form-field required">
<label for="field_2">Description</label>
<textarea id="field_2" />
</div>
<div class="form-field">
<label for="field_3">Choose value</label>
<select id="field_3">
<option value="1" selected>Option 1</option>
<option value="">Option 2</option>
<option value="">Option 3</option>
</select>
</div>
<hr />
<div class="form-field">
<input type="text" placeholder="Lorem ipsum dolor sit amet..." />
</div>
<div class="form-field">
<textarea />
</div>
<div class="form-field">
<select>
<option value="1" selected>Option 1</option>
<option value="">Option 2</option>
<option value="">Option 3</option>
</select>
</div>
<hr />
<div class="form-field">
<input type="checkbox" id="field_check" />
<label for="field_check">
I agree with the <a href="/">terms and conditions</a>
</label>
</div>
<div class="form-field">
<input type="radio" name="radio_check" id="field_radio1" value="1" />
<label for="field_radio1">Radio 1</label>
</div>
<div class="form-field">
<input type="radio" name="radio_check" id="field_radio2" value="2" />
<label for="field_radio2">Radio 2</label>
</div>
<div class="form-field form-field-toggle">
<input type="checkbox" id="field_toggle" />
<label for="field_toggle">Toggle check</label>
</div>
<hr />
<div class="form-field error">
<label for="field_error">Name + error</label>
<input type="text" id="field_error" />
<div class="help-block help-block-error">
<p>Something went wrong</p>
</div>
</div>
<div class="form-field">
<label for="field_hint">Name + hint</label>
<input type="text" id="field_hint" />
<div class="help-block">
<p>Lorem ipsum dolor <a href="/">sit</a> amet</p>
</div>
</div>
<div class="form-field">
<input type="checkbox" id="field_check_hint" />
<label for="field_check_hint">Checkbox hint</label>
<div class="help-block">Lorem ipsum</div>
</div>
<div class="form-field has-error">
<input type="checkbox" id="field_check_error" />
<label for="field_check_error">Checkbox error</label>
<div class="help-block help-error">Lorem ipsum</div>
</div>
<hr />
<div class="form-field disabled">
<input type="checkbox" id="field_check" disabled />
<label for="field_check">
I agree with the <a href="/">terms and conditions</a>
</label>
</div>
<div class="form-field">
<input type="radio" name="radio_check" id="field_radio1" value="1" disabled />
<label for="field_radio1">Radio 1</label>
</div>
<div class="form-field">
<input type="radio" name="radio_check" id="field_radio2" value="2" disabled />
<label for="field_radio2">Radio 2</label>
</div>
<div class="form-field form-field-toggle disabled">
<input type="checkbox" id="field_toggle" disabled />
<label for="field_toggle" />
</div>
<hr />
<div class="form-field disabled">
<label for="field_addon1">Name</label>
<div class="form-field-addon">
<i class="ri-mail-line" />
</div>
<input disabled type="text" id="field_addon1" />
</div>
<div class="form-field">
<div class="form-field-addon">
<i class="ri-mail-line" />
</div>
<input type="text" />
</div>
<hr />
<div class="form-group">
<div class="form-field">
<label for="field_group1">Name</label>
<div class="form-field-addon">
<div class="btn btn-circle btn-secondary">
<i class="ri-mail-line" />
</div>
</div>
<input type="text" id="field_group1" />
</div>
<div class="form-field">
<label for="field_group2">Password</label>
<div class="form-field-addon">
<i class="ri-mail-line" />
</div>
<input type="password" id="field_group2" />
</div>
</div>
<OverlayPanel bind:active={popupActive} popup={false}>
<h4 slot="header">My title</h4>
<p>Lorem ipsum dolor sit amet...</p>
<svelte:fragment slot="footer">
<button class="btn btn-secondary">Cancel</button>
<button class="btn btn-expanded">Save</button>
</svelte:fragment>
</OverlayPanel>
-5
View File
@@ -1,5 +0,0 @@
<script>
import { replace } from "svelte-spa-router";
replace("/collections");
</script>
+43
View File
@@ -0,0 +1,43 @@
<script>
import { tick } from "svelte";
import { replace } from "svelte-spa-router";
import ApiClient from "@/utils/ApiClient";
import FullPage from "@/components/base/FullPage.svelte";
import Installer from "@/components/base/Installer.svelte";
let showInstaller = false;
handler();
function handler() {
showInstaller = false;
const realQueryParams = new URLSearchParams(window.location.search);
if (realQueryParams.has(import.meta.env.PB_INSTALLER_PARAM)) {
ApiClient.logout(false);
showInstaller = true;
return;
}
if (ApiClient.AuthStore.isValid) {
replace("/collections");
} else {
ApiClient.logout();
}
}
</script>
{#if showInstaller}
<FullPage>
<Installer
on:submit={async () => {
showInstaller = false;
await tick();
// clear the installer param
window.location.search = "";
}}
/>
</FullPage>
{/if}
+76
View File
@@ -0,0 +1,76 @@
<script>
import { createEventDispatcher } from "svelte";
import ApiClient from "@/utils/ApiClient";
import Field from "@/components/base/Field.svelte";
const dispatch = createEventDispatcher();
let email = "";
let password = "";
let passwordConfirm = "";
let isLoading = false;
async function submit() {
if (isLoading) {
return;
}
isLoading = true;
try {
await ApiClient.Admins.create({
email,
password,
passwordConfirm,
});
await ApiClient.Admins.authViaEmail(email, password);
dispatch("submit");
} catch (err) {
ApiClient.errorResponseHandler(err);
}
isLoading = false;
}
</script>
<form class="block" autocomplete="off" on:submit|preventDefault={submit}>
<div class="content txt-center m-b-base">
<h4>Create your first admin account in order to continue</h4>
</div>
<Field class="form-field required" name="email" let:uniqueId>
<label for={uniqueId}>Email</label>
<!-- svelte-ignore a11y-autofocus -->
<input type="email" autocomplete="off" id={uniqueId} bind:value={email} required autofocus />
</Field>
<Field class="form-field required" name="password" let:uniqueId>
<label for={uniqueId}>Password</label>
<input
type="password"
autocomplete="new-password"
minlength="10"
id={uniqueId}
bind:value={password}
required
/>
<div class="help-block">Minimum 10 characters.</div>
</Field>
<Field class="form-field required" name="passwordConfirm" let:uniqueId>
<label for={uniqueId}>Password confirm</label>
<input type="password" minlength="10" id={uniqueId} bind:value={passwordConfirm} required />
</Field>
<button
type="submit"
class="btn btn-lg btn-block btn-next"
class:btn-disabled={isLoading}
class:btn-loading={isLoading}
>
<span class="txt">Create and login</span>
<i class="ri-arrow-right-line" />
</button>
</form>
+49 -23
View File
@@ -1,5 +1,7 @@
import { replace } from "svelte-spa-router";
import { wrap } from "svelte-spa-router/wrap";
import ApiClient from "@/utils/ApiClient";
import PageIndex from "@/components/PageIndex.svelte";
import PageLogs from "@/components/logs/PageLogs.svelte";
import PageRecords from "@/components/records/PageRecords.svelte";
import PageUsers from "@/components/users/PageUsers.svelte";
@@ -11,107 +13,131 @@ import PageStorage from "@/components/settings/PageStorage.svelte";
import PageAuthProviders from "@/components/settings/PageAuthProviders.svelte";
import PageTokenOptions from "@/components/settings/PageTokenOptions.svelte";
const routes = {
"/_elements": wrap({
asyncComponent: () => import("@/components/Elements.svelte"),
}),
const baseConditions = [
async (details) => {
const realQueryParams = new URLSearchParams(window.location.search);
if (details.location !== "/" && realQueryParams.has(import.meta.env.PB_INSTALLER_PARAM)) {
return replace("/")
}
return true
}
];
const routes = {
"/login": wrap({
component: PageAdminLogin,
conditions: [(_) => !ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => !ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: false },
}),
"/request-password-reset": wrap({
asyncComponent: () => import("@/components/admins/PageAdminRequestPasswordReset.svelte"),
conditions: [(_) => !ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => !ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: false },
}),
"/confirm-password-reset/:token": wrap({
asyncComponent: () => import("@/components/admins/PageAdminConfirmPasswordReset.svelte"),
conditions: [(_) => !ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => !ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: false },
}),
"/collections": wrap({
component: PageRecords,
conditions: [(_) => ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: true },
}),
"/logs": wrap({
component: PageLogs,
conditions: [(_) => ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: true },
}),
"/users": wrap({
component: PageUsers,
conditions: [(_) => ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: true },
}),
"/users/confirm-password-reset/:token": wrap({
asyncComponent: () => import("@/components/users/PageUserConfirmPasswordReset.svelte"),
conditions: [
conditions: baseConditions.concat([
() => {
// ensure that there is no authenticated user/admin model
ApiClient.logout(false);
return true;
},
],
]),
userData: { showAppSidebar: false },
}),
"/users/confirm-verification/:token": wrap({
asyncComponent: () => import("@/components/users/PageUserConfirmVerification.svelte"),
conditions: [
conditions: baseConditions.concat([
() => {
// ensure that there is no authenticated user/admin model
ApiClient.logout(false);
return true;
},
],
]),
userData: { showAppSidebar: false },
}),
"/users/confirm-email-change/:token": wrap({
asyncComponent: () => import("@/components/users/PageUserConfirmEmailChange.svelte"),
conditions: [
conditions: baseConditions.concat([
() => {
// ensure that there is no authenticated user/admin model
ApiClient.logout(false);
return true;
},
],
]),
userData: { showAppSidebar: false },
}),
"/settings": wrap({
component: PageApplication,
conditions: [(_) => ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: true },
}),
"/settings/admins": wrap({
component: PageAdmins,
conditions: [(_) => ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: true },
}),
"/settings/mail": wrap({
component: PageMail,
conditions: [(_) => ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: true },
}),
"/settings/storage": wrap({
component: PageStorage,
conditions: [(_) => ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: true },
}),
"/settings/auth-providers": wrap({
component: PageAuthProviders,
conditions: [(_) => ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: true },
}),
"/settings/tokens": wrap({
component: PageTokenOptions,
conditions: [(_) => ApiClient.AuthStore.isValid],
conditions: baseConditions.concat([(_) => ApiClient.AuthStore.isValid]),
userData: { showAppSidebar: true },
}),
// fallback
"*": wrap({
asyncComponent: () => import("@/components/NotFoundPage.svelte"),
component: PageIndex,
userData: { showAppSidebar: false },
}),
};