mirror of
https://github.com/LukeHagar/OpenAPI.gg.git
synced 2025-12-06 04:20:29 +00:00
add authentication tab
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,3 +8,5 @@ node_modules
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
.vercel
|
||||
|
||||
71
src/lib/authTemplates.ts
Normal file
71
src/lib/authTemplates.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import type {
|
||||
ApiKeyAuth,
|
||||
BasicAuth,
|
||||
BearerAuth,
|
||||
CookieAuth,
|
||||
OAuth2Auth,
|
||||
OpenIdConnectAuth
|
||||
} from './types/auth';
|
||||
|
||||
export const basicAuthTemplate: BasicAuth = {
|
||||
identifier: '',
|
||||
type: 'http',
|
||||
scheme: 'basic'
|
||||
};
|
||||
|
||||
export const bearerAuthTemplate: BearerAuth = {
|
||||
identifier: '',
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
bearerFormat: ''
|
||||
};
|
||||
|
||||
export const apiKeyAuthTemplate: ApiKeyAuth = {
|
||||
identifier: '',
|
||||
type: 'apiKey',
|
||||
in: 'header',
|
||||
name: ''
|
||||
};
|
||||
|
||||
export const openIdAuthTemplate: OpenIdConnectAuth = {
|
||||
identifier: '',
|
||||
type: 'openIdConnect',
|
||||
openIdConnectUrl: ''
|
||||
};
|
||||
|
||||
export const oauth2AuthTemplate: OAuth2Auth = {
|
||||
identifier: '',
|
||||
type: 'oauth2',
|
||||
description: '',
|
||||
flows: []
|
||||
};
|
||||
|
||||
export const cookieAuthTemplate: CookieAuth = {
|
||||
identifier: '',
|
||||
type: 'apiKey',
|
||||
in: 'cookie',
|
||||
name: ''
|
||||
};
|
||||
|
||||
export const oauth2FlowTemplates = {
|
||||
authorizationCode: {
|
||||
name: 'authorizationCode',
|
||||
authorizationUrl: '',
|
||||
tokenUrl: '',
|
||||
scopes: []
|
||||
},
|
||||
implicit: {
|
||||
name: 'implicit',
|
||||
authorizationUrl: '',
|
||||
scopes: []
|
||||
},
|
||||
password: {
|
||||
name: 'password',
|
||||
tokenUrl: '',
|
||||
scopes: []
|
||||
},
|
||||
clientCredentials: {
|
||||
name: 'clientCredentials',
|
||||
tokenUrl: ''
|
||||
}
|
||||
};
|
||||
154
src/lib/components/atoms/AuthenticationItem.svelte
Normal file
154
src/lib/components/atoms/AuthenticationItem.svelte
Normal file
@@ -0,0 +1,154 @@
|
||||
<script lang="ts">
|
||||
import { oauth2FlowTemplates } from '$lib/authTemplates';
|
||||
import type { SecuritySchema } from '$lib/types';
|
||||
import OAuthFlow from './OAuthFlow.svelte';
|
||||
|
||||
export let data: SecuritySchema;
|
||||
|
||||
let oauthFlow: string;
|
||||
const addOAuthFlow = () => {
|
||||
if (!data.hasOwnProperty('flows')) {
|
||||
return;
|
||||
}
|
||||
const tempData = data;
|
||||
let newFlow;
|
||||
|
||||
switch (oauthFlow) {
|
||||
case 'authorizationCode':
|
||||
newFlow = oauth2FlowTemplates.authorizationCode;
|
||||
break;
|
||||
case 'implicit':
|
||||
newFlow = oauth2FlowTemplates.implicit;
|
||||
break;
|
||||
case 'password':
|
||||
newFlow = oauth2FlowTemplates.password;
|
||||
break;
|
||||
case 'clientCredentials':
|
||||
newFlow = oauth2FlowTemplates.clientCredentials;
|
||||
break;
|
||||
default:
|
||||
newFlow = undefined;
|
||||
break;
|
||||
}
|
||||
|
||||
if (newFlow) {
|
||||
// @ts-expect-error - this is only called when data.flows exists
|
||||
tempData.flows.push(newFlow);
|
||||
data = tempData;
|
||||
}
|
||||
};
|
||||
|
||||
const removeFlow = (index: number) => {
|
||||
if (!data.hasOwnProperty('flows')) {
|
||||
return;
|
||||
}
|
||||
let tempData = data;
|
||||
// @ts-expect-error - this is only called when data.flows exists
|
||||
tempData.flows.splice(index, 1);
|
||||
data = tempData;
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2"></div>
|
||||
<div class="space-y-4">
|
||||
{#if data.type == 'http' && data.scheme == 'basic'}
|
||||
<h3 class="h3">Basic Auth</h3>
|
||||
<p>Basic authentication is a simple authentication scheme built into the HTTP protocol.</p>
|
||||
<label class="space-y-2">
|
||||
<h4 class="h4">Unique name for the security scheme</h4>
|
||||
<input class="input" type="text" placeholder="basicAuth" bind:value={data.identifier} />
|
||||
</label>
|
||||
{:else if data.type == 'http' && data.scheme == 'bearer'}
|
||||
<h3 class="h3">Bearer Auth</h3>
|
||||
<p>Basic authentication is a simple authentication scheme built into the HTTP protocol.</p>
|
||||
<label class="space-y-2">
|
||||
<h4 class="h4">Unique name for the security scheme</h4>
|
||||
<input class="input" type="text" placeholder="bearerAuth" bind:value={data.identifier} />
|
||||
</label>
|
||||
<label class="space-y-2">
|
||||
<h4 class="h4">Format: arbitrary string for documentation purposes</h4>
|
||||
<input class="input" type="text" placeholder="JWT" bind:value={data.identifier} />
|
||||
</label>
|
||||
{:else if data.type == 'apiKey' && data.in == 'header'}
|
||||
<!-- Cookie API Keys handled by CookieAuth -->
|
||||
<h3 class="h3">API Key</h3>
|
||||
<label class="space-y-2">
|
||||
<h4 class="h4">Unique name for the security scheme</h4>
|
||||
<input class="input" type="text" placeholder="apiKeyAuth" bind:value={data.identifier} />
|
||||
</label>
|
||||
<label class="space-y-2">
|
||||
<h4 class="h4">Location</h4>
|
||||
<select class="input">
|
||||
<option value="header" selected>Header</option>
|
||||
<option value="query">Query</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="space-y-2">
|
||||
<h4 class="h4">Name (required)</h4>
|
||||
<input class="input" type="text" placeholder="X-API-Key" />
|
||||
</label>
|
||||
{:else if data.type == 'openIdConnect'}
|
||||
<h3 class="h3">OpenID</h3>
|
||||
<label class="space-y-2">
|
||||
<h4 class="h4">Unique name for the security scheme</h4>
|
||||
<input class="input" type="text" placeholder="openIdAuth" bind:value={data.identifier} />
|
||||
</label>
|
||||
<label>
|
||||
<h4 class="h4">OpenID Connect URL</h4>
|
||||
<input
|
||||
class="input"
|
||||
type="url"
|
||||
placeholder="https://example.com/.well-known/openid-configuration"
|
||||
/>
|
||||
</label>
|
||||
{:else if data.type == 'oauth2'}
|
||||
<h3 class="h3">OAuth2</h3>
|
||||
<label class="space-y-2">
|
||||
<h4 class="h4">Unique name for the security scheme</h4>
|
||||
<input class="input" type="text" placeholder="oAuth" bind:value={data.identifier} />
|
||||
</label>
|
||||
<label class="space-y-2">
|
||||
<h4 class="h4">Description</h4>
|
||||
<textarea
|
||||
class="input rounded-container-token"
|
||||
placeholder="OAuth2 Authorization Description"
|
||||
/>
|
||||
</label>
|
||||
{#each data.flows as flow, index}
|
||||
<OAuthFlow bind:flow />
|
||||
<span class="flex justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="btn variant-ringed-error hover:variant-filled-error"
|
||||
on:click={() => {
|
||||
removeFlow(index);
|
||||
}}
|
||||
>
|
||||
Remove Flow
|
||||
</button>
|
||||
</span>
|
||||
<hr />
|
||||
{/each}
|
||||
<span class="flex justify-center items-center gap-2 max-w-md mx-auto">
|
||||
<select class="input" bind:value={oauthFlow}>
|
||||
<option value="authorizationCode">Authorization Code</option>
|
||||
<option value="implicit">Implicit</option>
|
||||
<option value="password">Password</option>
|
||||
<option value="clientCredentials">Client Credentials</option>
|
||||
</select>
|
||||
<button type="button" class="btn variant-filled-primary" on:click={addOAuthFlow}>
|
||||
Add OAuth2 Flow
|
||||
</button>
|
||||
</span>
|
||||
{:else if data.type == 'apiKey' && data.in == 'cookie'}
|
||||
<h3 class="h3">Cookie Auth</h3>
|
||||
<label class="space-y-2">
|
||||
<h4 class="h4">Unique name for the security scheme</h4>
|
||||
<input class="input" type="text" placeholder="cookieAuth" bind:value={data.identifier} />
|
||||
</label>
|
||||
<label class="space-y-2">
|
||||
<h4 class="h4">Cookie Name (required)</h4>
|
||||
<input class="input" type="text" placeholder="JSESSIONID" />
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
80
src/lib/components/atoms/OAuthFlow.svelte
Normal file
80
src/lib/components/atoms/OAuthFlow.svelte
Normal file
@@ -0,0 +1,80 @@
|
||||
<script lang="ts">
|
||||
import ScopeList from './ScopeList.svelte';
|
||||
|
||||
import type { Flows } from '$lib/types/auth';
|
||||
|
||||
export let flow: Flows;
|
||||
|
||||
const addScope = () => {
|
||||
flow.scopes = [...flow.scopes, [{ scope: '', description: '' }]];
|
||||
};
|
||||
const removeScope = (index: number) => {
|
||||
flow.scopes = flow.scopes.filter((_, i) => i !== index);
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
{#if flow.name == 'authorizationCode'}
|
||||
<h3 class="h3">Authorization Code</h3>
|
||||
<label class="space-y-2">
|
||||
<h4 class="h4">Authorization URL</h4>
|
||||
<input
|
||||
type="text"
|
||||
name="authorizationURL"
|
||||
class="input"
|
||||
placeholder="https://api.example.com/oauth2/authorize"
|
||||
bind:value={flow.authorizationUrl}
|
||||
/>
|
||||
</label>
|
||||
<label class="space-y-2">
|
||||
<h4 class="h4">Token URL</h4>
|
||||
<input
|
||||
type="text"
|
||||
name="tokenURL"
|
||||
class="input"
|
||||
placeholder="https://api.example.com/oauth2/token"
|
||||
bind:value={flow.tokenUrl}
|
||||
/>
|
||||
</label>
|
||||
<ScopeList bind:flow />
|
||||
{:else if flow.name == 'implicit'}
|
||||
<h3 class="h3">Implicit</h3>
|
||||
<label class="space-y-2">
|
||||
<h4 class="h4">Authorization URL</h4>
|
||||
<input
|
||||
type="text"
|
||||
name="authorizationURL"
|
||||
class="input"
|
||||
placeholder="https://api.example.com/oauth2/authorize"
|
||||
bind:value={flow.authorizationUrl}
|
||||
/>
|
||||
</label>
|
||||
<ScopeList bind:flow />
|
||||
{:else if flow.name == 'password'}
|
||||
<h3 class="h3">Password</h3>
|
||||
<label class="space-y-2">
|
||||
<h4 class="h4">Token URL</h4>
|
||||
<input
|
||||
type="text"
|
||||
name="tokenURL"
|
||||
class="input"
|
||||
placeholder="https://api.example.com/oauth2/token"
|
||||
bind:value={flow.tokenUrl}
|
||||
/>
|
||||
</label>
|
||||
<ScopeList bind:flow />
|
||||
{:else if flow.name == 'clientCredentials'}
|
||||
<h3 class="h3">Client Credentials</h3>
|
||||
<label class="space-y-2">
|
||||
<h4 class="h4">Token URL</h4>
|
||||
<input
|
||||
type="text"
|
||||
name="tokenURL"
|
||||
class="input"
|
||||
placeholder="https://api.example.com/oauth2/token"
|
||||
bind:value={flow.tokenUrl}
|
||||
/>
|
||||
</label>
|
||||
<ScopeList bind:flow />
|
||||
{/if}
|
||||
</div>
|
||||
58
src/lib/components/atoms/ScopeList.svelte
Normal file
58
src/lib/components/atoms/ScopeList.svelte
Normal file
@@ -0,0 +1,58 @@
|
||||
<script lang="ts">
|
||||
import type { Flows } from '$lib/types/auth';
|
||||
|
||||
export let flow: Flows;
|
||||
|
||||
const addScope = () => {
|
||||
flow.scopes = [...flow.scopes, [{ scope: '', description: '' }]];
|
||||
};
|
||||
const removeScope = (index: number) => {
|
||||
flow.scopes = flow.scopes.filter((_, i) => i !== index);
|
||||
};
|
||||
</script>
|
||||
|
||||
<h4 class="h4">Scopes</h4>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
{#each flow.scopes as scope, index}
|
||||
{#each scope as item, i}
|
||||
<tr>
|
||||
<td class="px-4">
|
||||
<input
|
||||
type="text"
|
||||
name="scope{index}-{i}"
|
||||
class="input"
|
||||
placeholder="scope"
|
||||
bind:value={item.scope}
|
||||
/>
|
||||
</td>
|
||||
<td class="px-2">
|
||||
<input
|
||||
type="text"
|
||||
name="description{index}-{i}"
|
||||
class="input"
|
||||
placeholder="a short description of the scope (optional)"
|
||||
bind:value={item.description}
|
||||
/>
|
||||
</td>
|
||||
<td class="max-w-16 px-2">
|
||||
<button
|
||||
type="button"
|
||||
class="btn variant-ringed-error hover:variant-filled-error"
|
||||
on:click={() => {
|
||||
removeScope(index);
|
||||
}}
|
||||
>
|
||||
Remove Scope
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
<span class="flex justify-center">
|
||||
<button type="button" class="btn variant-filled-primary" on:click={addScope}>Add Scope</button
|
||||
>
|
||||
</span>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<input
|
||||
class="input"
|
||||
name="url {id}"
|
||||
placeholder="https://api.example.com"
|
||||
placeholder="https://api.example.com/v1"
|
||||
type="url"
|
||||
bind:value={url}
|
||||
/>
|
||||
|
||||
83
src/lib/components/sections/Authentication.svelte
Normal file
83
src/lib/components/sections/Authentication.svelte
Normal file
@@ -0,0 +1,83 @@
|
||||
<script lang="ts">
|
||||
import { openApiStore } from '$lib';
|
||||
import {
|
||||
apiKeyAuthTemplate,
|
||||
basicAuthTemplate,
|
||||
bearerAuthTemplate,
|
||||
cookieAuthTemplate,
|
||||
oauth2AuthTemplate,
|
||||
openIdAuthTemplate
|
||||
} from '$lib/authTemplates';
|
||||
import AuthenticationItem from '../atoms/AuthenticationItem.svelte';
|
||||
|
||||
let selectedSchema: string;
|
||||
const addSecuritySchema = () => {
|
||||
let tempSchemaList = $openApiStore.securitySchemas;
|
||||
let newSchema;
|
||||
switch (selectedSchema) {
|
||||
case 'basicAuth':
|
||||
newSchema = basicAuthTemplate;
|
||||
break;
|
||||
case 'bearerAuth':
|
||||
newSchema = bearerAuthTemplate;
|
||||
break;
|
||||
case 'ApiKeyAuth':
|
||||
newSchema = apiKeyAuthTemplate;
|
||||
break;
|
||||
case 'openId':
|
||||
newSchema = openIdAuthTemplate;
|
||||
break;
|
||||
case 'oAuthSample':
|
||||
newSchema = oauth2AuthTemplate;
|
||||
break;
|
||||
case 'cookieAuth':
|
||||
newSchema = cookieAuthTemplate;
|
||||
break;
|
||||
default:
|
||||
newSchema = undefined;
|
||||
break;
|
||||
}
|
||||
|
||||
if (newSchema) {
|
||||
tempSchemaList = [...tempSchemaList, newSchema];
|
||||
$openApiStore.securitySchemas = tempSchemaList;
|
||||
}
|
||||
};
|
||||
const removeSecuritySchema = (index: number) => {
|
||||
let tempSchemaList = $openApiStore.securitySchemas;
|
||||
tempSchemaList.splice(index, 1);
|
||||
$openApiStore.securitySchemas = tempSchemaList;
|
||||
};
|
||||
</script>
|
||||
|
||||
<form class="container mx-auto card px-6 py-4 space-y-4">
|
||||
{#each $openApiStore.securitySchemas as schema, index}
|
||||
<AuthenticationItem bind:data={schema} />
|
||||
<span class="flex justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="btn variant-ringed-error hover:variant-filled-error"
|
||||
on:click={() => {
|
||||
removeSecuritySchema(index);
|
||||
}}
|
||||
>
|
||||
Remove Security Schema
|
||||
</button>
|
||||
</span>
|
||||
<hr />
|
||||
{/each}
|
||||
|
||||
<span class="flex justify-center items-center gap-2 max-w-sm mx-auto">
|
||||
<select name="security-schema" bind:value={selectedSchema} class="input">
|
||||
<option value="basicAuth" selected>Basic Auth</option>
|
||||
<option value="bearerAuth">Bearer Auth</option>
|
||||
<option value="ApiKeyAuth">API Key Auth</option>
|
||||
<option value="openId">OpenID</option>
|
||||
<option value="oAuthSample">OAuth2</option>
|
||||
<option value="cookieAuth">Cookie Auth</option>
|
||||
</select>
|
||||
<button type="button" class="btn variant-filled-primary" on:click={addSecuritySchema}>
|
||||
Add Security Schema
|
||||
</button>
|
||||
</span>
|
||||
</form>
|
||||
@@ -1,9 +1,12 @@
|
||||
import type { OpenAPI } from './types';
|
||||
import { persisted } from 'svelte-persisted-store';
|
||||
|
||||
export const openApiStore = persisted<OpenAPI>('openApi', {
|
||||
export const localStoragePrefix = 'openapigen-';
|
||||
|
||||
export const openApiStore = persisted<OpenAPI>(`${localStoragePrefix}openApi`, {
|
||||
title: '',
|
||||
version: '',
|
||||
description: '',
|
||||
servers: []
|
||||
servers: [],
|
||||
securitySchemas: []
|
||||
});
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
export interface OpenAPI extends Info {
|
||||
servers: Server[];
|
||||
}
|
||||
|
||||
export interface Info {
|
||||
title: string;
|
||||
version: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface Server {
|
||||
url: string;
|
||||
description?: string;
|
||||
}
|
||||
81
src/lib/types/auth.ts
Normal file
81
src/lib/types/auth.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
// Basic Auth
|
||||
export interface BasicAuth extends Auth {
|
||||
type: 'http';
|
||||
scheme: 'basic';
|
||||
}
|
||||
|
||||
// Bearer Token
|
||||
export interface BearerAuth extends Auth {
|
||||
type: 'http';
|
||||
scheme: 'bearer';
|
||||
bearerFormat?: string; // arbitrary value for documentation purposes
|
||||
}
|
||||
|
||||
// API Key
|
||||
export interface ApiKeyAuth extends Auth {
|
||||
type: 'apiKey';
|
||||
in: 'header' | 'query' | 'cookie';
|
||||
name: string;
|
||||
}
|
||||
|
||||
// OAuth2
|
||||
export interface OAuth2Auth extends Auth {
|
||||
type: 'oauth2';
|
||||
description: string;
|
||||
flows: Flows[];
|
||||
}
|
||||
|
||||
type Scope = [
|
||||
{
|
||||
scope: string;
|
||||
description?: string;
|
||||
}
|
||||
];
|
||||
|
||||
export type Flows =
|
||||
| OAuth2AuchorizationCodeFlow
|
||||
| OAuth2ImplicitFlow
|
||||
| OAuth2PasswordFlow
|
||||
| OAuth2ClientCredentialsFlow;
|
||||
|
||||
interface OAuth2AuchorizationCodeFlow {
|
||||
name: 'authorizationCode';
|
||||
authorizationUrl: string;
|
||||
tokenUrl: string;
|
||||
scopes: Scope[];
|
||||
}
|
||||
|
||||
interface OAuth2ImplicitFlow {
|
||||
name: 'implicit';
|
||||
authorizationUrl: string;
|
||||
scopes: Scope[];
|
||||
}
|
||||
|
||||
interface OAuth2PasswordFlow {
|
||||
name: 'password';
|
||||
tokenUrl: string;
|
||||
scopes: Scope[];
|
||||
}
|
||||
|
||||
interface OAuth2ClientCredentialsFlow {
|
||||
name: 'clientCredentials';
|
||||
tokenUrl: string;
|
||||
scopes: Scope[];
|
||||
}
|
||||
|
||||
// OpenID Connect
|
||||
export interface OpenIdConnectAuth extends Auth {
|
||||
type: 'openIdConnect';
|
||||
openIdConnectUrl: string;
|
||||
}
|
||||
|
||||
// Cookie Auth
|
||||
export interface CookieAuth extends Auth {
|
||||
type: 'apiKey';
|
||||
in: 'cookie';
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Auth {
|
||||
identifier: string; // a unique name for the auth-configuration
|
||||
}
|
||||
32
src/lib/types/index.ts
Normal file
32
src/lib/types/index.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type {
|
||||
ApiKeyAuth,
|
||||
BasicAuth,
|
||||
BearerAuth,
|
||||
CookieAuth,
|
||||
OAuth2Auth,
|
||||
OpenIdConnectAuth
|
||||
} from './auth';
|
||||
|
||||
export interface OpenAPI extends Info {
|
||||
servers: Server[];
|
||||
securitySchemas: SecuritySchema[];
|
||||
}
|
||||
|
||||
export interface Info {
|
||||
title: string;
|
||||
version: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface Server {
|
||||
url: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export type SecuritySchema =
|
||||
| BasicAuth
|
||||
| BearerAuth
|
||||
| ApiKeyAuth
|
||||
| OAuth2Auth
|
||||
| OpenIdConnectAuth
|
||||
| CookieAuth;
|
||||
@@ -3,6 +3,7 @@
|
||||
import Info from '$lib/components/sections/Info.svelte';
|
||||
import { TabGroup, Tab } from '@skeletonlabs/skeleton';
|
||||
import { persisted } from 'svelte-persisted-store';
|
||||
import Authentication from '$lib/components/sections/Authentication.svelte';
|
||||
|
||||
// let tabSet: number = 0;
|
||||
const tabSet = persisted<number>('tabSet', 0);
|
||||
@@ -13,7 +14,7 @@
|
||||
<h4 class="h4">Info</h4>
|
||||
</Tab>
|
||||
<Tab bind:group={$tabSet} name="authentication" value={1}>
|
||||
<h4 class="h4">Authentication</h4>
|
||||
<h4 class="h4">Authentication Schemas</h4>
|
||||
</Tab>
|
||||
<Tab bind:group={$tabSet} name="servers" value={2}>
|
||||
<h4 class="h4">Servers</h4>
|
||||
@@ -27,17 +28,13 @@
|
||||
<svelte:fragment slot="panel">
|
||||
{#if $tabSet === 0}
|
||||
<Info></Info>
|
||||
{/if}
|
||||
{#if $tabSet === 1}
|
||||
<p>Authentication</p>
|
||||
{/if}
|
||||
{#if $tabSet === 2}
|
||||
{:else if $tabSet === 1}
|
||||
<Authentication />
|
||||
{:else if $tabSet === 2}
|
||||
<Servers />
|
||||
{/if}
|
||||
{#if $tabSet === 3}
|
||||
{:else if $tabSet === 3}
|
||||
<p>Paths</p>
|
||||
{/if}
|
||||
{#if $tabSet === 4}
|
||||
{:else if $tabSet === 4}
|
||||
<p>Components</p>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
Reference in New Issue
Block a user