mirror of
https://github.com/LukeHagar/ui-development-kit.git
synced 2025-12-06 04:21:49 +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">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/logo.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>IdentityNow Admin Console</title>
|
||||
<title>IdentityNow Starter Application</title>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-theme="wintry">
|
||||
|
||||
@@ -16,23 +16,33 @@ export const handle: Handle = async ({ event, resolve }) => {
|
||||
event.locals.hasIdnSession = hasIdnSession;
|
||||
if (hasSession) {
|
||||
event.locals.session = getSession(event.cookies);
|
||||
if(hasIdnSession){
|
||||
event.locals.idnSession = await getToken(event.cookies);
|
||||
const lastToken = lastCheckedToken(event.cookies);
|
||||
const tokenDetails = getTokenDetails(event.cookies);
|
||||
if (tokenDetails && lastToken != '' && lastToken === event.locals.idnSession.access_token ) {
|
||||
event.locals.tokenDetails = tokenDetails
|
||||
} else {
|
||||
event.locals.tokenDetails = await checkToken(
|
||||
event.locals.session.baseUrl,
|
||||
event.locals.idnSession.access_token
|
||||
);
|
||||
event.cookies.set('tokenDetails', JSON.stringify(event.locals.tokenDetails), {
|
||||
path: '/',
|
||||
httpOnly: false,
|
||||
secure: false
|
||||
});
|
||||
}}
|
||||
if (hasIdnSession) {
|
||||
const session = await getToken(event.cookies);
|
||||
if (!session) {
|
||||
event.locals.hasIdnSession = false;
|
||||
event.locals.idnSession = undefined;
|
||||
} else {
|
||||
event.locals.idnSession = session;
|
||||
const lastToken = lastCheckedToken(event.cookies);
|
||||
const tokenDetails = getTokenDetails(event.cookies);
|
||||
if (tokenDetails && lastToken != '' && lastToken === event.locals.idnSession.access_token) {
|
||||
event.locals.tokenDetails = tokenDetails;
|
||||
} else {
|
||||
const tempTokenDetails = await checkToken(
|
||||
event.locals.session.baseUrl,
|
||||
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')) {
|
||||
|
||||
@@ -6,29 +6,35 @@
|
||||
export let onPageChange: (e: CustomEvent<number>) => void;
|
||||
export let onAmountChange: (e: CustomEvent<number>) => void;
|
||||
export let onGo: (e: KeyboardEvent | MouseEvent) => void;
|
||||
export let filters: string = '';
|
||||
export let sorters: string = '';
|
||||
export let filters: string | undefined = undefined;
|
||||
export let sorters: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<div class=" p-4 flex flex-row flex-wrap justify-between gap-4">
|
||||
<div class="flex flex-row gap-1">
|
||||
<input
|
||||
on:keydown={onGo}
|
||||
bind:value={filters}
|
||||
class="input"
|
||||
title="Filter"
|
||||
type="text"
|
||||
placeholder="Filter"
|
||||
/>
|
||||
<input
|
||||
on:keydown={onGo}
|
||||
bind:value={sorters}
|
||||
class="input"
|
||||
title="Sorter"
|
||||
type="text"
|
||||
placeholder="Sorter"
|
||||
/>
|
||||
<button on:click={onGo} class="btn variant-filled-primary !text-white"> Go </button>
|
||||
{#if filters !== undefined}
|
||||
<input
|
||||
on:keydown={onGo}
|
||||
bind:value={filters}
|
||||
class="input"
|
||||
title="Filter"
|
||||
type="text"
|
||||
placeholder="Filter"
|
||||
/>
|
||||
{/if}
|
||||
{#if sorters !== undefined}
|
||||
<input
|
||||
on:keydown={onGo}
|
||||
bind:value={sorters}
|
||||
class="input"
|
||||
title="Sorter"
|
||||
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>
|
||||
<p class="my-auto">Total Count: {totalCount}</p>
|
||||
<Paginator
|
||||
|
||||
@@ -7,7 +7,7 @@ export function formatDate(date: string | null | undefined) {
|
||||
}
|
||||
|
||||
export function getLimit(url: URL) {
|
||||
return url.searchParams.get('limit') || '250';
|
||||
return url.searchParams.get('limit') || '5';
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
const urlParams = new URLSearchParams();
|
||||
urlParams.set('page', params.page);
|
||||
urlParams.set('limit', params.limit);
|
||||
urlParams.set('sorters', params.sorters);
|
||||
urlParams.set('filters', params.filters);
|
||||
if (params.page != '') urlParams.set('page', params.page);
|
||||
if (params.limit != '') urlParams.set('limit', params.limit);
|
||||
if (params.sorters != '') urlParams.set('sorters', params.sorters);
|
||||
if (params.filters != '') urlParams.set('filters', params.filters);
|
||||
|
||||
console.log(`${path}?${urlParams.toString()}`);
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import HomeSvg from '$lib/Components/SVGs/HomeSVG.svelte';
|
||||
import MessagesSvg from '$lib/Components/SVGs/MessagesSVG.svelte';
|
||||
import ReportsSvg from '$lib/Components/SVGs/ReportsSVG.svelte';
|
||||
|
||||
export const navigation = [
|
||||
@@ -13,10 +12,20 @@ export const navigation = [
|
||||
icon: HomeSvg
|
||||
},
|
||||
{
|
||||
url: '/home/Example Pages',
|
||||
name: 'Reports',
|
||||
url: '/home/example-pages',
|
||||
name: 'Example Pages',
|
||||
description: 'a list of example pages showcasing how to implement the IdentityNow SDK.',
|
||||
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 url = `${apiUrl}/oauth/check_token/`;
|
||||
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;
|
||||
});
|
||||
// if (response) {
|
||||
// console.log(response.data);
|
||||
// }
|
||||
const tokenDetails = response!.data;
|
||||
if (!response) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const tokenDetails = response.data;
|
||||
|
||||
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 response = await axios.post(url).catch(function (err) {
|
||||
if (err.response) {
|
||||
@@ -104,10 +108,12 @@ export async function refreshToken(apiUrl: string, refreshToken: string): Promis
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
// if (response) {
|
||||
// console.log(response.data)
|
||||
// }
|
||||
const idnSession: IdnSession = response!.data as IdnSession;
|
||||
|
||||
if (!response) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const idnSession: IdnSession = response.data as IdnSession;
|
||||
return idnSession;
|
||||
}
|
||||
|
||||
@@ -147,7 +153,7 @@ export function getSession(cookies: Cookies): 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 idnSessionString = cookies.get('idnSession');
|
||||
|
||||
@@ -171,12 +177,17 @@ export async function getToken(cookies: Cookies): Promise<IdnSession> {
|
||||
if (isJwtExpired(idnSession.access_token)) {
|
||||
console.log('Refreshing IdnSession token...');
|
||||
const newSession = await refreshToken(session.baseUrl, idnSession.refresh_token);
|
||||
cookies.set('idnSession', JSON.stringify(newSession), {
|
||||
path: '/',
|
||||
httpOnly: false,
|
||||
secure: false
|
||||
});
|
||||
return Promise.resolve(newSession);
|
||||
if (newSession) {
|
||||
cookies.set('idnSession', JSON.stringify(newSession), {
|
||||
path: '/',
|
||||
httpOnly: false,
|
||||
secure: false
|
||||
});
|
||||
return Promise.resolve(newSession);
|
||||
} else {
|
||||
console.log('IdnSession token is expired');
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
} else {
|
||||
console.log('IdnSession token is good');
|
||||
return Promise.resolve(idnSession);
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
<!-- <svelte:fragment slot="sidebarRight">Sidebar Right</svelte:fragment> -->
|
||||
<!-- <svelte:fragment slot="pageHeader">Page Header</svelte:fragment> -->
|
||||
<!-- Router Slot -->
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col h-full">
|
||||
{#if crumbs.length > 0}
|
||||
<div class="pl-2 pt-2 pr-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">
|
||||
import ResourceLinksCard from '$lib/Components/HomepageCards/ResourceLinksCard.svelte';
|
||||
import StatusCard from '$lib/Components/HomepageCards/StatusCard.svelte';
|
||||
import SupportLinksCard from '$lib/Components/HomepageCards/SupportLinksCard.svelte';
|
||||
import TenantLinksCard from '$lib/Components/HomepageCards/TenantLinksCard.svelte';
|
||||
<div class="grid place-content-center h-full">
|
||||
<p class="text-center px-60">
|
||||
This starter application is meant to be an example of how you can build on top of the
|
||||
IdentityNow UI development kit to build your own applications and tools for IdentityNow
|
||||
|
||||
export let data;
|
||||
console.log(data);
|
||||
</script>
|
||||
<br />
|
||||
|
||||
<div class="flex flex-col gap-2 grow">
|
||||
<div class="flex flex-row flex-wrap gap-2 grow">
|
||||
<StatusCard />
|
||||
<TenantLinksCard tenantUrl={data.session?.tenantUrl} />
|
||||
<ResourceLinksCard />
|
||||
<SupportLinksCard />
|
||||
</div>
|
||||
On the left hand side you will see some example pages showcasing some different kinds of pages
|
||||
you could build. Each page is meant to be a starting point for you to build your own pages.
|
||||
</p>
|
||||
</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 = [
|
||||
{
|
||||
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',
|
||||
description:
|
||||
'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',
|
||||
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',
|
||||
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',
|
||||
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