Implementing the Account List Page
Overview
This guide will walk you through the process of implementing a new page that lists all the accounts in your tenant. This serves as an example you can use to learn how to implement many different types of custom UI pages.
This guide is a continuation of Getting Started. To implement a new page, you must have already created your routes and pages. To learn how to do so, refer to the Add New Route and Page section.
At the end of this guide you will be able to do the following:
- List all accounts in your tenant
- Paginate accounts by page limits of 5, 10, 50, 100, 250
- Click through each page of results
- View the JSON response of the account from your tenant
- Sort the results, using the sort syntax on the List Accounts endpoint.
- Filter the results, using the filter syntax on the List Accounts endpoint.
Get account data
The first thing your new page needs to do is get the account data to show. You can implement this by using the List Accounts endpoint.
To do so, add this logic to the server side of the accounts list page at src/routes/accounts/account-list/+page.server.ts
:
// Import classes needed
import {createConfiguration} from '$lib/sailpoint/sdk.js';
import type {Account} from 'sailpoint-api-client';
import {AccountsApi} from 'sailpoint-api-client';
export const load = async ({locals}) => {
// Create a typescript SDK configuration object using the
// baseUrl and access_token pulled from the logged in user.
const config = createConfiguration(
locals.session!.baseUrl,
locals.idnSession!.access_token,
);
// Initialize the api client for the account api
const api = new AccountsApi(config);
// Call list account with count=true so that we get the header of total accounts back
const reportResp = api.listAccounts({count: true});
const totalCount = new Promise<number>((resolve) => {
reportResp.then((response) => {
resolve(response.headers['x-total-count']);
});
});
const accountData = new Promise<Account[]>((resolve) => {
reportResp.then((response) => {
console.log(response.data); // Logs out the account response in the console
resolve(response.data);
});
});
// Return the data to the UI
return {accountData, totalCount};
};
Return to your accounts list page. In your IDE, you will see the response containing the accounts after the page loads.
Now that you have your account data, you need to display the data. You can add a table to the UI and display your results.
To do so, add this code to src/routes/accounts/account-list/+page.svelte
:
Show Code
<script lang="ts">
import Progress from '$lib/Components/Progress.svelte';
import {
TriggerCodeModal,
formatDate
} from '$lib/Utils.js';
import { getModalStore } from '@skeletonlabs/skeleton';
export let data;
const modalStore = getModalStore();
</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 accounts</p> <!-- Page title -->
</div>
{#await data.accountData}
<div class="grid h-full place-content-center p-8">
<Progress width="w-[100px]" />
</div>
{:then accountData}
<!-- If there is no account data show a message to the user -->
{#if accountData.length === 0}
<div class="card p-4">
<p class=" text-center text-success-500">No Accounts found</p>
</div>
{:else}
<div class="table-container">
<table class="table">
<thead class="table-head"> <!-- Table headers -->
<th> Name </th>
<th> Native Identity </th>
<th> Source </th>
<th> Created </th>
<th> Modified </th>
<th> Authoritative </th>
<th> Features </th>
<th> Has Entitlements </th>
<th></th>
</thead>
<tbody class="table-body">
{#each accountData as account} <!-- Create a table row for each account -->
<tr>
<td>
{account.name}
</td>
<td>
{account.nativeIdentity}
</td>
<td>
{account.sourceName}
</td>
<td>
{formatDate(account.created)}
</td>
<td>
{formatDate(account.modified)}
</td>
<td>
{account.authoritative}
</td>
<td>
{account.features}
</td>
<td>
{account.hasEntitlements}
</td>
<td>
<div class="flex flex-col justify-center gap-1">
<!-- Trigger code modal view to see raw account data -->
<button
on:click={() => TriggerCodeModal(account, modalStore)}
class="btn btn-sm variant-filled-primary text-sm !text-white"
>
View
</button>
</div>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
{/await}
</div>
Save the +page.svelte
file and return to the accounts list page. You will see up to 250 accounts in the table.
Pagination
You will likely have more than 250 accounts in your tenant. To handle more than 250 accounts, you must implement pagination on your front end page. Pagination is the process of paging through your records. With pagination, you can handle 1000 accounts in 4 pages of 250, for example.
To implement pagination, add the following code. This code allows you to paginate accounts in groups of 5, 10, 50, 100 and 250, depending on your requirement:
The highlighted portions of the code are the changes made to the previous code to allow pagination.
- account-list/+page.svelte
- accounts-list/+page.server.ts
<script lang="ts">
import Paginator from '$lib/Components/Paginator.svelte';
import Progress from '$lib/Components/Progress.svelte';
import {
TriggerCodeModal,
formatDate,
createOnPageChange,
createOnAmountChange
} from '$lib/Utils.js';
import { getModalStore } from '@skeletonlabs/skeleton';
export let data;
const modalStore = getModalStore();
$: onPageChange = createOnPageChange(
{ ...data.params, filters: '', sorters: ''},
'/accounts/account-list'
);
$: onAmountChange = createOnAmountChange(
{ ...data.params, filters: '', sorters: ''},
'/accounts/account-list'
);
</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 accounts</p>
</div>
{#await data.accountData}
<div class="grid h-full place-content-center p-8">
<Progress width="w-[100px]" />
</div>
{:then accountData}
{#await data.totalCount then totalCount}
{#if totalCount > 250 || Number(data.params.limit) < totalCount}
<div class="card p-4">
<Paginator
{onAmountChange}
{onPageChange}
settings={{
page: Number(data.params.page),
limit: Number(data.params.limit),
size: totalCount,
amounts: [5, 10, 50, 100, 250]
}}
{totalCount}
/>
</div>
{/if}
{/await}
{#if accountData.length === 0}
<div class="card p-4">
<p class=" text-center text-success-500">No Accounts found</p>
</div>
{:else}
<div class="table-container">
<table class="table">
<thead class="table-head">
<th> Name </th>
<th> Native Identity </th>
<th> Source </th>
<th> Created </th>
<th> Modified </th>
<th> Authoritative </th>
<th> Features </th>
<th> Has Entitlements </th>
<th></th>
</thead>
<tbody class="table-body">
{#each accountData as account}
<tr>
<td>
{account.name}
</td>
<td>
{account.nativeIdentity}
</td>
<td>
{account.sourceName}
</td>
<td>
{formatDate(account.created)}
</td>
<td>
{formatDate(account.modified)}
</td>
<td>
{account.authoritative}
</td>
<td>
{account.features}
</td>
<td>
{account.hasEntitlements}
</td>
<td>
<div class="flex flex-col justify-center gap-1">
<button
on:click={() => TriggerCodeModal(account, modalStore)}
class="btn btn-sm variant-filled-primary text-sm !text-white"
>
View
</button>
</div>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
{/await}
</div>
import { createConfiguration } from '$lib/sailpoint/sdk.js';
import { getLimit, getPage } from '$lib/Utils.js';
import type { Account } from 'sailpoint-api-client';
import { AccountsApi } from 'sailpoint-api-client';
export const load = async ({ url, locals }) => {
const config = createConfiguration(locals.session!.baseUrl, locals.idnSession!.access_token);
const api = new AccountsApi(config);
const page = getPage(url);
const limit = getLimit(url);
const reportResp = api.listAccounts({count: true, limit: Number(limit), offset: Number(page) * Number(limit)});
const totalCount = new Promise<number>((resolve) => {
reportResp.then((response) => {
resolve(response.headers['x-total-count']);
});
});
const accountData = new Promise<Account[]>((resolve) => {
reportResp.then((response) => {
resolve(response.data);
});
});
return { accountData, totalCount, params: {page, limit}};
};
Return to the accounts list page. You will see the paginator at the top of the page. You can now paginate through the accounts in your tenant.
Sort and filter
To better view and organize the data displayed in your accounts list page, you may want to implement sorting and filtering. Sorting is the process of organizing the data. You may want to provide users with a way to sort the data in ascending or descending alphabetical order based on the account name, for example. Filtering is the process of limiting the displayed data based on specified details. You may want to provide users with a way to filter the data to only include accounts associated with one source, for example.
To implement sorting and filtering, add the following highlighted code. It allows you to sort and filter the accounts in your tenant:
With this implementation, once a user types in a filter or sorter and clicks the 'Go' button, the UI Development Kit calls the onCreateGo
function and reloads the page with the new sorters and filters.
On the server side, the kit uses the getFilters
and getSorters
functions to get the filters and sorters from the URL.
The kit then passes these functions to the List Accounts endpoint to filter and sort the accounts.
- account-list/+page.svelte
- accounts-list/+page.server.ts
<script lang="ts">
import Paginator from '$lib/Components/Paginator.svelte';
import Progress from '$lib/Components/Progress.svelte';
import {
TriggerCodeModal,
formatDate,
createOnPageChange,
createOnAmountChange,
createOnGo
} from '$lib/Utils.js';
import { getModalStore } from '@skeletonlabs/skeleton';
export let data;
const modalStore = getModalStore();
$: onPageChange = createOnPageChange(
{ ...data.params, filters, sorters},
'/accounts/account-list'
);
$: onAmountChange = createOnAmountChange(
{ ...data.params, filters, sorters},
'/accounts/account-list'
);
$: onGo = createOnGo(
{ ...data.params, filters, sorters },
'/accounts/account-list'
);
let filters = data.params.filters || '';
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 accounts</p>
</div>
{#await data.accountData}
<div class="grid h-full place-content-center p-8">
<Progress width="w-[100px]" />
</div>
{:then accountData}
{#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
bind:filters
{totalCount}
/>
</div>
{/if}
{/await}
{#if accountData.length === 0}
<div class="card p-4">
<p class=" text-center text-success-500">No Accounts found</p>
</div>
{:else}
<div class="table-container">
<table class="table">
<thead class="table-head">
<th> Name </th>
<th> Native Identity </th>
<th> Source </th>
<th> Created </th>
<th> Modified </th>
<th> Authoritative </th>
<th> Features </th>
<th> Has Entitlements </th>
<th></th>
</thead>
<tbody class="table-body">
{#each accountData as account}
<tr>
<td>
{account.name}
</td>
<td>
{account.nativeIdentity}
</td>
<td>
{account.sourceName}
</td>
<td>
{formatDate(account.created)}
</td>
<td>
{formatDate(account.modified)}
</td>
<td>
{account.authoritative}
</td>
<td>
{account.features}
</td>
<td>
{account.hasEntitlements}
</td>
<td>
<div class="flex flex-col justify-center gap-1">
<button
on:click={() => TriggerCodeModal(account, modalStore)}
class="btn btn-sm variant-filled-primary text-sm !text-white"
>
View
</button>
</div>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
{/await}
</div>
import { createConfiguration } from '$lib/sailpoint/sdk.js';
import { getFilters, getLimit, getPage, getSorters } from '$lib/Utils.js';
import type { Account } from 'sailpoint-api-client';
import { AccountsApi } from 'sailpoint-api-client';
export const load = async ({ url, locals }) => {
const config = createConfiguration(locals.session!.baseUrl, locals.idnSession!.access_token);
const api = new AccountsApi(config);
const page = getPage(url);
const limit = getLimit(url);
const sorters = getSorters(url);
const filters = getFilters(url);
const reportResp = api.listAccounts({count: true, sorters: sorters, filters: filters, limit: Number(limit), offset: Number(page) * Number(limit)});
const totalCount = new Promise<number>((resolve) => {
reportResp.then((response) => {
resolve(response.headers['x-total-count']);
});
});
const accountData = new Promise<Account[]>((resolve) => {
reportResp.then((response) => {
resolve(response.data);
});
});
return { accountData, totalCount, params: {page, limit, sorters, filters}};
};
Error Handling
You have now implemented a new page that lists all the accounts in your tenant, and you can now paginate, sort and filter the accounts in your tenant.
Ideally, everything in your custom UIs will work smoothly, but you will likely encounter errors at some point when you're implementing a page. If you provide an invalid filter or sorter, the list accounts endpoint will return a 400 error, for example.
You can handle this error by adding a try catch
block to the server side of the accounts list page.
To learn more about handling errors in your UI, refer to Error Handling.
Discuss
The most valuable resource for ISC developers is the SailPoint Developer Community itself, where ISC users and experts all over the world come together to ask questions and provide solutions.
To learn more about the ISC UI Development Kit and discuss it with SailPoint Developer Community members, go to the SailPoint Developer Community Forum.