Skip to content

Commit

Permalink
added loadable wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
Eugeny committed Dec 22, 2024
1 parent d41148f commit 935e142
Show file tree
Hide file tree
Showing 19 changed files with 641 additions and 631 deletions.
1 change: 1 addition & 0 deletions warpgate-web/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default [
},
globals: {
...globals.browser,
T: false,
},
},
rules: {
Expand Down
142 changes: 70 additions & 72 deletions warpgate-web/src/admin/App.svelte
Original file line number Diff line number Diff line change
@@ -1,81 +1,79 @@
<script lang="ts">
import { serverInfo, reloadServerInfo } from 'gateway/lib/store'
import { serverInfo, reloadServerInfo } from 'gateway/lib/store'
import Router, { link } from 'svelte-spa-router'
import active from 'svelte-spa-router/active'
import { wrap } from 'svelte-spa-router/wrap'
import ThemeSwitcher from 'common/ThemeSwitcher.svelte'
import DelayedSpinner from 'common/DelayedSpinner.svelte'
import AuthBar from 'common/AuthBar.svelte'
import Brand from 'common/Brand.svelte'
import Router, { link } from 'svelte-spa-router'
import active from 'svelte-spa-router/active'
import { wrap } from 'svelte-spa-router/wrap'
import ThemeSwitcher from 'common/ThemeSwitcher.svelte'
import AuthBar from 'common/AuthBar.svelte'
import Brand from 'common/Brand.svelte'
import Loadable from 'common/Loadable.svelte'
async function init () {
await reloadServerInfo()
}
async function init () {
await reloadServerInfo()
}
init()
const initPromise = init()
const routes = {
'/': wrap({
asyncComponent: () => import('./Home.svelte') as any,
}),
'/sessions/:id': wrap({
asyncComponent: () => import('./Session.svelte') as any,
}),
'/recordings/:id': wrap({
asyncComponent: () => import('./Recording.svelte') as any,
}),
'/targets/create': wrap({
asyncComponent: () => import('./CreateTarget.svelte') as any,
}),
'/targets/:id': wrap({
asyncComponent: () => import('./Target.svelte') as any,
}),
'/roles/create': wrap({
asyncComponent: () => import('./CreateRole.svelte') as any,
}),
'/roles/:id': wrap({
asyncComponent: () => import('./Role.svelte') as any,
}),
'/users/create': wrap({
asyncComponent: () => import('./CreateUser.svelte') as any,
}),
'/users/:id': wrap({
asyncComponent: () => import('./User.svelte') as any,
}),
'/log': wrap({
asyncComponent: () => import('./Log.svelte') as any,
}),
'/config': wrap({
asyncComponent: () => import('./config/Config.svelte') as any,
}),
'/config/parameters': wrap({
asyncComponent: () => import('./config/Parameters.svelte') as any,
}),
'/config/users': wrap({
asyncComponent: () => import('./config/Users.svelte') as any,
}),
'/config/roles': wrap({
asyncComponent: () => import('./config/Roles.svelte') as any,
}),
'/config/targets': wrap({
asyncComponent: () => import('./config/Targets.svelte') as any,
}),
'/config/ssh': wrap({
asyncComponent: () => import('./config/SSHKeys.svelte') as any,
}),
'/config/tickets': wrap({
asyncComponent: () => import('./config/Tickets.svelte') as any,
}),
'/config/tickets/create': wrap({
asyncComponent: () => import('./CreateTicket.svelte') as any,
}),
}
const routes = {
'/': wrap({
asyncComponent: () => import('./Home.svelte') as any,
}),
'/sessions/:id': wrap({
asyncComponent: () => import('./Session.svelte') as any,
}),
'/recordings/:id': wrap({
asyncComponent: () => import('./Recording.svelte') as any,
}),
'/targets/create': wrap({
asyncComponent: () => import('./CreateTarget.svelte') as any,
}),
'/targets/:id': wrap({
asyncComponent: () => import('./Target.svelte') as any,
}),
'/roles/create': wrap({
asyncComponent: () => import('./CreateRole.svelte') as any,
}),
'/roles/:id': wrap({
asyncComponent: () => import('./Role.svelte') as any,
}),
'/users/create': wrap({
asyncComponent: () => import('./CreateUser.svelte') as any,
}),
'/users/:id': wrap({
asyncComponent: () => import('./User.svelte') as any,
}),
'/log': wrap({
asyncComponent: () => import('./Log.svelte') as any,
}),
'/config': wrap({
asyncComponent: () => import('./config/Config.svelte') as any,
}),
'/config/parameters': wrap({
asyncComponent: () => import('./config/Parameters.svelte') as any,
}),
'/config/users': wrap({
asyncComponent: () => import('./config/Users.svelte') as any,
}),
'/config/roles': wrap({
asyncComponent: () => import('./config/Roles.svelte') as any,
}),
'/config/targets': wrap({
asyncComponent: () => import('./config/Targets.svelte') as any,
}),
'/config/ssh': wrap({
asyncComponent: () => import('./config/SSHKeys.svelte') as any,
}),
'/config/tickets': wrap({
asyncComponent: () => import('./config/Tickets.svelte') as any,
}),
'/config/tickets/create': wrap({
asyncComponent: () => import('./CreateTicket.svelte') as any,
}),
}
</script>

{#await init()}
<DelayedSpinner />
{:then}
<Loadable promise={initPromise}>
<div class="app container">
<header>
<a href="/@warpgate" class="d-flex logo-link me-4">
Expand All @@ -100,7 +98,7 @@ const routes = {
<ThemeSwitcher />
</footer>
</div>
{/await}
</Loadable>

<style lang="scss">
@media (max-width: 767px) {
Expand Down
185 changes: 85 additions & 100 deletions warpgate-web/src/admin/CredentialEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,16 @@
<script lang="ts">
import { faIdBadge, faKey, faKeyboard, faMobileScreen } from '@fortawesome/free-solid-svg-icons'
import { api, CredentialKind, type ExistingPasswordCredential, type ExistingPublicKeyCredential, type ExistingSsoCredential, type ExistingOtpCredential, type UserRequireCredentialsPolicy } from 'admin/lib/api'
import DelayedSpinner from 'common/DelayedSpinner.svelte'
import Fa from 'svelte-fa'
import { Button } from '@sveltestrap/sveltestrap'
import { stringifyError } from 'common/errors'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
import CreatePasswordModal from './CreatePasswordModal.svelte'
import SsoCredentialModal from './SsoCredentialModal.svelte'
import PublicKeyCredentialModal from './PublicKeyCredentialModal.svelte'
import CreateOtpModal from './CreateOtpModal.svelte'
import AuthPolicyEditor from './AuthPolicyEditor.svelte'
import { possibleCredentials } from 'common/protocols'
import CredentialUsedStateBadge from 'common/CredentialUsedStateBadge.svelte'
import Loadable from 'common/Loadable.svelte'
interface Props {
userId: string
Expand All @@ -29,7 +27,6 @@
}
let { userId, username, credentialPolicy = $bindable() }: Props = $props()
let error: string|null = $state(null)
let credentials: ExistingCredential[] = $state([])
let creatingPassword = $state(false)
Expand All @@ -49,16 +46,12 @@
]
async function load () {
try {
await Promise.all([
loadPasswords(),
loadSso(),
loadPublicKeys(),
loadOtp(),
])
} catch (err) {
error = await stringifyError(err)
}
await Promise.all([
loadPasswords(),
loadSso(),
loadPublicKeys(),
loadOtp(),
])
}
async function loadPasswords () {
Expand Down Expand Up @@ -240,97 +233,89 @@
}}>Add SSO</Button>
</div>

{#await loadPromise}
<DelayedSpinner />
{:then}

<div class="list-group list-group-flush mb-3">
{#each credentials as credential}
<div class="list-group-item credential">
{#if credential.kind === CredentialKind.Password }
<Fa fw icon={faKeyboard} />
<span class="label me-auto">Password</span>
{/if}
{#if credential.kind === 'PublicKey'}
<Fa fw icon={faKey} />
<div class="main me-auto">
<div class="label d-flex align-items-center">
{credential.label}
<Loadable promise={loadPromise}>
<div class="list-group list-group-flush mb-3">
{#each credentials as credential}
<div class="list-group-item credential">
{#if credential.kind === CredentialKind.Password }
<Fa fw icon={faKeyboard} />
<span class="label me-auto">Password</span>
{/if}
{#if credential.kind === 'PublicKey'}
<Fa fw icon={faKey} />
<div class="main me-auto">
<div class="label d-flex align-items-center">
{credential.label}
</div>
<small class="d-block text-muted">{abbreviatePublicKey(credential.opensshPublicKey)}</small>
</div>
<small class="d-block text-muted">{abbreviatePublicKey(credential.opensshPublicKey)}</small>
</div>
<CredentialUsedStateBadge credential={credential} />
<div class="me-2"></div>
{/if}
{#if credential.kind === 'Totp'}
<Fa fw icon={faMobileScreen} />
<span class="label me-auto">One-time password</span>
{/if}
{#if credential.kind === CredentialKind.Sso}
<Fa fw icon={faIdBadge} />
<span class="label">Single sign-on</span>
<span class="text-muted ms-2 me-auto">
{credential.email}
{#if credential.provider} ({credential.provider}){/if}
</span>
{/if}

{#if credential.kind === CredentialKind.PublicKey || credential.kind === CredentialKind.Sso}
<a
class="ms-2"
href={''}
onclick={e => {
if (credential.kind === CredentialKind.Sso) {
editingSsoCredentialInstance = credential
editingSsoCredential = true
}
if (credential.kind === CredentialKind.PublicKey) {
editingPublicKeyCredentialInstance = credential
editingPublicKeyCredential = true
}
e.preventDefault()
}}>
Change
</a>
{/if}
<a
class="ms-2"
href={''}
onclick={e => {
deleteCredential(credential)
e.preventDefault()
}}>
Delete
</a>
<CredentialUsedStateBadge credential={credential} />
<div class="me-2"></div>
{/if}
{#if credential.kind === 'Totp'}
<Fa fw icon={faMobileScreen} />
<span class="label me-auto">One-time password</span>
{/if}
{#if credential.kind === CredentialKind.Sso}
<Fa fw icon={faIdBadge} />
<span class="label">Single sign-on</span>
<span class="text-muted ms-2 me-auto">
{credential.email}
{#if credential.provider} ({credential.provider}){/if}
</span>
{/if}

{#if credential.kind === CredentialKind.PublicKey || credential.kind === CredentialKind.Sso}
<a
class="ms-2"
href={''}
onclick={e => {
if (credential.kind === CredentialKind.Sso) {
editingSsoCredentialInstance = credential
editingSsoCredential = true
}
if (credential.kind === CredentialKind.PublicKey) {
editingPublicKeyCredentialInstance = credential
editingPublicKeyCredential = true
}
e.preventDefault()
}}>
Change
</a>
{/if}
<a
class="ms-2"
href={''}
onclick={e => {
deleteCredential(credential)
e.preventDefault()
}}>
Delete
</a>
</div>
{/each}
</div>
{/each}
</div>

<h4>Auth policy</h4>
<div class="list-group list-group-flush mb-3">
{#each policyProtocols as protocol}
<div class="list-group-item">
<div>
<strong>{protocol.name}</strong>
<h4>Auth policy</h4>
<div class="list-group list-group-flush mb-3">
{#each policyProtocols as protocol}
<div class="list-group-item">
<div>
<strong>{protocol.name}</strong>
</div>
{#if possibleCredentials[protocol.id]}
{@const _possibleCredentials = assertDefined(possibleCredentials[protocol.id])}
<AuthPolicyEditor
bind:value={credentialPolicy}
existingCredentials={credentials}
possibleCredentials={_possibleCredentials}
protocolId={protocol.id}
/>
{/if}
</div>
{#if possibleCredentials[protocol.id]}
{@const _possibleCredentials = assertDefined(possibleCredentials[protocol.id])}
<AuthPolicyEditor
bind:value={credentialPolicy}
existingCredentials={credentials}
possibleCredentials={_possibleCredentials}
protocolId={protocol.id}
/>
{/if}
{/each}
</div>
{/each}
</div>

{/await}

{#if error}
<Alert color="danger">{error}</Alert>
{/if}
</Loadable>

{#if creatingPassword}
<CreatePasswordModal
Expand Down
Loading

0 comments on commit 935e142

Please sign in to comment.