Update app list screen to show unified app list with publish status
This commit is contained in:
parent
d30a9ef494
commit
f63f9a7c51
|
@ -6,51 +6,65 @@
|
||||||
Layout,
|
Layout,
|
||||||
ActionMenu,
|
ActionMenu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
|
StatusLight,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { gradient } from "actions"
|
import { gradient } from "actions"
|
||||||
import { auth } from "stores/portal"
|
import { auth } from "stores/portal"
|
||||||
|
import { AppStatus } from "constants"
|
||||||
|
|
||||||
export let app
|
export let app
|
||||||
export let exportApp
|
export let exportApp
|
||||||
export let openApp
|
export let viewApp
|
||||||
|
export let editApp
|
||||||
export let deleteApp
|
export let deleteApp
|
||||||
|
export let unpublishApp
|
||||||
export let releaseLock
|
export let releaseLock
|
||||||
export let deletable
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<Layout noPadding gap="XS" alignContent="start">
|
<Layout noPadding gap="XS" alignContent="start">
|
||||||
<div class="preview" use:gradient={{ seed: app.name }} />
|
<div class="preview" use:gradient={{ seed: app.name }} />
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="name" on:click={() => openApp(app)}>
|
{#if app.lockedBy}
|
||||||
|
<Icon name="LockClosed" />
|
||||||
|
{/if}
|
||||||
|
<div class="name" on:click={() => editApp(app)}>
|
||||||
<Heading size="XS">
|
<Heading size="XS">
|
||||||
{app.name}
|
{app.name}
|
||||||
</Heading>
|
</Heading>
|
||||||
</div>
|
</div>
|
||||||
<ActionMenu align="right">
|
<ActionMenu align="right">
|
||||||
<Icon slot="control" name="More" hoverable />
|
<Icon slot="control" name="More" hoverable />
|
||||||
<MenuItem on:click={() => exportApp(app)} icon="Download">
|
{#if app.deployed}
|
||||||
Export
|
<MenuItem on:click={() => viewApp(app)} icon="GlobeOutline">
|
||||||
</MenuItem>
|
View published app
|
||||||
{#if deletable}
|
</MenuItem>
|
||||||
|
<MenuItem on:click={() => unpublishApp(app)} icon="GlobeRemove">
|
||||||
|
Unpublish
|
||||||
|
</MenuItem>
|
||||||
|
{/if}
|
||||||
|
{#if app.lockedBy && app.lockedBy?.email === $auth.user?.email}
|
||||||
|
<MenuItem on:click={() => releaseLock(app)} icon="LockOpen">
|
||||||
|
Release lock
|
||||||
|
</MenuItem>
|
||||||
|
{/if}
|
||||||
|
{#if !app.deployed}
|
||||||
<MenuItem on:click={() => deleteApp(app)} icon="Delete">
|
<MenuItem on:click={() => deleteApp(app)} icon="Delete">
|
||||||
Delete
|
Delete
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{/if}
|
{/if}
|
||||||
{#if app.lockedBy && app.lockedBy?.email === $auth.user?.email}
|
<MenuItem on:click={() => exportApp(app)} icon="Download">
|
||||||
<MenuItem on:click={() => releaseLock(app.appId)} icon="LockOpen">
|
Export
|
||||||
Release Lock
|
</MenuItem>
|
||||||
</MenuItem>
|
|
||||||
{/if}
|
|
||||||
</ActionMenu>
|
</ActionMenu>
|
||||||
</div>
|
</div>
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
Edited {Math.floor(1 + Math.random() * 10)} months ago
|
Updated {Math.floor(1 + Math.random() * 10)} months ago
|
||||||
</Body>
|
</Body>
|
||||||
{#if app.lockedBy}
|
<StatusLight active={app.deployed} neutral={!app.deployed}>
|
||||||
<Icon name="LockClosed" />
|
{#if app.deployed}Published{:else}Unpublished{/if}
|
||||||
{/if}
|
</StatusLight>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,13 +73,17 @@
|
||||||
.wrapper {
|
.wrapper {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.wrapper :global(.spectrum-StatusLight) {
|
||||||
|
padding: 0;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.preview {
|
.preview {
|
||||||
height: 135px;
|
height: 135px;
|
||||||
border-radius: var(--border-radius-s);
|
border-radius: var(--border-radius-s);
|
||||||
margin-bottom: var(--spacing-s);
|
margin-bottom: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title,
|
|
||||||
.status {
|
.status {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -74,12 +92,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
text-decoration: none;
|
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
width: 0;
|
}
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
.title {
|
||||||
margin-right: var(--spacing-m);
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
.title :global(.spectrum-Icon) {
|
||||||
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
.title :global(h1) {
|
.title :global(h1) {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
@ -1,55 +1,82 @@
|
||||||
<script>
|
<script>
|
||||||
import { gradient } from "actions"
|
import { gradient } from "actions"
|
||||||
import { Heading, Button, Icon, ActionMenu, MenuItem } from "@budibase/bbui"
|
import {
|
||||||
|
Heading,
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
ActionMenu,
|
||||||
|
MenuItem,
|
||||||
|
StatusLight,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import { auth } from "stores/portal"
|
import { auth } from "stores/portal"
|
||||||
|
|
||||||
export let app
|
export let app
|
||||||
export let openApp
|
|
||||||
export let exportApp
|
export let exportApp
|
||||||
|
export let viewApp
|
||||||
|
export let editApp
|
||||||
export let deleteApp
|
export let deleteApp
|
||||||
|
export let unpublishApp
|
||||||
export let releaseLock
|
export let releaseLock
|
||||||
export let last
|
export let last
|
||||||
export let deletable
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="title" class:last>
|
<div class="title" class:last>
|
||||||
<div class="preview" use:gradient={{ seed: app.name }} />
|
<div class="preview" use:gradient={{ seed: app.name }} />
|
||||||
<div class="name" on:click={() => openApp(app)}>
|
<div class="name" on:click={() => editApp(app)}>
|
||||||
<Heading size="XS">
|
<Heading size="XS">
|
||||||
{app.name}
|
{app.name}
|
||||||
</Heading>
|
</Heading>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class:last>
|
<div class:last>
|
||||||
Edited {Math.round(Math.random() * 10 + 1)} months ago
|
Updated {Math.round(Math.random() * 10 + 1)} months ago
|
||||||
</div>
|
</div>
|
||||||
<div class:last>
|
<div class:last>
|
||||||
{#if app.lockedBy}
|
<StatusLight
|
||||||
{#if app.lockedBy.email === $auth.user.email}
|
positive={!app.lockedYou && !app.lockedOther}
|
||||||
<div class="status status--locked-you" />
|
notice={app.lockedYou}
|
||||||
|
negative={app.lockedOther}
|
||||||
|
>
|
||||||
|
{#if app.lockedYou}
|
||||||
Locked by you
|
Locked by you
|
||||||
{:else}
|
{:else if app.lockedOther}
|
||||||
<div class="status status--locked-other" />
|
|
||||||
Locked by {app.lockedBy.email}
|
Locked by {app.lockedBy.email}
|
||||||
|
{:else}
|
||||||
|
Open
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
</StatusLight>
|
||||||
<div class="status status--open" />
|
|
||||||
Open
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
<div class:last>
|
<div class:last>
|
||||||
<Button on:click={() => openApp(app)} size="S" secondary>Open</Button>
|
<StatusLight active={app.deployed} neutral={!app.deployed}>
|
||||||
|
{#if app.deployed}Published{:else}Unpublished{/if}
|
||||||
|
</StatusLight>
|
||||||
|
</div>
|
||||||
|
<div class:last>
|
||||||
|
<Button
|
||||||
|
disabled={app.lockedOther}
|
||||||
|
on:click={() => editApp(app)}
|
||||||
|
size="S"
|
||||||
|
secondary>Open</Button
|
||||||
|
>
|
||||||
<ActionMenu align="right">
|
<ActionMenu align="right">
|
||||||
<Icon hoverable slot="control" name="More" />
|
<Icon hoverable slot="control" name="More" />
|
||||||
<MenuItem on:click={() => exportApp(app)} icon="Download">Export</MenuItem>
|
{#if app.deployed}
|
||||||
{#if deletable}
|
<MenuItem on:click={() => viewApp(app)} icon="GlobeOutline">
|
||||||
<MenuItem on:click={() => deleteApp(app)} icon="Delete">Delete</MenuItem>
|
View published app
|
||||||
{/if}
|
</MenuItem>
|
||||||
{#if app.lockedBy && app.lockedBy?.email === $auth.user?.email}
|
<MenuItem on:click={() => unpublishApp(app)} icon="GlobeRemove">
|
||||||
<MenuItem on:click={() => releaseLock(app.appId)} icon="LockOpen">
|
Unpublish
|
||||||
Release Lock
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if app.lockedBy && app.lockedBy?.email === $auth.user?.email}
|
||||||
|
<MenuItem on:click={() => releaseLock(app)} icon="LockOpen">
|
||||||
|
Release lock
|
||||||
|
</MenuItem>
|
||||||
|
{/if}
|
||||||
|
{#if !app.deployed}
|
||||||
|
<MenuItem on:click={() => deleteApp(app)} icon="Delete">Delete</MenuItem>
|
||||||
|
{/if}
|
||||||
|
<MenuItem on:click={() => exportApp(app)} icon="Download">Export</MenuItem>
|
||||||
</ActionMenu>
|
</ActionMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -61,24 +88,16 @@
|
||||||
}
|
}
|
||||||
.name {
|
.name {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.name :global(.spectrum-Heading) {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.title :global(h1:hover) {
|
.title :global(h1:hover) {
|
||||||
color: var(--spectrum-global-color-blue-600);
|
color: var(--spectrum-global-color-blue-600);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: color 130ms ease;
|
transition: color 130ms ease;
|
||||||
}
|
}
|
||||||
.status {
|
|
||||||
height: 10px;
|
|
||||||
width: 10px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
.status--locked-you {
|
|
||||||
background-color: var(--spectrum-global-color-orange-600);
|
|
||||||
}
|
|
||||||
.status--locked-other {
|
|
||||||
background-color: var(--spectrum-global-color-red-600);
|
|
||||||
}
|
|
||||||
.status--open {
|
|
||||||
background-color: var(--spectrum-global-color-green-600);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -10,8 +10,9 @@ export const FrontendTypes = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppStatus = {
|
export const AppStatus = {
|
||||||
DEV: "dev",
|
ALL: "all",
|
||||||
PUBLISHED: "published",
|
DEV: "development",
|
||||||
|
DEPLOYED: "published",
|
||||||
}
|
}
|
||||||
|
|
||||||
// fields on the user table that cannot be edited
|
// fields on the user table that cannot be edited
|
||||||
|
|
|
@ -21,9 +21,7 @@
|
||||||
|
|
||||||
const menu = [
|
const menu = [
|
||||||
{ title: "Apps", href: "/builder/portal/apps" },
|
{ title: "Apps", href: "/builder/portal/apps" },
|
||||||
{ title: "Drafts", href: "/builder/portal/drafts" },
|
|
||||||
{ title: "Users", href: "/builder/portal/manage/users", heading: "Manage" },
|
{ title: "Users", href: "/builder/portal/manage/users", heading: "Manage" },
|
||||||
{ title: "Groups", href: "/builder/portal/manage/groups" },
|
|
||||||
{ title: "Auth", href: "/builder/portal/manage/auth" },
|
{ title: "Auth", href: "/builder/portal/manage/auth" },
|
||||||
{ title: "Email", href: "/builder/portal/manage/email" },
|
{ title: "Email", href: "/builder/portal/manage/email" },
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,15 +26,39 @@
|
||||||
import { AppStatus } from "constants"
|
import { AppStatus } from "constants"
|
||||||
|
|
||||||
let layout = "grid"
|
let layout = "grid"
|
||||||
let appStatus = AppStatus.PUBLISHED
|
let sortBy = "name"
|
||||||
let template
|
let template
|
||||||
let appToDelete
|
let selectedApp
|
||||||
let creationModal
|
let creationModal
|
||||||
let deletionModal
|
let deletionModal
|
||||||
|
let unpublishModal
|
||||||
let creatingApp = false
|
let creatingApp = false
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
|
||||||
$: appStatus && apps.load(appStatus)
|
$: enrichedApps = enrichApps($apps, $auth.user, sortBy)
|
||||||
|
|
||||||
|
const enrichApps = (apps, user, sortBy) => {
|
||||||
|
const enrichedApps = apps.map(app => ({
|
||||||
|
...app,
|
||||||
|
deployed: app.status === AppStatus.DEPLOYED,
|
||||||
|
lockedYou: app.lockedBy?.email === user.email,
|
||||||
|
lockedOther: app.lockedBy && app.lockedBy.email !== user.email,
|
||||||
|
}))
|
||||||
|
if (sortBy === "status") {
|
||||||
|
return enrichedApps.sort((a, b) => {
|
||||||
|
if (a.status === b.status) {
|
||||||
|
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1
|
||||||
|
}
|
||||||
|
return a.status === AppStatus.DEPLOYED ? -1 : 1
|
||||||
|
})
|
||||||
|
} else if (sortBy === "name") {
|
||||||
|
return enrichedApps.sort((a, b) => {
|
||||||
|
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return enrichedApps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const checkKeys = async () => {
|
const checkKeys = async () => {
|
||||||
const response = await api.get(`/api/keys/`)
|
const response = await api.get(`/api/keys/`)
|
||||||
|
@ -60,19 +84,19 @@
|
||||||
creatingApp = false
|
creatingApp = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const openApp = app => {
|
const viewApp = app => {
|
||||||
if (app.lockedBy && app.lockedBy?.email !== $auth.user?.email) {
|
const id = app.deployed ? app.prodId : app.devId
|
||||||
|
window.open(`/${id}`, "_blank")
|
||||||
|
}
|
||||||
|
|
||||||
|
const editApp = app => {
|
||||||
|
if (app.lockedOther) {
|
||||||
notifications.error(
|
notifications.error(
|
||||||
`App locked by ${app.lockedBy.email}. Please allow lock to expire or have them unlock this app.`
|
`App locked by ${app.lockedBy.email}. Please allow lock to expire or have them unlock this app.`
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
$goto(`../../app/${app.devId}`)
|
||||||
if (appStatus === AppStatus.DEV) {
|
|
||||||
$goto(`../../app/${app.appId}`)
|
|
||||||
} else {
|
|
||||||
window.open(`/${app.appId}`, "_blank")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const exportApp = app => {
|
const exportApp = app => {
|
||||||
|
@ -82,36 +106,66 @@
|
||||||
app.name
|
app.name
|
||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
notifications.success("App export complete")
|
notifications.success("App exported successfully")
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
notifications.error(`Error exporting app: ${err}`)
|
||||||
notifications.error("App export failed")
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unpublishApp = app => {
|
||||||
|
selectedApp = app
|
||||||
|
unpublishModal.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmUnpublishApp = async () => {
|
||||||
|
if (!selectedApp) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await del(`/api/applications/${selectedApp.prodId}`)
|
||||||
|
if (response.status !== 200) {
|
||||||
|
const json = await response.json()
|
||||||
|
throw json.message
|
||||||
|
}
|
||||||
|
await apps.load()
|
||||||
|
notifications.success("App unpublished successfully")
|
||||||
|
} catch (err) {
|
||||||
|
notifications.error(`Error unpublishing app: ${err}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteApp = app => {
|
const deleteApp = app => {
|
||||||
appToDelete = app
|
selectedApp = app
|
||||||
deletionModal.show()
|
deletionModal.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmDeleteApp = async () => {
|
const confirmDeleteApp = async () => {
|
||||||
if (!appToDelete) {
|
if (!selectedApp) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await del(`/api/applications/${appToDelete?.appId}`)
|
try {
|
||||||
await apps.load()
|
const response = await del(`/api/applications/${selectedApp?.devId}`)
|
||||||
appToDelete = null
|
if (response.status !== 200) {
|
||||||
notifications.success("App deleted successfully.")
|
const json = await response.json()
|
||||||
|
throw json.message
|
||||||
|
}
|
||||||
|
await apps.load()
|
||||||
|
notifications.success("App deleted successfully")
|
||||||
|
} catch (err) {
|
||||||
|
notifications.error(`Error deleting app: ${err}`)
|
||||||
|
}
|
||||||
|
selectedApp = null
|
||||||
}
|
}
|
||||||
|
|
||||||
const releaseLock = async appId => {
|
const releaseLock = async app => {
|
||||||
try {
|
try {
|
||||||
const response = await del(`/api/dev/${appId}/lock`)
|
const response = await del(`/api/dev/${app.devId}/lock`)
|
||||||
const json = await response.json()
|
if (response.status !== 200) {
|
||||||
if (response.status !== 200) throw json.message
|
const json = await response.json()
|
||||||
|
throw json.message
|
||||||
notifications.success("Lock released")
|
}
|
||||||
await apps.load(appStatus)
|
await apps.load()
|
||||||
|
notifications.success("Lock released successfully")
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifications.error(`Error releasing lock: ${err}`)
|
notifications.error(`Error releasing lock: ${err}`)
|
||||||
}
|
}
|
||||||
|
@ -119,7 +173,7 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
checkKeys()
|
checkKeys()
|
||||||
await apps.load(appStatus)
|
await apps.load()
|
||||||
loaded = true
|
loaded = true
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -136,10 +190,12 @@
|
||||||
<div class="filter">
|
<div class="filter">
|
||||||
<div class="select">
|
<div class="select">
|
||||||
<Select
|
<Select
|
||||||
bind:value={appStatus}
|
bind:value={sortBy}
|
||||||
|
placeholder={null}
|
||||||
options={[
|
options={[
|
||||||
{ label: "Published", value: AppStatus.PUBLISHED },
|
{ label: "Sort by name", value: "name" },
|
||||||
{ label: "In Development", value: AppStatus.DEV },
|
{ label: "Sort by recently updated", value: "updated" },
|
||||||
|
{ label: "Sort by status", value: "status" },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -158,27 +214,28 @@
|
||||||
/>
|
/>
|
||||||
</ActionGroup>
|
</ActionGroup>
|
||||||
</div>
|
</div>
|
||||||
{#if loaded && $apps.length}
|
{#if loaded && enrichedApps.length}
|
||||||
<div
|
<div
|
||||||
class:appGrid={layout === "grid"}
|
class:appGrid={layout === "grid"}
|
||||||
class:appTable={layout === "table"}
|
class:appTable={layout === "table"}
|
||||||
>
|
>
|
||||||
{#each $apps as app, idx (app.appId)}
|
{#each enrichedApps as app, idx (app.appId)}
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={layout === "grid" ? AppCard : AppRow}
|
this={layout === "grid" ? AppCard : AppRow}
|
||||||
deletable={appStatus === AppStatus.PUBLISHED}
|
|
||||||
{releaseLock}
|
{releaseLock}
|
||||||
{app}
|
{app}
|
||||||
{openApp}
|
{unpublishApp}
|
||||||
|
{viewApp}
|
||||||
|
{editApp}
|
||||||
{exportApp}
|
{exportApp}
|
||||||
{deleteApp}
|
{deleteApp}
|
||||||
last={idx === $apps.length - 1}
|
last={idx === enrichedApps.length - 1}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</Layout>
|
</Layout>
|
||||||
{#if !$apps.length && !creatingApp && loaded}
|
{#if !enrichedApps.length && !creatingApp && loaded}
|
||||||
<div class="empty-wrapper">
|
<div class="empty-wrapper">
|
||||||
<Modal inline>
|
<Modal inline>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
|
@ -215,7 +272,15 @@
|
||||||
okText="Delete app"
|
okText="Delete app"
|
||||||
onOk={confirmDeleteApp}
|
onOk={confirmDeleteApp}
|
||||||
>
|
>
|
||||||
Are you sure you want to delete the app <b>{appToDelete?.name}</b>?
|
Are you sure you want to delete the app <b>{selectedApp?.name}</b>?
|
||||||
|
</ConfirmDialog>
|
||||||
|
<ConfirmDialog
|
||||||
|
bind:this={unpublishModal}
|
||||||
|
title="Confirm unpublish"
|
||||||
|
okText="Unpublish app"
|
||||||
|
onOk={confirmUnpublishApp}
|
||||||
|
>
|
||||||
|
Are you sure you want to unpublish the app <b>{selectedApp?.name}</b>?
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -228,7 +293,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.select {
|
.select {
|
||||||
width: 150px;
|
width: 190px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.appGrid {
|
.appGrid {
|
||||||
|
@ -239,7 +304,7 @@
|
||||||
.appTable {
|
.appTable {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto;
|
grid-template-rows: auto;
|
||||||
grid-template-columns: 1fr 1fr 1fr auto;
|
grid-template-columns: 1fr 1fr 1fr 1fr auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.appTable :global(> div) {
|
.appTable :global(> div) {
|
||||||
|
@ -256,7 +321,6 @@
|
||||||
.appTable :global(> div:not(.last)) {
|
.appTable :global(> div:not(.last)) {
|
||||||
border-bottom: var(--border-light);
|
border-bottom: var(--border-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-wrapper {
|
.empty-wrapper {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -1,15 +1,49 @@
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import { get } from "builderStore/api"
|
import { get } from "builderStore/api"
|
||||||
|
import { AppStatus } from "../../constants"
|
||||||
|
|
||||||
export function createAppStore() {
|
export function createAppStore() {
|
||||||
const store = writable([])
|
const store = writable([])
|
||||||
|
|
||||||
async function load(status = "") {
|
async function load() {
|
||||||
try {
|
try {
|
||||||
const res = await get(`/api/applications?status=${status}`)
|
const res = await get(`/api/applications?status=all`)
|
||||||
const json = await res.json()
|
const json = await res.json()
|
||||||
if (res.ok && Array.isArray(json)) {
|
if (res.ok && Array.isArray(json)) {
|
||||||
store.set(json)
|
// Merge apps into one sensible list
|
||||||
|
let appMap = {}
|
||||||
|
let devApps = json.filter(app => app.status === AppStatus.DEV)
|
||||||
|
let deployedApps = json.filter(app => app.status === AppStatus.DEPLOYED)
|
||||||
|
|
||||||
|
// First append all dev app version
|
||||||
|
devApps.forEach(app => {
|
||||||
|
const id = app.appId.substring(8)
|
||||||
|
appMap[id] = {
|
||||||
|
...app,
|
||||||
|
devId: app.appId,
|
||||||
|
devRev: app._rev,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Then merge with all prod app versions
|
||||||
|
deployedApps.forEach(app => {
|
||||||
|
const id = app.appId.substring(4)
|
||||||
|
appMap[id] = {
|
||||||
|
...appMap[id],
|
||||||
|
...app,
|
||||||
|
prodId: app.appId,
|
||||||
|
prodRev: app._rev,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Transform into an array and clean up
|
||||||
|
const apps = Object.values(appMap)
|
||||||
|
apps.forEach(app => {
|
||||||
|
app.appId = app.devId.substring(8)
|
||||||
|
delete app._id
|
||||||
|
delete app._rev
|
||||||
|
})
|
||||||
|
store.set(apps)
|
||||||
} else {
|
} else {
|
||||||
store.set([])
|
store.set([])
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,9 @@ const StaticDatabases = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppStatus = {
|
const AppStatus = {
|
||||||
DEV: "dev",
|
DEV: "development",
|
||||||
ALL: "all",
|
ALL: "all",
|
||||||
DEPLOYED: "PUBLISHED",
|
DEPLOYED: "published",
|
||||||
}
|
}
|
||||||
|
|
||||||
const DocumentTypes = {
|
const DocumentTypes = {
|
||||||
|
|
Loading…
Reference in New Issue