Merge pull request #15740 from Budibase/BUDI-9127/oauth2-settings
OAuth2 configuration frontend
This commit is contained in:
commit
26f7677e51
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
export let title
|
||||
<script lang="ts">
|
||||
export let title = ""
|
||||
</script>
|
||||
|
||||
<div class="side-nav">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
export let text
|
||||
export let url
|
||||
<script lang="ts">
|
||||
export let text = ""
|
||||
export let url = ""
|
||||
export let active = false
|
||||
export let disabled = false
|
||||
</script>
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { Content, SideNav, SideNavItem } from "@/components/portal/page"
|
||||
import { Page, Layout, AbsTooltip, TooltipPosition } from "@budibase/bbui"
|
||||
import { url, isActive } from "@roxi/routify"
|
||||
import DeleteModal from "@/components/deploy/DeleteModal.svelte"
|
||||
import { isOnlyUser, appStore } from "@/stores/builder"
|
||||
import { featureFlag } from "@/helpers"
|
||||
import { FeatureFlag } from "@budibase/types"
|
||||
|
||||
let deleteModal
|
||||
let deleteModal: DeleteModal
|
||||
|
||||
$: oauth2Enabled = featureFlag.isEnabled(FeatureFlag.OAUTH2_CONFIG)
|
||||
</script>
|
||||
|
||||
<!-- routify:options index=4 -->
|
||||
|
@ -44,11 +48,18 @@
|
|||
url={$url("./version")}
|
||||
active={$isActive("./version")}
|
||||
/>
|
||||
{#if oauth2Enabled}
|
||||
<SideNavItem
|
||||
text="OAuth2"
|
||||
url={$url("./oauth2")}
|
||||
active={$isActive("./oauth2")}
|
||||
/>
|
||||
{/if}
|
||||
<div class="delete-action">
|
||||
<AbsTooltip
|
||||
position={TooltipPosition.Bottom}
|
||||
text={$isOnlyUser
|
||||
? null
|
||||
? undefined
|
||||
: "Unavailable - another user is editing this app"}
|
||||
>
|
||||
<SideNavItem
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
<script lang="ts">
|
||||
import { oauth2 } from "@/stores/builder"
|
||||
import type { CreateOAuth2Config } from "@/types"
|
||||
import {
|
||||
Body,
|
||||
Button,
|
||||
Divider,
|
||||
Heading,
|
||||
Input,
|
||||
Link,
|
||||
Modal,
|
||||
ModalContent,
|
||||
} from "@budibase/bbui"
|
||||
|
||||
let modal: Modal
|
||||
|
||||
function openModal() {
|
||||
modal.show()
|
||||
}
|
||||
|
||||
let config: Partial<CreateOAuth2Config> = {}
|
||||
|
||||
$: saveOAuth2Config = async () => {
|
||||
await oauth2.create(config as any) // TODO
|
||||
}
|
||||
</script>
|
||||
|
||||
<Button cta size="M" on:click={openModal}>Add OAuth2</Button>
|
||||
<Modal bind:this={modal}>
|
||||
<ModalContent onConfirm={saveOAuth2Config} size="M">
|
||||
<Heading size="S">Create new OAuth2 connection</Heading>
|
||||
<Body size="S">
|
||||
The OAuth 2 authentication below uses the Client Credentials (machine to
|
||||
machine) grant type.
|
||||
</Body>
|
||||
<Divider noGrid noMargin />
|
||||
<Input label="Name*" placeholder="Type here..." bind:value={config.name} />
|
||||
<Input
|
||||
label="Service URL*"
|
||||
placeholder="E.g. www.google.com"
|
||||
bind:value={config.url}
|
||||
/>
|
||||
<div class="field-info">
|
||||
<Body size="XS" color="var(--spectrum-global-color-gray-700)">
|
||||
The location where the flow sends the credentials. This field should be
|
||||
a full URL.
|
||||
</Body>
|
||||
</div>
|
||||
<Input
|
||||
label="Client ID*"
|
||||
placeholder="Type here..."
|
||||
bind:value={config.clientId}
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
label="Client secret*"
|
||||
placeholder="Type here..."
|
||||
bind:value={config.clientSecret}
|
||||
/>
|
||||
<Body size="S"
|
||||
>To learn how to configure OAuth2, our documentation <Link
|
||||
href="TODO"
|
||||
target="_blank"
|
||||
size="M">our documentation.</Link
|
||||
></Body
|
||||
>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.field-info {
|
||||
margin-top: calc(var(--spacing-xl) * -1 + var(--spacing-s));
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts">
|
||||
import { ActionMenu, Icon, MenuItem } from "@budibase/bbui"
|
||||
|
||||
function onEdit() {
|
||||
// TODO
|
||||
}
|
||||
function onDelete() {
|
||||
// TODO
|
||||
}
|
||||
</script>
|
||||
|
||||
<ActionMenu align="right">
|
||||
<div slot="control" class="control icon">
|
||||
<Icon size="S" hoverable name="MoreSmallList" />
|
||||
</div>
|
||||
<MenuItem on:click={onEdit} icon="Edit">Edit</MenuItem>
|
||||
<MenuItem on:click={onDelete} icon="Delete">Delete</MenuItem>
|
||||
</ActionMenu>
|
|
@ -0,0 +1,61 @@
|
|||
<script lang="ts">
|
||||
import { Layout, Heading, Body, Divider, Table } from "@budibase/bbui"
|
||||
import { oauth2 } from "@/stores/builder"
|
||||
import AddButton from "./AddButton.svelte"
|
||||
import { onMount } from "svelte"
|
||||
import MoreMenuRenderer from "./MoreMenuRenderer.svelte"
|
||||
|
||||
const schema = {
|
||||
name: {
|
||||
sortable: false,
|
||||
},
|
||||
lastUsed: {
|
||||
displayName: "Last used",
|
||||
sortable: false,
|
||||
},
|
||||
more: {
|
||||
width: "auto",
|
||||
displayName: "",
|
||||
},
|
||||
}
|
||||
const customRenderers = [{ column: "more", component: MoreMenuRenderer }]
|
||||
|
||||
onMount(() => {
|
||||
oauth2.fetch()
|
||||
})
|
||||
|
||||
$: configs = $oauth2.configs.map(c => ({
|
||||
lastUsed: "Never used",
|
||||
...c,
|
||||
}))
|
||||
</script>
|
||||
|
||||
<Layout noPadding>
|
||||
<Layout gap="XS" noPadding>
|
||||
<div class="header">
|
||||
<Heading>OAuth2</Heading>
|
||||
<AddButton />
|
||||
</div>
|
||||
<Body
|
||||
>Manage and configure OAuth 2.0 Client Credentials for secure API access.</Body
|
||||
>
|
||||
</Layout>
|
||||
<Divider />
|
||||
|
||||
<Table
|
||||
data={configs}
|
||||
loading={$oauth2.loading}
|
||||
{schema}
|
||||
{customRenderers}
|
||||
allowEditRows={false}
|
||||
allowEditColumns={false}
|
||||
allowClickRows={false}
|
||||
/>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
|
@ -37,6 +37,9 @@ import { flags } from "./flags"
|
|||
import { rowActions } from "./rowActions"
|
||||
import componentTreeNodesStore from "./componentTreeNodes"
|
||||
import { appPublished } from "./published"
|
||||
import { oauth2 } from "./oauth2"
|
||||
|
||||
import { FetchAppPackageResponse } from "@budibase/types"
|
||||
|
||||
export {
|
||||
componentTreeNodesStore,
|
||||
|
@ -77,6 +80,7 @@ export {
|
|||
screenComponentsList,
|
||||
screenComponentErrors,
|
||||
screenComponentErrorList,
|
||||
oauth2,
|
||||
}
|
||||
|
||||
export const reset = () => {
|
||||
|
@ -106,7 +110,7 @@ const resetBuilderHistory = () => {
|
|||
automationHistoryStore.reset()
|
||||
}
|
||||
|
||||
export const initialise = async pkg => {
|
||||
export const initialise = async (pkg: FetchAppPackageResponse) => {
|
||||
const { application } = pkg
|
||||
// must be first operation to make sure subsequent requests have correct app ID
|
||||
appStore.syncAppPackage(pkg)
|
|
@ -15,7 +15,7 @@ export class NavigationStore extends BudiStore<AppNavigation> {
|
|||
super(INITIAL_NAVIGATION_STATE)
|
||||
}
|
||||
|
||||
syncAppNavigation(nav: AppNavigation) {
|
||||
syncAppNavigation(nav?: AppNavigation) {
|
||||
this.update(state => ({
|
||||
...state,
|
||||
...nav,
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import { API } from "@/api"
|
||||
import { BudiStore } from "@/stores/BudiStore"
|
||||
import { CreateOAuth2Config } from "@/types"
|
||||
|
||||
interface Config {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
interface OAuth2StoreState {
|
||||
configs: Config[]
|
||||
loading: boolean
|
||||
error?: string
|
||||
}
|
||||
|
||||
export class OAuth2Store extends BudiStore<OAuth2StoreState> {
|
||||
constructor() {
|
||||
super({
|
||||
configs: [],
|
||||
loading: false,
|
||||
})
|
||||
}
|
||||
|
||||
async fetch() {
|
||||
this.store.update(store => ({
|
||||
...store,
|
||||
loading: true,
|
||||
}))
|
||||
try {
|
||||
const configs = await API.oauth2.fetch()
|
||||
this.store.update(store => ({
|
||||
...store,
|
||||
configs: configs.map(c => ({ id: c.id, name: c.name })),
|
||||
loading: false,
|
||||
}))
|
||||
} catch (e: any) {
|
||||
this.store.update(store => ({
|
||||
...store,
|
||||
loading: false,
|
||||
error: e.message,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async create(config: CreateOAuth2Config) {
|
||||
await API.oauth2.create(config)
|
||||
await this.fetch()
|
||||
}
|
||||
}
|
||||
|
||||
export const oauth2 = new OAuth2Store()
|
|
@ -1 +1,2 @@
|
|||
export * from "./bindings"
|
||||
export * from "./oauth2"
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import { CreateOAuth2ConfigRequest } from "@budibase/types"
|
||||
|
||||
export interface CreateOAuth2Config extends CreateOAuth2ConfigRequest {}
|
|
@ -45,6 +45,7 @@ import { buildAuditLogEndpoints } from "./auditLogs"
|
|||
import { buildLogsEndpoints } from "./logs"
|
||||
import { buildMigrationEndpoints } from "./migrations"
|
||||
import { buildRowActionEndpoints } from "./rowActions"
|
||||
import { buildOAuth2Endpoints } from "./oauth2"
|
||||
|
||||
export type { APIClient } from "./types"
|
||||
|
||||
|
@ -290,5 +291,6 @@ export const createAPIClient = (config: APIClientConfig = {}): APIClient => {
|
|||
...buildMigrationEndpoints(API),
|
||||
viewV2: buildViewV2Endpoints(API),
|
||||
rowActions: buildRowActionEndpoints(API),
|
||||
oauth2: buildOAuth2Endpoints(API),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import {
|
||||
FetchOAuth2ConfigsResponse,
|
||||
CreateOAuth2ConfigResponse,
|
||||
OAuth2ConfigResponse,
|
||||
CreateOAuth2ConfigRequest,
|
||||
} from "@budibase/types"
|
||||
import { BaseAPIClient } from "./types"
|
||||
|
||||
export interface OAuth2Endpoints {
|
||||
fetch: () => Promise<OAuth2ConfigResponse[]>
|
||||
create: (
|
||||
config: CreateOAuth2ConfigRequest
|
||||
) => Promise<CreateOAuth2ConfigResponse>
|
||||
}
|
||||
|
||||
export const buildOAuth2Endpoints = (API: BaseAPIClient): OAuth2Endpoints => ({
|
||||
/**
|
||||
* Gets all OAuth2 configurations for the app.
|
||||
* @param tableId the ID of the table
|
||||
*/
|
||||
fetch: async () => {
|
||||
return (
|
||||
await API.get<FetchOAuth2ConfigsResponse>({
|
||||
url: `/api/oauth2`,
|
||||
})
|
||||
).configs
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a OAuth2 configuration.
|
||||
* @param name the name of the row action
|
||||
* @param tableId the ID of the table
|
||||
*/
|
||||
create: async config => {
|
||||
return await API.post<
|
||||
CreateOAuth2ConfigRequest,
|
||||
CreateOAuth2ConfigResponse
|
||||
>({
|
||||
url: `/api/oauth2`,
|
||||
body: {
|
||||
...config,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
|
@ -16,6 +16,7 @@ import { LayoutEndpoints } from "./layouts"
|
|||
import { LicensingEndpoints } from "./licensing"
|
||||
import { LogEndpoints } from "./logs"
|
||||
import { MigrationEndpoints } from "./migrations"
|
||||
import { OAuth2Endpoints } from "./oauth2"
|
||||
import { OtherEndpoints } from "./other"
|
||||
import { PermissionEndpoints } from "./permissions"
|
||||
import { PluginEndpoins } from "./plugins"
|
||||
|
@ -132,4 +133,8 @@ export type APIClient = BaseAPIClient &
|
|||
TableEndpoints &
|
||||
TemplateEndpoints &
|
||||
UserEndpoints &
|
||||
ViewEndpoints & { rowActions: RowActionEndpoints; viewV2: ViewV2Endpoints }
|
||||
ViewEndpoints & {
|
||||
rowActions: RowActionEndpoints
|
||||
viewV2: ViewV2Endpoints
|
||||
oauth2: OAuth2Endpoints
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
interface OAuth2ConfigResponse {
|
||||
export interface OAuth2ConfigResponse {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
export enum FeatureFlag {
|
||||
USE_ZOD_VALIDATOR = "USE_ZOD_VALIDATOR",
|
||||
AI_JS_GENERATION = "AI_JS_GENERATION",
|
||||
OAUTH2_CONFIG = "OAUTH2_CONFIG",
|
||||
|
||||
// Account-portal
|
||||
DIRECT_LOGIN_TO_ACCOUNT_PORTAL = "DIRECT_LOGIN_TO_ACCOUNT_PORTAL",
|
||||
|
||||
AI_JS_GENERATION = "AI_JS_GENERATION",
|
||||
}
|
||||
|
||||
export const FeatureFlagDefaults: Record<FeatureFlag, boolean> = {
|
||||
[FeatureFlag.USE_ZOD_VALIDATOR]: false,
|
||||
[FeatureFlag.AI_JS_GENERATION]: false,
|
||||
[FeatureFlag.OAUTH2_CONFIG]: false,
|
||||
|
||||
// Account-portal
|
||||
[FeatureFlag.DIRECT_LOGIN_TO_ACCOUNT_PORTAL]: false,
|
||||
|
|
Loading…
Reference in New Issue