2020-08-04 03:08:54 +12:00
|
|
|
<script>
|
2021-01-15 06:01:31 +13:00
|
|
|
import { writable, get as svelteGet } from "svelte/store"
|
2021-05-10 22:53:32 +12:00
|
|
|
import {
|
|
|
|
notifications,
|
|
|
|
Input,
|
|
|
|
ModalContent,
|
|
|
|
Dropzone,
|
|
|
|
Body,
|
|
|
|
Checkbox,
|
|
|
|
} from "@budibase/bbui"
|
2021-04-02 06:00:46 +13:00
|
|
|
import { store, automationStore, hostingStore } from "builderStore"
|
2021-11-04 00:43:52 +13:00
|
|
|
import { admin, auth } from "stores/portal"
|
2021-05-10 22:53:32 +12:00
|
|
|
import { string, mixed, object } from "yup"
|
2021-05-12 23:49:42 +12:00
|
|
|
import api, { get, post } from "builderStore/api"
|
2021-09-21 22:47:14 +12:00
|
|
|
import analytics, { Events } from "analytics"
|
2021-01-07 06:28:22 +13:00
|
|
|
import { onMount } from "svelte"
|
2021-05-10 23:28:54 +12:00
|
|
|
import { capitalise } from "helpers"
|
2021-05-08 00:13:51 +12:00
|
|
|
import { goto } from "@roxi/routify"
|
2021-08-17 21:41:37 +12:00
|
|
|
import { APP_NAME_REGEX } from "constants"
|
2021-10-06 23:38:17 +13:00
|
|
|
import TemplateList from "./TemplateList.svelte"
|
2020-05-27 22:54:53 +12:00
|
|
|
|
2020-09-26 01:47:42 +12:00
|
|
|
export let template
|
2021-10-20 03:09:38 +13:00
|
|
|
export let inline
|
2020-05-27 22:54:53 +12:00
|
|
|
|
2021-05-10 22:53:32 +12:00
|
|
|
const values = writable({ name: null })
|
2021-04-16 06:28:50 +12:00
|
|
|
const errors = writable({})
|
|
|
|
const touched = writable({})
|
2021-05-10 22:53:32 +12:00
|
|
|
const validator = {
|
2021-08-17 21:41:37 +12:00
|
|
|
name: string()
|
|
|
|
.trim()
|
|
|
|
.required("Your application must have a name")
|
|
|
|
.matches(
|
|
|
|
APP_NAME_REGEX,
|
|
|
|
"App name must be letters, numbers and spaces only"
|
|
|
|
),
|
2021-10-06 11:02:28 +13:00
|
|
|
file: template?.fromFile
|
|
|
|
? mixed().required("Please choose a file to import")
|
|
|
|
: null,
|
2021-05-10 22:53:32 +12:00
|
|
|
}
|
2021-01-08 04:37:41 +13:00
|
|
|
|
2020-08-04 01:59:50 +12:00
|
|
|
let submitting = false
|
2021-04-16 06:28:50 +12:00
|
|
|
let valid = false
|
2021-10-20 03:09:38 +13:00
|
|
|
let initialTemplateInfo = template?.fromFile || template?.key
|
2021-10-06 11:02:28 +13:00
|
|
|
|
2021-05-10 22:53:32 +12:00
|
|
|
$: checkValidity($values, validator)
|
2021-10-20 03:09:38 +13:00
|
|
|
$: showTemplateSelection = !template && !initialTemplateInfo
|
2021-01-07 05:58:29 +13:00
|
|
|
|
|
|
|
onMount(async () => {
|
2021-05-12 04:49:26 +12:00
|
|
|
await hostingStore.actions.fetchDeployedApps()
|
|
|
|
const existingAppNames = svelteGet(hostingStore).deployedAppNames
|
2021-05-10 22:53:32 +12:00
|
|
|
validator.name = string()
|
2021-08-17 21:41:37 +12:00
|
|
|
.trim()
|
2021-05-10 22:53:32 +12:00
|
|
|
.required("Your application must have a name")
|
2021-08-17 21:41:37 +12:00
|
|
|
.matches(APP_NAME_REGEX, "App name must be letters and numbers only")
|
2021-05-12 04:49:26 +12:00
|
|
|
.test(
|
|
|
|
"non-existing-app-name",
|
2021-05-10 22:53:32 +12:00
|
|
|
"Another app with the same name already exists",
|
|
|
|
value => {
|
|
|
|
return !existingAppNames.some(
|
2021-05-12 04:49:26 +12:00
|
|
|
appName => appName.toLowerCase() === value.toLowerCase()
|
|
|
|
)
|
2021-05-10 22:53:32 +12:00
|
|
|
}
|
2021-05-12 04:49:26 +12:00
|
|
|
)
|
2021-01-07 05:58:29 +13:00
|
|
|
})
|
2020-08-04 01:59:50 +12:00
|
|
|
|
2021-04-16 06:28:50 +12:00
|
|
|
const checkValidity = async (values, validator) => {
|
|
|
|
const obj = object().shape(validator)
|
2021-05-04 22:32:22 +12:00
|
|
|
Object.keys(validator).forEach(key => ($errors[key] = null))
|
2021-10-21 06:37:07 +13:00
|
|
|
if (template?.fromFile && values.file == null) {
|
|
|
|
valid = false
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-08-04 01:59:50 +12:00
|
|
|
try {
|
2021-04-16 06:28:50 +12:00
|
|
|
await obj.validate(values, { abortEarly: false })
|
|
|
|
} catch (validationErrors) {
|
2021-05-04 22:32:22 +12:00
|
|
|
validationErrors.inner.forEach(error => {
|
2021-04-16 06:31:04 +12:00
|
|
|
$errors[error.path] = capitalise(error.message)
|
2021-04-16 06:28:50 +12:00
|
|
|
})
|
2020-08-04 01:59:50 +12:00
|
|
|
}
|
2021-10-21 06:37:07 +13:00
|
|
|
|
2021-04-16 06:28:50 +12:00
|
|
|
valid = await obj.isValid(values)
|
2020-08-04 01:59:50 +12:00
|
|
|
}
|
|
|
|
|
2020-11-07 01:38:59 +13:00
|
|
|
async function createNewApp() {
|
2021-11-25 22:29:09 +13:00
|
|
|
const templateToUse = Object.keys(template).length === 0 ? null : template
|
2020-08-04 01:59:50 +12:00
|
|
|
submitting = true
|
2021-05-10 22:53:32 +12:00
|
|
|
|
|
|
|
// Check a template exists if we are important
|
2021-11-25 22:29:09 +13:00
|
|
|
if (templateToUse?.fromFile && !$values.file) {
|
2021-05-10 22:53:32 +12:00
|
|
|
$errors.file = "Please choose a file to import"
|
|
|
|
valid = false
|
|
|
|
submitting = false
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-08-04 01:59:50 +12:00
|
|
|
try {
|
2021-03-16 07:32:20 +13:00
|
|
|
// Create form data to create app
|
|
|
|
let data = new FormData()
|
2021-08-17 21:41:37 +12:00
|
|
|
data.append("name", $values.name.trim())
|
2021-11-25 22:29:09 +13:00
|
|
|
data.append("useTemplate", templateToUse != null)
|
|
|
|
if (templateToUse) {
|
|
|
|
data.append("templateName", templateToUse.name)
|
|
|
|
data.append("templateKey", templateToUse.key)
|
2021-05-10 22:53:32 +12:00
|
|
|
data.append("templateFile", $values.file)
|
2021-03-16 07:32:20 +13:00
|
|
|
}
|
|
|
|
|
2020-08-04 02:26:28 +12:00
|
|
|
// Create App
|
2021-03-16 07:32:20 +13:00
|
|
|
const appResp = await post("/api/applications", data, {})
|
2020-08-04 02:26:28 +12:00
|
|
|
const appJson = await appResp.json()
|
2021-02-24 07:37:37 +13:00
|
|
|
if (!appResp.ok) {
|
|
|
|
throw new Error(appJson.message)
|
|
|
|
}
|
|
|
|
|
2021-09-21 22:47:14 +12:00
|
|
|
analytics.captureEvent(Events.APP.CREATED, {
|
2021-05-10 22:53:32 +12:00
|
|
|
name: $values.name,
|
2021-09-21 22:47:14 +12:00
|
|
|
appId: appJson.instance._id,
|
2021-11-25 22:29:09 +13:00
|
|
|
templateToUse,
|
2020-08-04 01:59:50 +12:00
|
|
|
})
|
2020-08-04 02:26:28 +12:00
|
|
|
|
|
|
|
// Select Correct Application/DB in prep for creating user
|
2020-11-20 05:56:23 +13:00
|
|
|
const applicationPkg = await get(
|
2021-05-17 08:25:37 +12:00
|
|
|
`/api/applications/${appJson.instance._id}/appPackage`
|
2020-11-20 05:56:23 +13:00
|
|
|
)
|
2020-08-04 02:26:28 +12:00
|
|
|
const pkg = await applicationPkg.json()
|
|
|
|
if (applicationPkg.ok) {
|
2020-11-05 06:09:45 +13:00
|
|
|
await store.actions.initialise(pkg)
|
2020-11-25 07:11:34 +13:00
|
|
|
await automationStore.actions.fetch()
|
2021-08-07 03:38:07 +12:00
|
|
|
// update checklist - incase first app
|
|
|
|
await admin.init()
|
2020-08-04 02:26:28 +12:00
|
|
|
} else {
|
|
|
|
throw new Error(pkg)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create user
|
|
|
|
const user = {
|
2021-04-16 06:28:50 +12:00
|
|
|
roleId: $values.roleId,
|
2020-08-04 02:26:28 +12:00
|
|
|
}
|
2021-04-21 04:17:44 +12:00
|
|
|
const userResp = await api.post(`/api/users/metadata/self`, user)
|
2021-04-16 06:28:50 +12:00
|
|
|
await userResp.json()
|
2021-11-05 02:03:18 +13:00
|
|
|
await auth.setInitInfo({})
|
2021-05-17 08:25:37 +12:00
|
|
|
$goto(`/builder/app/${appJson.instance._id}`)
|
2020-08-04 01:59:50 +12:00
|
|
|
} catch (error) {
|
|
|
|
console.error(error)
|
2021-04-09 22:02:53 +12:00
|
|
|
notifications.error(error)
|
2021-02-24 07:37:37 +13:00
|
|
|
submitting = false
|
2020-05-27 21:44:15 +12:00
|
|
|
}
|
|
|
|
}
|
2021-11-04 00:43:52 +13:00
|
|
|
|
|
|
|
function getModalTitle() {
|
|
|
|
let title = "Create App"
|
|
|
|
if (template.fromFile) {
|
|
|
|
title = "Import App"
|
|
|
|
} else if (template.key) {
|
|
|
|
title = "Create app from template"
|
|
|
|
}
|
|
|
|
return title
|
|
|
|
}
|
2021-11-10 05:40:31 +13:00
|
|
|
|
|
|
|
async function onCancel() {
|
|
|
|
template = null
|
|
|
|
await auth.setInitInfo({})
|
|
|
|
}
|
2020-05-27 02:25:37 +12:00
|
|
|
</script>
|
|
|
|
|
2021-10-06 23:38:17 +13:00
|
|
|
{#if showTemplateSelection}
|
|
|
|
<ModalContent
|
2021-10-07 02:33:21 +13:00
|
|
|
title={"Get started quickly"}
|
|
|
|
showConfirmButton={false}
|
2021-10-06 23:38:17 +13:00
|
|
|
size="L"
|
|
|
|
onConfirm={() => {
|
2021-10-20 03:09:38 +13:00
|
|
|
template = {}
|
2021-10-06 23:38:17 +13:00
|
|
|
return false
|
|
|
|
}}
|
2021-10-20 22:05:51 +13:00
|
|
|
showCancelButton={!inline}
|
|
|
|
showCloseIcon={!inline}
|
2021-10-06 23:38:17 +13:00
|
|
|
>
|
|
|
|
<TemplateList
|
2021-10-20 02:06:43 +13:00
|
|
|
onSelect={(selected, { useImport } = {}) => {
|
2021-10-07 02:33:21 +13:00
|
|
|
if (!selected) {
|
2021-10-20 03:09:38 +13:00
|
|
|
template = useImport ? { fromFile: true } : {}
|
2021-10-07 02:33:21 +13:00
|
|
|
return
|
|
|
|
}
|
2021-10-06 23:38:17 +13:00
|
|
|
template = selected
|
2021-05-10 22:53:32 +12:00
|
|
|
}}
|
|
|
|
/>
|
2021-10-06 23:38:17 +13:00
|
|
|
</ModalContent>
|
|
|
|
{:else}
|
|
|
|
<ModalContent
|
2021-11-04 00:43:52 +13:00
|
|
|
title={getModalTitle()}
|
2021-10-06 23:38:17 +13:00
|
|
|
confirmText={template?.fromFile ? "Import app" : "Create app"}
|
|
|
|
onConfirm={createNewApp}
|
2021-11-10 05:40:31 +13:00
|
|
|
onCancel={inline ? onCancel : null}
|
2021-10-20 03:09:38 +13:00
|
|
|
cancelText={inline ? "Back" : undefined}
|
|
|
|
showCloseIcon={!inline}
|
2021-10-06 23:38:17 +13:00
|
|
|
disabled={!valid}
|
|
|
|
>
|
|
|
|
{#if template?.fromFile}
|
|
|
|
<Dropzone
|
|
|
|
error={$touched.file && $errors.file}
|
|
|
|
gallery={false}
|
|
|
|
label="File to import"
|
|
|
|
value={[$values.file]}
|
|
|
|
on:change={e => {
|
|
|
|
$values.file = e.detail?.[0]
|
|
|
|
$touched.file = true
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
{/if}
|
|
|
|
<Body size="S">
|
|
|
|
Give your new app a name, and choose which groups have access (paid plans
|
|
|
|
only).
|
|
|
|
</Body>
|
|
|
|
<Input
|
|
|
|
bind:value={$values.name}
|
|
|
|
error={$touched.name && $errors.name}
|
|
|
|
on:blur={() => ($touched.name = true)}
|
|
|
|
label="Name"
|
|
|
|
/>
|
|
|
|
<Checkbox label="Group access" disabled value={true} text="All users" />
|
|
|
|
</ModalContent>
|
|
|
|
{/if}
|