mirror of
https://github.com/LukeHagar/ui-development-kit.git
synced 2025-12-06 12:57:44 +00:00
Merge pull request #13 from sailpoint-oss/added-report-form
Added pagination report and example source description update form
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="/logo.ico" />
|
<link rel="icon" href="/logo.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>IdentityNow Admin Console</title>
|
<title>IdentityNow Starter Application</title>
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body data-theme="wintry">
|
<body data-theme="wintry">
|
||||||
|
|||||||
@@ -16,23 +16,33 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|||||||
event.locals.hasIdnSession = hasIdnSession;
|
event.locals.hasIdnSession = hasIdnSession;
|
||||||
if (hasSession) {
|
if (hasSession) {
|
||||||
event.locals.session = getSession(event.cookies);
|
event.locals.session = getSession(event.cookies);
|
||||||
if(hasIdnSession){
|
if (hasIdnSession) {
|
||||||
event.locals.idnSession = await getToken(event.cookies);
|
const session = await getToken(event.cookies);
|
||||||
const lastToken = lastCheckedToken(event.cookies);
|
if (!session) {
|
||||||
const tokenDetails = getTokenDetails(event.cookies);
|
event.locals.hasIdnSession = false;
|
||||||
if (tokenDetails && lastToken != '' && lastToken === event.locals.idnSession.access_token ) {
|
event.locals.idnSession = undefined;
|
||||||
event.locals.tokenDetails = tokenDetails
|
} else {
|
||||||
} else {
|
event.locals.idnSession = session;
|
||||||
event.locals.tokenDetails = await checkToken(
|
const lastToken = lastCheckedToken(event.cookies);
|
||||||
event.locals.session.baseUrl,
|
const tokenDetails = getTokenDetails(event.cookies);
|
||||||
event.locals.idnSession.access_token
|
if (tokenDetails && lastToken != '' && lastToken === event.locals.idnSession.access_token) {
|
||||||
);
|
event.locals.tokenDetails = tokenDetails;
|
||||||
event.cookies.set('tokenDetails', JSON.stringify(event.locals.tokenDetails), {
|
} else {
|
||||||
path: '/',
|
const tempTokenDetails = await checkToken(
|
||||||
httpOnly: false,
|
event.locals.session.baseUrl,
|
||||||
secure: false
|
event.locals.idnSession.access_token
|
||||||
});
|
);
|
||||||
}}
|
if (tempTokenDetails) {
|
||||||
|
event.locals.tokenDetails = tempTokenDetails;
|
||||||
|
event.cookies.set('tokenDetails', JSON.stringify(event.locals.tokenDetails), {
|
||||||
|
path: '/',
|
||||||
|
httpOnly: false,
|
||||||
|
secure: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.url.pathname.startsWith('/home') || event.url.pathname.startsWith('/api')) {
|
if (event.url.pathname.startsWith('/home') || event.url.pathname.startsWith('/api')) {
|
||||||
|
|||||||
@@ -6,29 +6,35 @@
|
|||||||
export let onPageChange: (e: CustomEvent<number>) => void;
|
export let onPageChange: (e: CustomEvent<number>) => void;
|
||||||
export let onAmountChange: (e: CustomEvent<number>) => void;
|
export let onAmountChange: (e: CustomEvent<number>) => void;
|
||||||
export let onGo: (e: KeyboardEvent | MouseEvent) => void;
|
export let onGo: (e: KeyboardEvent | MouseEvent) => void;
|
||||||
export let filters: string = '';
|
export let filters: string | undefined = undefined;
|
||||||
export let sorters: string = '';
|
export let sorters: string | undefined = undefined;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class=" p-4 flex flex-row flex-wrap justify-between gap-4">
|
<div class=" p-4 flex flex-row flex-wrap justify-between gap-4">
|
||||||
<div class="flex flex-row gap-1">
|
<div class="flex flex-row gap-1">
|
||||||
<input
|
{#if filters !== undefined}
|
||||||
on:keydown={onGo}
|
<input
|
||||||
bind:value={filters}
|
on:keydown={onGo}
|
||||||
class="input"
|
bind:value={filters}
|
||||||
title="Filter"
|
class="input"
|
||||||
type="text"
|
title="Filter"
|
||||||
placeholder="Filter"
|
type="text"
|
||||||
/>
|
placeholder="Filter"
|
||||||
<input
|
/>
|
||||||
on:keydown={onGo}
|
{/if}
|
||||||
bind:value={sorters}
|
{#if sorters !== undefined}
|
||||||
class="input"
|
<input
|
||||||
title="Sorter"
|
on:keydown={onGo}
|
||||||
type="text"
|
bind:value={sorters}
|
||||||
placeholder="Sorter"
|
class="input"
|
||||||
/>
|
title="Sorter"
|
||||||
<button on:click={onGo} class="btn variant-filled-primary !text-white"> Go </button>
|
type="text"
|
||||||
|
placeholder="Sorter"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{#if filters !== undefined || sorters !== undefined}
|
||||||
|
<button on:click={onGo} class="btn variant-filled-primary !text-white"> Go </button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<p class="my-auto">Total Count: {totalCount}</p>
|
<p class="my-auto">Total Count: {totalCount}</p>
|
||||||
<Paginator
|
<Paginator
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export function formatDate(date: string | null | undefined) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getLimit(url: URL) {
|
export function getLimit(url: URL) {
|
||||||
return url.searchParams.get('limit') || '250';
|
return url.searchParams.get('limit') || '5';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFilters(url: URL) {
|
export function getFilters(url: URL) {
|
||||||
@@ -71,10 +71,10 @@ export function createOnGo(params: PaginationParams, path: string) {
|
|||||||
if (e.type !== 'click' && (e as KeyboardEvent).key !== 'Enter') return;
|
if (e.type !== 'click' && (e as KeyboardEvent).key !== 'Enter') return;
|
||||||
|
|
||||||
const urlParams = new URLSearchParams();
|
const urlParams = new URLSearchParams();
|
||||||
urlParams.set('page', params.page);
|
if (params.page != '') urlParams.set('page', params.page);
|
||||||
urlParams.set('limit', params.limit);
|
if (params.limit != '') urlParams.set('limit', params.limit);
|
||||||
urlParams.set('sorters', params.sorters);
|
if (params.sorters != '') urlParams.set('sorters', params.sorters);
|
||||||
urlParams.set('filters', params.filters);
|
if (params.filters != '') urlParams.set('filters', params.filters);
|
||||||
|
|
||||||
console.log(`${path}?${urlParams.toString()}`);
|
console.log(`${path}?${urlParams.toString()}`);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import HomeSvg from '$lib/Components/SVGs/HomeSVG.svelte';
|
import HomeSvg from '$lib/Components/SVGs/HomeSVG.svelte';
|
||||||
import MessagesSvg from '$lib/Components/SVGs/MessagesSVG.svelte';
|
|
||||||
import ReportsSvg from '$lib/Components/SVGs/ReportsSVG.svelte';
|
import ReportsSvg from '$lib/Components/SVGs/ReportsSVG.svelte';
|
||||||
|
|
||||||
export const navigation = [
|
export const navigation = [
|
||||||
@@ -13,10 +12,20 @@ export const navigation = [
|
|||||||
icon: HomeSvg
|
icon: HomeSvg
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: '/home/Example Pages',
|
url: '/home/example-pages',
|
||||||
name: 'Reports',
|
name: 'Example Pages',
|
||||||
description: 'a list of example pages showcasing how to implement the IdentityNow SDK.',
|
description: 'a list of example pages showcasing how to implement the IdentityNow SDK.',
|
||||||
icon: ReportsSvg
|
icon: ReportsSvg
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/home/example-form',
|
||||||
|
name: 'Example Form',
|
||||||
|
description: 'A form example using the IdentityNow SDK.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/home/form-integration',
|
||||||
|
name: 'SailPoint Form Integration',
|
||||||
|
description: 'A form example using the IdentityNow SDK.'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export function setTokenDetails(cookies: Cookies, tokenDetails: TokenDetails) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkToken(apiUrl: string, token: string): Promise<TokenDetails> {
|
export async function checkToken(apiUrl: string, token: string): Promise<TokenDetails | undefined> {
|
||||||
const body = 'token=' + token;
|
const body = 'token=' + token;
|
||||||
const url = `${apiUrl}/oauth/check_token/`;
|
const url = `${apiUrl}/oauth/check_token/`;
|
||||||
const response = await axios.post(url, body).catch(function (err) {
|
const response = await axios.post(url, body).catch(function (err) {
|
||||||
@@ -85,15 +85,19 @@ export async function checkToken(apiUrl: string, token: string): Promise<TokenDe
|
|||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
// if (response) {
|
if (!response) {
|
||||||
// console.log(response.data);
|
return undefined;
|
||||||
// }
|
}
|
||||||
const tokenDetails = response!.data;
|
|
||||||
|
const tokenDetails = response.data;
|
||||||
|
|
||||||
return tokenDetails;
|
return tokenDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function refreshToken(apiUrl: string, refreshToken: string): Promise<IdnSession> {
|
export async function refreshToken(
|
||||||
|
apiUrl: string,
|
||||||
|
refreshToken: string
|
||||||
|
): Promise<IdnSession | undefined> {
|
||||||
const url = `${apiUrl}/oauth/token?grant_type=refresh_token&client_id=sailpoint-cli&refresh_token=${refreshToken}`;
|
const url = `${apiUrl}/oauth/token?grant_type=refresh_token&client_id=sailpoint-cli&refresh_token=${refreshToken}`;
|
||||||
const response = await axios.post(url).catch(function (err) {
|
const response = await axios.post(url).catch(function (err) {
|
||||||
if (err.response) {
|
if (err.response) {
|
||||||
@@ -104,10 +108,12 @@ export async function refreshToken(apiUrl: string, refreshToken: string): Promis
|
|||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
// if (response) {
|
|
||||||
// console.log(response.data)
|
if (!response) {
|
||||||
// }
|
return undefined;
|
||||||
const idnSession: IdnSession = response!.data as IdnSession;
|
}
|
||||||
|
|
||||||
|
const idnSession: IdnSession = response.data as IdnSession;
|
||||||
return idnSession;
|
return idnSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +153,7 @@ export function getSession(cookies: Cookies): Session {
|
|||||||
return JSON.parse(sessionString) as Session;
|
return JSON.parse(sessionString) as Session;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getToken(cookies: Cookies): Promise<IdnSession> {
|
export async function getToken(cookies: Cookies): Promise<IdnSession | undefined> {
|
||||||
const sessionString = cookies.get('session');
|
const sessionString = cookies.get('session');
|
||||||
const idnSessionString = cookies.get('idnSession');
|
const idnSessionString = cookies.get('idnSession');
|
||||||
|
|
||||||
@@ -171,12 +177,17 @@ export async function getToken(cookies: Cookies): Promise<IdnSession> {
|
|||||||
if (isJwtExpired(idnSession.access_token)) {
|
if (isJwtExpired(idnSession.access_token)) {
|
||||||
console.log('Refreshing IdnSession token...');
|
console.log('Refreshing IdnSession token...');
|
||||||
const newSession = await refreshToken(session.baseUrl, idnSession.refresh_token);
|
const newSession = await refreshToken(session.baseUrl, idnSession.refresh_token);
|
||||||
cookies.set('idnSession', JSON.stringify(newSession), {
|
if (newSession) {
|
||||||
path: '/',
|
cookies.set('idnSession', JSON.stringify(newSession), {
|
||||||
httpOnly: false,
|
path: '/',
|
||||||
secure: false
|
httpOnly: false,
|
||||||
});
|
secure: false
|
||||||
return Promise.resolve(newSession);
|
});
|
||||||
|
return Promise.resolve(newSession);
|
||||||
|
} else {
|
||||||
|
console.log('IdnSession token is expired');
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('IdnSession token is good');
|
console.log('IdnSession token is good');
|
||||||
return Promise.resolve(idnSession);
|
return Promise.resolve(idnSession);
|
||||||
|
|||||||
@@ -177,7 +177,7 @@
|
|||||||
<!-- <svelte:fragment slot="sidebarRight">Sidebar Right</svelte:fragment> -->
|
<!-- <svelte:fragment slot="sidebarRight">Sidebar Right</svelte:fragment> -->
|
||||||
<!-- <svelte:fragment slot="pageHeader">Page Header</svelte:fragment> -->
|
<!-- <svelte:fragment slot="pageHeader">Page Header</svelte:fragment> -->
|
||||||
<!-- Router Slot -->
|
<!-- Router Slot -->
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col h-full">
|
||||||
{#if crumbs.length > 0}
|
{#if crumbs.length > 0}
|
||||||
<div class="pl-2 pt-2 pr-2">
|
<div class="pl-2 pt-2 pr-2">
|
||||||
<ol class="breadcrumb card p-2">
|
<ol class="breadcrumb card p-2">
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// src/routes/api/post.ts
|
||||||
|
|
||||||
|
import { createConfiguration } from '$lib/sailpoint/sdk.js';
|
||||||
|
import { error, json } from '@sveltejs/kit';
|
||||||
|
import {
|
||||||
|
CustomFormsBetaApi,
|
||||||
|
type CustomFormsBetaApiCreateFormInstanceRequest
|
||||||
|
} from 'sailpoint-api-client';
|
||||||
|
|
||||||
|
export const POST = async ({ request, locals }) => {
|
||||||
|
const config = createConfiguration(locals.session!.baseUrl, locals.idnSession!.access_token);
|
||||||
|
const api = new CustomFormsBetaApi(config);
|
||||||
|
|
||||||
|
const { formDefinitionId, formInput } = await request.json();
|
||||||
|
|
||||||
|
if (!formDefinitionId || !formInput) {
|
||||||
|
error(400, 'formDefinitionId and formInput are required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const expireDate = new Date();
|
||||||
|
expireDate.setDate(expireDate.getDate() + 1);
|
||||||
|
|
||||||
|
const formInstance: CustomFormsBetaApiCreateFormInstanceRequest = {
|
||||||
|
body: {
|
||||||
|
createdBy: {
|
||||||
|
id: 'BOOYAH',
|
||||||
|
type: 'SOURCE'
|
||||||
|
},
|
||||||
|
expire: expireDate.toISOString(),
|
||||||
|
formDefinitionId: formDefinitionId,
|
||||||
|
formInput: {
|
||||||
|
...formInput
|
||||||
|
},
|
||||||
|
recipients: [
|
||||||
|
{
|
||||||
|
id: locals.idnSession?.identity_id,
|
||||||
|
type: 'IDENTITY'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
standAloneForm: true,
|
||||||
|
state: 'ASSIGNED',
|
||||||
|
ttl: 1571827560
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formInstanceResp = await api.createFormInstance(formInstance);
|
||||||
|
|
||||||
|
// Process the request body and perform any necessary operations
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Return the response
|
||||||
|
return json({ formDefinitionId, formInput, formInstanceResp: formInstanceResp.data });
|
||||||
|
};
|
||||||
@@ -1,18 +1,11 @@
|
|||||||
<script lang="ts">
|
<div class="grid place-content-center h-full">
|
||||||
import ResourceLinksCard from '$lib/Components/HomepageCards/ResourceLinksCard.svelte';
|
<p class="text-center px-60">
|
||||||
import StatusCard from '$lib/Components/HomepageCards/StatusCard.svelte';
|
This starter application is meant to be an example of how you can build on top of the
|
||||||
import SupportLinksCard from '$lib/Components/HomepageCards/SupportLinksCard.svelte';
|
IdentityNow UI development kit to build your own applications and tools for IdentityNow
|
||||||
import TenantLinksCard from '$lib/Components/HomepageCards/TenantLinksCard.svelte';
|
|
||||||
|
|
||||||
export let data;
|
<br />
|
||||||
console.log(data);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-2 grow">
|
On the left hand side you will see some example pages showcasing some different kinds of pages
|
||||||
<div class="flex flex-row flex-wrap gap-2 grow">
|
you could build. Each page is meant to be a starting point for you to build your own pages.
|
||||||
<StatusCard />
|
</p>
|
||||||
<TenantLinksCard tenantUrl={data.session?.tenantUrl} />
|
|
||||||
<ResourceLinksCard />
|
|
||||||
<SupportLinksCard />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
51
Sveltekit-App/src/routes/home/example-form/+page.server.ts
Normal file
51
Sveltekit-App/src/routes/home/example-form/+page.server.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { createConfiguration } from '$lib/sailpoint/sdk';
|
||||||
|
import {
|
||||||
|
Paginator,
|
||||||
|
SourcesApi,
|
||||||
|
type Source,
|
||||||
|
type SourcesApiUpdateSourceRequest
|
||||||
|
} from 'sailpoint-api-client';
|
||||||
|
import type { Actions } from './$types';
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
default: async ({ locals, request }) => {
|
||||||
|
const data = await request.formData();
|
||||||
|
|
||||||
|
console.log('default action');
|
||||||
|
console.log('data', data);
|
||||||
|
|
||||||
|
const config = createConfiguration(locals.session!.baseUrl, locals.idnSession!.access_token);
|
||||||
|
const api = new SourcesApi(config);
|
||||||
|
|
||||||
|
const source = JSON.parse(data.get('source')?.toString() || '{}');
|
||||||
|
const updatedDescription = data.get('updatedDescription')?.toString();
|
||||||
|
|
||||||
|
const params: SourcesApiUpdateSourceRequest = {
|
||||||
|
id: source.id,
|
||||||
|
jsonPatchOperation: [{ op: 'replace', path: '/description', value: updatedDescription }]
|
||||||
|
};
|
||||||
|
|
||||||
|
const resp = await api.updateSource(params);
|
||||||
|
|
||||||
|
if (resp.status !== 200) {
|
||||||
|
return { status: 'error', error: resp.statusText };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { status: 'success' };
|
||||||
|
}
|
||||||
|
} satisfies Actions;
|
||||||
|
|
||||||
|
export const load = async ({ locals }) => {
|
||||||
|
const config = createConfiguration(locals.session!.baseUrl, locals.idnSession!.access_token);
|
||||||
|
const api = new SourcesApi(config);
|
||||||
|
|
||||||
|
const sourceResp = Paginator.paginate(api, api.listSources, { limit: 1000 });
|
||||||
|
|
||||||
|
const sources = new Promise<Source[]>((resolve) => {
|
||||||
|
sourceResp.then((response) => {
|
||||||
|
resolve(response.data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return { sources };
|
||||||
|
};
|
||||||
68
Sveltekit-App/src/routes/home/example-form/+page.svelte
Normal file
68
Sveltekit-App/src/routes/home/example-form/+page.svelte
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { enhance } from '$app/forms';
|
||||||
|
import Progress from '$lib/Components/Progress.svelte';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import type { Writable } from 'svelte/store';
|
||||||
|
import { localStorageStore } from '@skeletonlabs/skeleton';
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
|
||||||
|
const selectedSource: Writable<any> = localStorageStore('selectedSource', undefined);
|
||||||
|
const updatedDescription: Writable<string> = localStorageStore('updatedDescription', '');
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const sources = await data.sources;
|
||||||
|
if (sources.length > 0) {
|
||||||
|
$selectedSource = JSON.stringify(sources[0]);
|
||||||
|
$updatedDescription = sources[0].description || '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChange = (e: any) => {
|
||||||
|
$updatedDescription = JSON.parse(e.target.value).description || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
$: console.log($selectedSource);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex justify-center flex-col align-middle gap-2">
|
||||||
|
<div class="card p-4">
|
||||||
|
<p class="text-2xl text-center">Example Form</p>
|
||||||
|
</div>
|
||||||
|
<form method="POST" class="card p-4">
|
||||||
|
<p class="text-2xl text-center">Update Source Description</p>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
{#await data.sources}
|
||||||
|
<div class="flex flex-row justify-center">
|
||||||
|
<Progress width="w-[100px]" />
|
||||||
|
</div>
|
||||||
|
{:then sources}
|
||||||
|
<label>
|
||||||
|
<span>Sources:</span>
|
||||||
|
<select
|
||||||
|
on:change={handleChange}
|
||||||
|
name="source"
|
||||||
|
placeholder="Select a source"
|
||||||
|
bind:value={$selectedSource}
|
||||||
|
class="select"
|
||||||
|
>
|
||||||
|
<option hidden disabled>Select a source</option>
|
||||||
|
{#each sources as source}
|
||||||
|
{@const sourceString = JSON.stringify(source)}
|
||||||
|
<option value={sourceString} selected={$selectedSource === sourceString}>
|
||||||
|
{source.name} - {source.type}
|
||||||
|
</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<span>Description:</span>
|
||||||
|
<textarea name="updatedDescription" class="textarea" bind:value={$updatedDescription} />
|
||||||
|
</label>
|
||||||
|
{/await}
|
||||||
|
|
||||||
|
<button type="submit" class="btn variant-filled">Submit</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { getLimit, getPage, getSorters } from '$lib/Utils.js';
|
||||||
|
import { createConfiguration } from '$lib/sailpoint/sdk.js';
|
||||||
|
import type { IdentityDocument, Search, SearchApiSearchPostRequest } from 'sailpoint-api-client';
|
||||||
|
import { SearchApi } from 'sailpoint-api-client';
|
||||||
|
|
||||||
|
export const load = async ({ url, locals }) => {
|
||||||
|
const config = createConfiguration(locals.session!.baseUrl, locals.idnSession!.access_token);
|
||||||
|
const api = new SearchApi(config);
|
||||||
|
|
||||||
|
const page = getPage(url);
|
||||||
|
const limit = getLimit(url);
|
||||||
|
const sorters = getSorters(url);
|
||||||
|
|
||||||
|
const search: Search = {
|
||||||
|
indices: ['identities'],
|
||||||
|
query: {
|
||||||
|
query: `*`
|
||||||
|
},
|
||||||
|
sort: sorters !== '' ? [sorters] : ['name']
|
||||||
|
};
|
||||||
|
|
||||||
|
const requestParams: SearchApiSearchPostRequest = {
|
||||||
|
search,
|
||||||
|
offset: Number(page) * Number(limit),
|
||||||
|
limit: Number(limit),
|
||||||
|
count: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const reportResp = api.searchPost(requestParams);
|
||||||
|
|
||||||
|
const totalCount = new Promise<number>((resolve) => {
|
||||||
|
reportResp.then((response) => {
|
||||||
|
resolve(response.headers['x-total-count']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const reportData = new Promise<IdentityDocument[]>((resolve) => {
|
||||||
|
reportResp.then((response) => {
|
||||||
|
resolve(response.data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return { reportData, totalCount, params: { page, limit, sorters } };
|
||||||
|
};
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Paginator from '$lib/Components/Paginator.svelte';
|
||||||
|
import Progress from '$lib/Components/Progress.svelte';
|
||||||
|
import {
|
||||||
|
TriggerCodeModal,
|
||||||
|
createOnAmountChange,
|
||||||
|
createOnGo,
|
||||||
|
createOnPageChange,
|
||||||
|
formatDate
|
||||||
|
} from '$lib/Utils.js';
|
||||||
|
import { getModalStore } from '@skeletonlabs/skeleton';
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
|
||||||
|
const modalStore = getModalStore();
|
||||||
|
|
||||||
|
$: onPageChange = createOnPageChange(
|
||||||
|
{ ...data.params, filters: '', sorters },
|
||||||
|
'/home/reports/list-of-identities'
|
||||||
|
);
|
||||||
|
$: onAmountChange = createOnAmountChange(
|
||||||
|
{ ...data.params, filters: '', sorters },
|
||||||
|
'/home/reports/list-of-identities'
|
||||||
|
);
|
||||||
|
$: onGo = createOnGo(
|
||||||
|
{ ...data.params, filters: '', sorters },
|
||||||
|
'/home/reports/list-of-identities'
|
||||||
|
);
|
||||||
|
|
||||||
|
let sorters = data.params.sorters || '';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex justify-center flex-col align-middle gap-2">
|
||||||
|
<div class="card p-4">
|
||||||
|
<p class="text-2xl text-center">List of all identities</p>
|
||||||
|
</div>
|
||||||
|
{#await data.reportData}
|
||||||
|
<div class="grid h-full place-content-center p-8">
|
||||||
|
<Progress width="w-[100px]" />
|
||||||
|
</div>
|
||||||
|
{:then reportData}
|
||||||
|
{#await data.totalCount then totalCount}
|
||||||
|
{#if totalCount > 250 || Number(data.params.limit) < totalCount}
|
||||||
|
<div class="card p-4">
|
||||||
|
<Paginator
|
||||||
|
{onAmountChange}
|
||||||
|
{onGo}
|
||||||
|
{onPageChange}
|
||||||
|
settings={{
|
||||||
|
page: Number(data.params.page),
|
||||||
|
limit: Number(data.params.limit),
|
||||||
|
size: totalCount,
|
||||||
|
amounts: [5, 10, 50, 100, 250]
|
||||||
|
}}
|
||||||
|
bind:sorters
|
||||||
|
{totalCount}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
|
{#if reportData.length === 0}
|
||||||
|
<div class="card p-4">
|
||||||
|
<p class=" text-center text-success-500">No inactive identities with access found</p>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="table">
|
||||||
|
<thead class="table-head">
|
||||||
|
<th> Name </th>
|
||||||
|
<th> DisplayName </th>
|
||||||
|
<th> Sources </th>
|
||||||
|
<th> Created </th>
|
||||||
|
<th> Modified </th>
|
||||||
|
<th> Access Count </th>
|
||||||
|
<th> Entitlement Count </th>
|
||||||
|
<th> Role Count </th>
|
||||||
|
<th></th>
|
||||||
|
</thead>
|
||||||
|
<tbody class="table-body">
|
||||||
|
{#each reportData as identity}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{identity.name}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{identity.displayName}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{identity.accounts?.map((account) => account.source?.name).join(', ')}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{formatDate(identity.created)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{formatDate(identity.modified)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{identity.accessCount}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{identity.entitlementCount}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{identity.roleCount}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex flex-col justify-center gap-1">
|
||||||
|
<button
|
||||||
|
on:click={() => TriggerCodeModal(identity, modalStore)}
|
||||||
|
class="btn btn-sm variant-filled-primary text-sm !text-white"
|
||||||
|
>
|
||||||
|
View
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
|
</div>
|
||||||
@@ -1,23 +1,28 @@
|
|||||||
export const reports = [
|
export const reports = [
|
||||||
{
|
{
|
||||||
url: '/home/reports/inactive-identities-with-access',
|
url: '/home/example-pages/list-of-identities',
|
||||||
|
name: 'List of Identities',
|
||||||
|
description: 'This report will show all identities in the system'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/home/example-pages/inactive-identities-with-access',
|
||||||
name: 'Inactive Identities With Access',
|
name: 'Inactive Identities With Access',
|
||||||
description:
|
description:
|
||||||
'This report will show all identities that are inactive but still have access in sources'
|
'This report will show all identities that are inactive but still have access in sources'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: '/home/reports/missing-cloud-life-cycle-state',
|
url: '/home/example-pages/missing-cloud-life-cycle-state',
|
||||||
name: 'Missing Cloud Life Cycle State',
|
name: 'Missing Cloud Life Cycle State',
|
||||||
description: 'This report will show all identities that are missing a cloud life cycle state'
|
description: 'This report will show all identities that are missing a cloud life cycle state'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
url: '/home/reports/source-owner-configured',
|
url: '/home/example-pages/source-owner-configured',
|
||||||
name: 'Source Owner Configured',
|
name: 'Source Owner Configured',
|
||||||
description: 'This report will show all sources and their configured owners'
|
description: 'This report will show all sources and their configured owners'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: '/home/reports/source-aggregations',
|
url: '/home/example-pages/source-aggregations',
|
||||||
name: 'Source Aggregations',
|
name: 'Source Aggregations',
|
||||||
description: 'This report will show all sources and their most recent aggregation events'
|
description: 'This report will show all sources and their most recent aggregation events'
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { createConfiguration } from '$lib/sailpoint/sdk.js';
|
||||||
|
import {
|
||||||
|
CustomFormsBetaApi,
|
||||||
|
type ExportFormDefinitionsByTenant200ResponseInnerBeta
|
||||||
|
} from 'sailpoint-api-client';
|
||||||
|
|
||||||
|
export const load = async ({ locals }) => {
|
||||||
|
const config = createConfiguration(locals.session!.baseUrl, locals.idnSession!.access_token);
|
||||||
|
const api = new CustomFormsBetaApi(config);
|
||||||
|
|
||||||
|
const formsResp = api.exportFormDefinitionsByTenant({});
|
||||||
|
|
||||||
|
const forms = new Promise<ExportFormDefinitionsByTenant200ResponseInnerBeta[]>((resolve) => {
|
||||||
|
formsResp.then((response) => {
|
||||||
|
resolve(response.data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return { forms };
|
||||||
|
};
|
||||||
160
Sveltekit-App/src/routes/home/form-integration/+page.svelte
Normal file
160
Sveltekit-App/src/routes/home/form-integration/+page.svelte
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { ExportFormDefinitionsByTenant200ResponseInnerBeta } from 'sailpoint-api-client';
|
||||||
|
import Progress from '$lib/Components/Progress.svelte';
|
||||||
|
export let data;
|
||||||
|
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
let selectedForm: ExportFormDefinitionsByTenant200ResponseInnerBeta;
|
||||||
|
let dialog: HTMLDialogElement;
|
||||||
|
let inputs = {};
|
||||||
|
let loading = false;
|
||||||
|
let formUrl: string;
|
||||||
|
let conditions = new Map();
|
||||||
|
|
||||||
|
function parseFormConditions(conditions) {
|
||||||
|
let parsedConditionals = new Map();
|
||||||
|
console.log(conditions);
|
||||||
|
for (const condition of conditions || []) {
|
||||||
|
for (const rule of condition.rules) {
|
||||||
|
console.log(rule);
|
||||||
|
const temp = parsedConditionals.get(rule.source) || [];
|
||||||
|
parsedConditionals.set(rule.source, Array.from(new Set([...temp, rule.value])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(parsedConditionals);
|
||||||
|
return parsedConditionals;
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (selectedForm) {
|
||||||
|
conditions = parseFormConditions(selectedForm.object?.formConditions);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<dialog class="card p-8" bind:this={dialog}>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
{#if selectedForm}
|
||||||
|
<p id="name" class="text-center">
|
||||||
|
Name: {selectedForm.object?.name}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Description: <br />
|
||||||
|
{selectedForm.object?.description}
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<p>Form Inputs:</p>
|
||||||
|
{#if selectedForm.object?.formInput}
|
||||||
|
{#each selectedForm.object?.formInput as input}
|
||||||
|
{#if conditions.get(input.label)}
|
||||||
|
<label class="label" for={input.label}>
|
||||||
|
<span>{input.label}</span>
|
||||||
|
<select class="input" id={input.label} bind:value={inputs[input.label]}>
|
||||||
|
{#each conditions.get(input.label) as condition}
|
||||||
|
<option value={condition}>{condition}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
{:else}
|
||||||
|
<label class="label" for={input.label}>
|
||||||
|
<span>{input.label}</span>
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
id={input.label}
|
||||||
|
bind:value={inputs[input.label]}
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if loading}
|
||||||
|
<div class="flex flex-row justify-center">
|
||||||
|
<Progress width="w-[80px]" />
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
{#if formUrl}
|
||||||
|
<a class="btn variant-filled-secondary" href={formUrl} target="_blank" rel="noreferrer">
|
||||||
|
Open Form
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
<button
|
||||||
|
class="btn variant-filled-primary"
|
||||||
|
on:click={async () => {
|
||||||
|
loading = true;
|
||||||
|
console.log(inputs);
|
||||||
|
const formResp = await fetch('/api/sailpoint/form/create-instance', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ formDefinitionId: selectedForm.object?.id, formInput: inputs })
|
||||||
|
});
|
||||||
|
const respData = await formResp.json();
|
||||||
|
console.log(respData);
|
||||||
|
|
||||||
|
formUrl = respData.formInstanceResp.standAloneFormUrl;
|
||||||
|
loading = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#if formUrl}
|
||||||
|
Refresh form link
|
||||||
|
{:else}
|
||||||
|
Create form link
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
<button
|
||||||
|
class="btn variant-filled-warning"
|
||||||
|
on:click={() => {
|
||||||
|
dialog.close();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{#await data.forms}
|
||||||
|
<div class="flex flex-row justify-center">
|
||||||
|
<Progress width="w-[80px]" />
|
||||||
|
</div>
|
||||||
|
{:then forms}
|
||||||
|
<div class="flex flex-row">
|
||||||
|
{#each forms as form}
|
||||||
|
<div class="card flex flex-col p-4 gap-4">
|
||||||
|
<p id="name" class="text-center">
|
||||||
|
Name: {form.object?.name}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
ID: {form.object?.id}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Description: <br />
|
||||||
|
{form.object?.description}
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<p>Form Inputs:</p>
|
||||||
|
{#if form.object?.formInput}
|
||||||
|
{#each form.object?.formInput as input}
|
||||||
|
<p class="">
|
||||||
|
{input.label}
|
||||||
|
</p>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="btn variant-filled-primary"
|
||||||
|
on:click={() => {
|
||||||
|
selectedForm = form;
|
||||||
|
dialog.showModal();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Assign form
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user