mirror of
https://github.com/LukeHagar/Anchor.git
synced 2025-12-06 04:19:08 +00:00
Partial rewrite save, testing action
This commit is contained in:
75
.github/workflows/build.yaml
vendored
Normal file
75
.github/workflows/build.yaml
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
name: Build and Upload Chrome Extension
|
||||
|
||||
run-name: ${{ github.actor }} is Building and Uploading a new Anchor Version 🚀
|
||||
on: [push, workflow_dispatch]
|
||||
jobs:
|
||||
Build-And-Push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Checkout the main branch of this repo
|
||||
- name: Checkout PR branch
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: lukehagar/anchor
|
||||
path: anchor
|
||||
ref: main
|
||||
|
||||
# Checkout the main branch of api-specs
|
||||
- name: Checkout API Specs Repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: sailpoint-oss/api-specs
|
||||
path: api-specs
|
||||
ref: main
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
|
||||
- name: Install swagger-cli
|
||||
run: |
|
||||
npm install -g swagger-cli
|
||||
|
||||
- name: Dereference and Bundle Beta API Specification
|
||||
id: buildBeta
|
||||
run: |
|
||||
swagger-cli bundle --dereference api-specs/idn/sailpoint-api.beta.yaml -t json -o anchor/src/routes/api-client/BetaSpec.json
|
||||
|
||||
- name: Dereference and Bundle V3 API Specification
|
||||
id: buildV3
|
||||
if: steps.buildBeta.outcome == 'success'
|
||||
run: |
|
||||
swagger-cli bundle --dereference api-specs/idn/sailpoint-api.v3.yaml -t json -o anchor/src/routes/api-client/V3Spec.json
|
||||
|
||||
- name: Dereference and Bundle V2 API Specification
|
||||
id: buildV2
|
||||
if: steps.buildV3.outcome == 'success'
|
||||
run: |
|
||||
swagger-cli bundle --dereference api-specs/idn/sailpoint-api.v2.yaml -t json -o anchor/src/routes/api-client/V2Spec.json
|
||||
|
||||
- name: Dereference and Bundle CC API Specification
|
||||
id: buildCC
|
||||
if: steps.buildV2.outcome == 'success'
|
||||
run: |
|
||||
swagger-cli bundle --dereference api-specs/idn/sailpoint-api.cc.yaml -t json -o anchor/src/routes/api-client/CCSpec.json
|
||||
|
||||
- name: Install Dependencies
|
||||
id: installDeps
|
||||
if: steps.buildCC.outcome == 'success'
|
||||
run: |
|
||||
cd anchor
|
||||
pnpm install
|
||||
|
||||
- name: Build Extension
|
||||
id: buildExtension
|
||||
if: steps.installDeps.outcome == 'success'
|
||||
run: |
|
||||
cd anchor
|
||||
pnpm build
|
||||
|
||||
- name: Archive chrome-extension artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: anchor-${{ github.sha }}
|
||||
path: anchor-${{ github.event.pull_request.head.sha }}
|
||||
1
.npmrc
1
.npmrc
@@ -1,2 +1,3 @@
|
||||
engine-strict=true
|
||||
resolution-mode=highest
|
||||
enable-pre-post-scripts=true
|
||||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"prettier.documentSelectors": ["**/*.svelte"],
|
||||
"tailwindCSS.classAttributes": [
|
||||
"classes",
|
||||
"class",
|
||||
"accent",
|
||||
"active",
|
||||
|
||||
13
package.json
13
package.json
@@ -4,6 +4,7 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"prebuild": "curl https://raw.githubusercontent.com/sailpoint-oss/api-specs/main/dereferenced/deref-sailpoint-api.v3.yaml -o src/lib/V3Spec.yaml && curl https://raw.githubusercontent.com/sailpoint-oss/api-specs/main/dereferenced/deref-sailpoint-api.beta.yaml -o src/lib/BetaSpec.yaml && yq -o=json eval src/lib/V3Spec.yaml > src/routes/api-client/V3Spec.json && yq -o=json eval src/lib/BetaSpec.yaml > src/routes/api-client/BetaSpec.json",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
@@ -12,7 +13,9 @@
|
||||
"format": "prettier --plugin-search-dir . --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@floating-ui/dom": "^1.2.7",
|
||||
"@apidevtools/swagger-parser": "^10.1.0",
|
||||
"@floating-ui/dom": "^1.2.8",
|
||||
"@modyfi/vite-plugin-yaml": "^1.0.4",
|
||||
"@skeletonlabs/skeleton": "^1.5.1",
|
||||
"@sveltejs/adapter-auto": "^2.0.0",
|
||||
"@sveltejs/adapter-static": "^2.0.2",
|
||||
@@ -22,24 +25,28 @@
|
||||
"@types/chrome": "^0.0.235",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
"algoliasearch": "^4.17.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"axios": "^1.4.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-svelte": "^2.26.0",
|
||||
"highlight.js": "^11.8.0",
|
||||
"openapi-types": "^12.1.0",
|
||||
"postcss": "^8.4.23",
|
||||
"prettier": "^2.8.0",
|
||||
"prettier-plugin-svelte": "^2.8.1",
|
||||
"sailpoint-api-client": "^1.0.4",
|
||||
"svelte": "^3.54.0",
|
||||
"svelte-algolia-instantsearch": "^0.7.0",
|
||||
"svelte-check": "^3.0.1",
|
||||
"svelte-jsoneditor": "^0.17.3",
|
||||
"sveltekit-adapter-chrome-extension": "^2.0.0",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^4.3.0"
|
||||
"vite": "^4.3.0",
|
||||
"yaml": "^2.2.2"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
713
pnpm-lock.yaml
generated
713
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
169
src/app.d.ts
vendored
169
src/app.d.ts
vendored
@@ -8,20 +8,157 @@ declare namespace App {
|
||||
// interface Platform {}
|
||||
}
|
||||
|
||||
declare type IdnSession = {
|
||||
tenant?: string;
|
||||
authType?: string;
|
||||
baseUrl?: string;
|
||||
logoutUrl?: string;
|
||||
accessToken?: string;
|
||||
refreshIn?: number | string;
|
||||
pollUrl?: string;
|
||||
strongAuth?: boolean | string;
|
||||
strongAuthUrl?: string;
|
||||
csrfToken?: string;
|
||||
expiration?: date;
|
||||
org?: string;
|
||||
region?: string;
|
||||
pod?: string;
|
||||
layer?: string;
|
||||
declare type Status = {
|
||||
page: Page;
|
||||
status: Status2;
|
||||
};
|
||||
|
||||
declare type Page = {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
time_zone: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
declare type Status2 = {
|
||||
indicator: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
declare type IdnSession = {
|
||||
tenant: string;
|
||||
authType: string;
|
||||
baseUrl: string;
|
||||
logoutUrl: string;
|
||||
accessToken: string;
|
||||
refreshIn: number;
|
||||
pollUrl: string;
|
||||
strongAuth: boolean;
|
||||
strongAuthUrl: string;
|
||||
csrfToken: string;
|
||||
expiration: date;
|
||||
};
|
||||
|
||||
declare type HostingData = {
|
||||
org: string;
|
||||
pod: string;
|
||||
publicPod: string;
|
||||
layer: string;
|
||||
region: string;
|
||||
};
|
||||
|
||||
declare type TenantData = {
|
||||
id: string;
|
||||
alias: string;
|
||||
uid: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
uuid: string;
|
||||
encryptionKey: null;
|
||||
encryptionCheck: null;
|
||||
status: string;
|
||||
pending: boolean;
|
||||
passwordResetSinceLastLogin: boolean;
|
||||
usageCertAttested: null;
|
||||
userFlags: Meta;
|
||||
enabled: boolean;
|
||||
altAuthVia: string;
|
||||
altAuthViaIntegrationData: null;
|
||||
kbaAnswers: number;
|
||||
disablePasswordReset: boolean;
|
||||
ptaSourceId: null;
|
||||
supportsPasswordPush: boolean;
|
||||
attributes: Attributes;
|
||||
externalId: string;
|
||||
role: string[];
|
||||
phone: null;
|
||||
email: string;
|
||||
personalEmail: null;
|
||||
employeeNumber: null;
|
||||
riskScore: number;
|
||||
featureFlags: { [key: string]: boolean };
|
||||
feature: string[];
|
||||
orgEncryptionKey: string;
|
||||
orgEncryptionKeyId: string;
|
||||
meta: any;
|
||||
org: Org;
|
||||
stepUpAuth: boolean;
|
||||
bxInstallPrompted: boolean;
|
||||
federatedLogin: boolean;
|
||||
auth: Auth;
|
||||
onNetwork: boolean;
|
||||
onTrustedGeo: boolean;
|
||||
loginUrl: string;
|
||||
};
|
||||
|
||||
declare type Attributes = {
|
||||
lastLoginTimestamp: number;
|
||||
uid: string;
|
||||
firstname: string;
|
||||
cloudAuthoritativeSource: string;
|
||||
cloudStatus: string;
|
||||
displayName: string;
|
||||
internalCloudStatus: string;
|
||||
lastSyncDate: string;
|
||||
workPhone: string;
|
||||
email: string;
|
||||
lastname: string;
|
||||
};
|
||||
|
||||
declare type Auth = {
|
||||
service: string;
|
||||
encryption: string;
|
||||
};
|
||||
|
||||
declare type Org = {
|
||||
name: string;
|
||||
scriptName: string;
|
||||
mode: string;
|
||||
numQuestions: number;
|
||||
status: string;
|
||||
maxRegisteredUsers: number;
|
||||
pod: string;
|
||||
pwdResetPersonalPhone: boolean;
|
||||
pwdResetPersonalEmail: boolean;
|
||||
pwdResetKba: boolean;
|
||||
pwdResetEmail: boolean;
|
||||
pwdResetDuo: boolean;
|
||||
pwdResetPhoneMask: boolean;
|
||||
authErrorText: null;
|
||||
strongAuthKba: boolean;
|
||||
strongAuthPersonalPhone: boolean;
|
||||
strongAuthPersonalEmail: boolean;
|
||||
integrations: any[];
|
||||
productName: string;
|
||||
kbaReqForAuthn: number;
|
||||
kbaReqAnswers: number;
|
||||
lockoutAttemptThreshold: number;
|
||||
lockoutTimeMinutes: number;
|
||||
usageCertRequired: boolean;
|
||||
adminStrongAuthRequired: boolean;
|
||||
enableExternalPasswordChange: boolean;
|
||||
enablePasswordReplay: boolean;
|
||||
enableAutomaticPasswordReplay: boolean;
|
||||
notifyAuthenticationSettingChange: boolean;
|
||||
netmasks: null;
|
||||
countryCodes: null;
|
||||
whiteList: boolean;
|
||||
usernameEmptyText: null;
|
||||
usernameLabel: null;
|
||||
enableAutomationGeneration: boolean;
|
||||
emailTestMode: boolean;
|
||||
emailTestAddress: string;
|
||||
orgType: string;
|
||||
passwordReplayState: string;
|
||||
systemNotificationConfig: string;
|
||||
maxClusterDebugHours: string;
|
||||
brandName: string;
|
||||
logo: null;
|
||||
emailFromAddress: string;
|
||||
standardLogoUrl: null;
|
||||
narrowLogoUrl: null;
|
||||
actionButtonColor: string;
|
||||
activeLinkColor: string;
|
||||
navigationColor: string;
|
||||
};
|
||||
|
||||
@@ -3,3 +3,7 @@ html,
|
||||
body {
|
||||
@apply h-[600px] w-[800px];
|
||||
}
|
||||
|
||||
.ais-Hits-list {
|
||||
@apply flex flex-col gap-1;
|
||||
}
|
||||
|
||||
161443
src/lib/BetaSpec.yaml
Normal file
161443
src/lib/BetaSpec.yaml
Normal file
File diff suppressed because it is too large
Load Diff
28
src/lib/Components/Clear.svelte
Normal file
28
src/lib/Components/Clear.svelte
Normal file
@@ -0,0 +1,28 @@
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg
|
||||
fill="#000000"
|
||||
height="800px"
|
||||
width="800px"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 489.425 489.425"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<g>
|
||||
<g>
|
||||
<path
|
||||
d="M122.825,394.663c17.8,19.4,43.2,30.6,69.5,30.6h216.9c44.2,0,80.2-36,80.2-80.2v-200.7c0-44.2-36-80.2-80.2-80.2h-216.9
|
||||
c-26.4,0-51.7,11.1-69.5,30.6l-111.8,121.7c-14.7,16.1-14.7,40.3,0,56.4L122.825,394.663z M29.125,233.063l111.8-121.8
|
||||
c13.2-14.4,32-22.6,51.5-22.6h216.9c30.7,0,55.7,25,55.7,55.7v200.6c0,30.7-25,55.7-55.7,55.7h-217c-19.5,0-38.3-8.2-51.5-22.6
|
||||
l-111.7-121.8C23.025,249.663,23.025,239.663,29.125,233.063z"
|
||||
/>
|
||||
<path
|
||||
d="M225.425,309.763c2.4,2.4,5.5,3.6,8.7,3.6s6.3-1.2,8.7-3.6l47.8-47.8l47.8,47.8c2.4,2.4,5.5,3.6,8.7,3.6s6.3-1.2,8.7-3.6
|
||||
c4.8-4.8,4.8-12.5,0-17.3l-47.9-47.8l47.8-47.8c4.8-4.8,4.8-12.5,0-17.3s-12.5-4.8-17.3,0l-47.8,47.8l-47.8-47.8
|
||||
c-4.8-4.8-12.5-4.8-17.3,0s-4.8,12.5,0,17.3l47.8,47.8l-47.8,47.8C220.725,297.263,220.725,304.962,225.425,309.763z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
20
src/lib/Components/CurrentUser.svelte
Normal file
20
src/lib/Components/CurrentUser.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export let tenantData: Writable<TenantData>;
|
||||
</script>
|
||||
|
||||
<div class="p-2 card variant-soft-surface min-w-[150px]">
|
||||
<p class="underline text-center pb-2">Current User</p>
|
||||
<div class="flex flex-row justify-between">
|
||||
<p class="text-sm">Account:</p>
|
||||
<p class="text-sm">{$tenantData.uid}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-between">
|
||||
<p class="text-sm pb-2">Roles:</p>
|
||||
{#each $tenantData.role as role}
|
||||
<p class="text-xs">{role}</p>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
25
src/lib/Components/HostingData.svelte
Normal file
25
src/lib/Components/HostingData.svelte
Normal file
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export let hostingData: Writable<HostingData>;
|
||||
</script>
|
||||
|
||||
<div class="p-2 card variant-soft-surface min-w-[150px]">
|
||||
<p class="underline text-center pb-2">Hosting Data</p>
|
||||
<div class="flex flex-row justify-between">
|
||||
<p class="text-sm">Org:</p>
|
||||
<p class="text-sm">{$hostingData.org}</p>
|
||||
</div>
|
||||
<div class="flex flex-row justify-between">
|
||||
<p class="text-sm">Pod:</p>
|
||||
<p class="text-sm">{$hostingData.pod}</p>
|
||||
</div>
|
||||
<div class="flex flex-row justify-between">
|
||||
<p class="text-sm">Layer:</p>
|
||||
<p class="text-sm">{$hostingData.layer}</p>
|
||||
</div>
|
||||
<div class="flex flex-row justify-between">
|
||||
<p class="text-sm">Region:</p>
|
||||
<p class="text-sm">{$hostingData.region}</p>
|
||||
</div>
|
||||
</div>
|
||||
54
src/lib/Components/Resources.svelte
Normal file
54
src/lib/Components/Resources.svelte
Normal file
@@ -0,0 +1,54 @@
|
||||
<script lang="ts">
|
||||
import { popup, type PopupSettings } from '@skeletonlabs/skeleton';
|
||||
|
||||
const links: { label: string; href: string }[] = [
|
||||
{
|
||||
label: '🖥 Developer Community',
|
||||
href: 'https://developer.sailpoint.com/discuss/'
|
||||
},
|
||||
{ label: '📎 API Documentation', href: 'https://developer.sailpoint.com/idn/api/v3' },
|
||||
{ label: '🧘♂️ CLI Documentation', href: 'https://developer.sailpoint.com/idn/tools/cli' },
|
||||
{
|
||||
label: '🔌 Connector Reference',
|
||||
href: 'https://community.sailpoint.com/t5/IdentityNow-Connectors/IdentityNow-Connectors/ta-p/80019'
|
||||
},
|
||||
{
|
||||
label: '🚩 Transform Guides',
|
||||
href: 'https://community.sailpoint.com/t5/Search/bd-p/search?searchString=%22IdentityNow+Transforms+-%22'
|
||||
},
|
||||
{
|
||||
label: '🧭 Compass',
|
||||
href: 'https://community.sailpoint.com'
|
||||
},
|
||||
{
|
||||
label: '🔏 User Level Access Matrix',
|
||||
href: 'https://documentation.sailpoint.com/saas/help/common/users/user_level_matrix.html'
|
||||
}
|
||||
];
|
||||
|
||||
const popupResources: PopupSettings = {
|
||||
event: 'click',
|
||||
target: 'popupResources',
|
||||
placement: 'bottom',
|
||||
closeQuery: '.listbox-item'
|
||||
};
|
||||
</script>
|
||||
|
||||
<button class="btn btn-sm variant-ghost-primary" use:popup={popupResources}>Resources</button>
|
||||
|
||||
<div class="p-2 card" data-popup="popupResources">
|
||||
<ul class="flex flex-col justify-center gap-2">
|
||||
{#each links as link}
|
||||
<li class="listbox-item">
|
||||
<a
|
||||
class="hover:underline text-center hover:text-tertiary-600"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={link.href}
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
111
src/lib/Components/Search.svelte
Normal file
111
src/lib/Components/Search.svelte
Normal file
@@ -0,0 +1,111 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
InstantSearch,
|
||||
SearchBox,
|
||||
Hits,
|
||||
Pagination,
|
||||
HitsPerPage
|
||||
} from 'svelte-algolia-instantsearch';
|
||||
import algoliasearch from 'algoliasearch/lite';
|
||||
import { RadioGroup, RadioItem, popup, type PopupSettings } from '@skeletonlabs/skeleton';
|
||||
import SearchIcon from './searchIcon.svelte';
|
||||
|
||||
const searchClient = algoliasearch('TB01H1DFAM', '726952a7a9389c484b6c96808a3e0010');
|
||||
|
||||
// Available Indexes
|
||||
// prod_DEVELOPER_SAILPOINT_COM
|
||||
// discourse-posts
|
||||
|
||||
let index: string = 'prod_DEVELOPER_SAILPOINT_COM';
|
||||
|
||||
const popupFocusBlur: PopupSettings = {
|
||||
event: 'focus-click',
|
||||
target: 'popupFocusBlur',
|
||||
placement: 'bottom',
|
||||
closeQuery: ''
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="px-2 py-4">
|
||||
{#key index}
|
||||
<InstantSearch indexName={index} {searchClient}>
|
||||
<div class="flex flex-col gap-1 h-full">
|
||||
<div class="flex flex-row">
|
||||
<div use:popup={popupFocusBlur} class="grow relative">
|
||||
<SearchIcon />
|
||||
<SearchBox
|
||||
placeholder="Search the {index === 'prod_DEVELOPER_SAILPOINT_COM' ? 'Docs' : 'Forum'}"
|
||||
classes={{
|
||||
input: 'input rounded-r-none pl-12',
|
||||
form: '',
|
||||
resetIcon: 'white',
|
||||
submit: 'hidden',
|
||||
reset: 'hidden',
|
||||
root: 'grow w-full'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<select bind:value={index} class="rounded-l-none rounded-full select w-fit">
|
||||
<option value="prod_DEVELOPER_SAILPOINT_COM">Docs</option>
|
||||
<option value="discourse-posts">Forum</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="card max-w-[785px] p-2" data-popup="popupFocusBlur">
|
||||
<div class="flex overflow-y-scroll overflow-hidden max-h-[300px] grow">
|
||||
<Hits let:hit classes={{ root: 'grow' }}>
|
||||
{#if hit.hierarchy}
|
||||
<a href={hit.url}>
|
||||
<div
|
||||
class="flex flex-col card variant-soft-surface overflow-hidden p-2 w-[765px]"
|
||||
>
|
||||
<div class="flex flex-row justify-start gap-2">
|
||||
<p class="truncate">
|
||||
{hit.hierarchy.lvl1} - {hit.hierarchy.lvl2}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p class="text-xs opacity-70">Tags: {hit.tags.join(', ')}</p>
|
||||
<!-- <p class="text-xs text-primary-300 opacity-70">{hit.url_without_anchor}</p> -->
|
||||
</div>
|
||||
</a>
|
||||
{:else}
|
||||
<a href={hit.topic.url}>
|
||||
<div class="flex flex-col card variant-soft-surface p-2 max-w-[765px] w-[765px]">
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="flex flex-col">
|
||||
<p class="truncate max-w-[650px]">
|
||||
{hit.topic.title}
|
||||
</p>
|
||||
<p class="text-xs opacity-70 pb-2">
|
||||
{hit.user.name}
|
||||
</p>
|
||||
</div>
|
||||
<p class=" top-1 right-1">{hit.topic.views} 👁️🗨️</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-between">
|
||||
{#if hit.topic.tags.length > 0}
|
||||
<p class="text-xs opacity-70">Tags: {hit.topic.tags.join(', ')}</p>
|
||||
{:else}
|
||||
<p class="text-xs opacity-70">No Tags</p>
|
||||
{/if}
|
||||
<!-- <p class="text-xs text-primary-300 opacity-70 truncate max-w-[550px]">
|
||||
{hit.topic.url}
|
||||
</p> -->
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{/if}
|
||||
</Hits>
|
||||
</div>
|
||||
<div class="flex flex-row justify-center grow">
|
||||
<div class="flex flex-col justify-center grow">
|
||||
<Pagination classes={{ list: 'flex flex-row justify-center gap-2 text-xl' }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</InstantSearch>
|
||||
{/key}
|
||||
</div>
|
||||
37
src/lib/Components/StatusPage.svelte
Normal file
37
src/lib/Components/StatusPage.svelte
Normal file
@@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let resp: Promise<Status>;
|
||||
|
||||
const getStatus = async () => {
|
||||
console.debug('Getting Status Page Details');
|
||||
resp = await (await fetch('https://status.sailpoint.com/api/v2/status.json')).json();
|
||||
console.debug(resp);
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
getStatus();
|
||||
setInterval(() => getStatus(), 10000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="p-2 card variant-soft-surface min-w-[183.53px]">
|
||||
<p class="underline text-center pb-2">Status Page</p>
|
||||
<div class="flex flex-row gap-2 justify-center">
|
||||
{#await resp}
|
||||
<div class="placeholder-circle w-16" />
|
||||
<p>Checking</p>
|
||||
{:then status}
|
||||
{#if status?.status?.description == 'All Systems Operational'}
|
||||
<p class="text-green-500 text-center">All Systems Operational</p>
|
||||
{:else}
|
||||
<div>
|
||||
<p class="text-red-500 text-center">Ongoing Issues</p>
|
||||
<a href="https://status.sailpoint.com" rel="noreferrer" target="_blank">
|
||||
Click for details
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
</div>
|
||||
26
src/lib/Components/Support.svelte
Normal file
26
src/lib/Components/Support.svelte
Normal file
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { labelSort } from '$lib/utilities';
|
||||
|
||||
const links: { label: string; href: string }[] = [
|
||||
{
|
||||
label: '🎫 Submit a ticket',
|
||||
href: 'https://support.sailpoint.com/csm?id=sc_cat_item&sys_id=a78364e81bec151050bcc8866e4bcb5c&referrer=popular_items'
|
||||
},
|
||||
{
|
||||
label: '🔭 Scope of SaaS Support',
|
||||
href: 'https://community.sailpoint.com/t5/IdentityNow-Wiki/What-is-supported-by-SaaS-Support/ta-p/198779'
|
||||
},
|
||||
{ label: '🔖 Support Knowledge Base', href: 'https://support.sailpoint.com/' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="p-2 card variant-soft-surface">
|
||||
<p class="underline text-center pb-2">Support</p>
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each links as link}
|
||||
<a class="btn btn-sm variant-filled" target="_blank" rel="noreferrer" href={link.href}>
|
||||
{link.label}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
27
src/lib/Components/TenantData.svelte
Normal file
27
src/lib/Components/TenantData.svelte
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export let tenantData: Writable<TenantData>;
|
||||
</script>
|
||||
|
||||
<div class="p-2 card variant-soft-surface min-w-[150px]">
|
||||
<p class="underline text-center pb-2">Tenant Data</p>
|
||||
<div class="flex flex-row flex-wrap justify-between">
|
||||
<p class="text-sm">Name:</p>
|
||||
<p class="text-sm px-1">{$tenantData.org.name}</p>
|
||||
</div>
|
||||
<div class="flex flex-row justify-between">
|
||||
<p class="text-sm">Tenant:</p>
|
||||
<p class="text-sm">{$tenantData.org.scriptName}</p>
|
||||
</div>
|
||||
<div class="flex flex-row justify-between">
|
||||
<p class="text-sm">Tenant Type:</p>
|
||||
<p class="text-sm">{$tenantData.org.orgType}</p>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<p class="text-sm pb-2">Enabled Features:</p>
|
||||
{#each $tenantData.feature as feature}
|
||||
<p class="text-xs">{feature}</p>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
50
src/lib/Components/TenantLinks.svelte
Normal file
50
src/lib/Components/TenantLinks.svelte
Normal file
@@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
import { popup, type PopupSettings } from '@skeletonlabs/skeleton';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export let idnSession: Writable<IdnSession>;
|
||||
|
||||
const links: { label: string; slug: string }[] = [
|
||||
{ label: '🔒 Grant Tenant Access', slug: '/ui/a/admin/global/grant-tenant-access' },
|
||||
{
|
||||
label: '🏠 Dashboard',
|
||||
slug: '/ui/admin#admin:dashboard:overview'
|
||||
},
|
||||
{ label: '🧔 Identity Profiles', slug: '/ui/admin#admin:identities:profiles' },
|
||||
{ label: '🧮 Identity List', slug: '/ui/a/admin/identities/all-identities' },
|
||||
{ label: '📁 Access Profiles', slug: '/ui/a/admin/access/access-profiles/landing' },
|
||||
{ label: '📦 Roles', slug: '/ui/a/admin/access/roles/landing-page' },
|
||||
{ label: '🍱 Sources', slug: '/ui/a/admin/connections/sources-list/configured-sources' },
|
||||
{
|
||||
label: '🪛 Virtual Appliances',
|
||||
slug: '/ui/a/admin/connections/virtual-appliances/clusters-list'
|
||||
},
|
||||
{ label: '🔍 Search Tenant', slug: '/ui/search/search' }
|
||||
];
|
||||
|
||||
const popupTenantLinks: PopupSettings = {
|
||||
event: 'click',
|
||||
target: 'popupTenantLinks',
|
||||
placement: 'bottom',
|
||||
closeQuery: '.listbox-item'
|
||||
};
|
||||
</script>
|
||||
|
||||
<button class="btn btn-sm variant-ghost-primary" use:popup={popupTenantLinks}>Tenant Links</button>
|
||||
|
||||
<div class="p-2 card" data-popup="popupTenantLinks">
|
||||
<ul class="flex flex-col gap-2">
|
||||
{#each links as link}
|
||||
<li class="listbox-item">
|
||||
<a
|
||||
class="hover:underline text-center hover:text-tertiary-600"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={$idnSession.tenant || 'https://placeholder.com' + link.slug}
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
17
src/lib/Components/searchIcon.svelte
Normal file
17
src/lib/Components/searchIcon.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
export let color = 'white';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class="absolute h-8 left-0 top-0 bottom-0 z-50 mt-1 pt-1 has:focus:hidden"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...$$restProps}
|
||||
viewBox="0 0 50 50"
|
||||
width="50px"
|
||||
height="50px"
|
||||
fill={color}
|
||||
>
|
||||
<path
|
||||
d="M 21 3 C 11.621094 3 4 10.621094 4 20 C 4 29.378906 11.621094 37 21 37 C 24.710938 37 28.140625 35.804688 30.9375 33.78125 L 44.09375 46.90625 L 46.90625 44.09375 L 33.90625 31.0625 C 36.460938 28.085938 38 24.222656 38 20 C 38 10.621094 30.378906 3 21 3 Z M 21 5 C 29.296875 5 36 11.703125 36 20 C 36 28.296875 29.296875 35 21 35 C 12.703125 35 6 28.296875 6 20 C 6 11.703125 12.703125 5 21 5 Z"
|
||||
/>
|
||||
</svg>
|
||||
121060
src/lib/V3Spec.yaml
Normal file
121060
src/lib/V3Spec.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,77 +1,136 @@
|
||||
import axios from 'axios';
|
||||
import { idnSession } from './settings';
|
||||
import {
|
||||
hostingData,
|
||||
idnSession,
|
||||
noHostingData,
|
||||
noSession,
|
||||
noTenantData,
|
||||
tenantData
|
||||
} from './settings';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
// Gets currently active tab from Chrome via Extension API
|
||||
export async function getActiveTabURL() {
|
||||
const tabs = await chrome.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
});
|
||||
|
||||
if (tabs.length === 0) {
|
||||
console.debug('No Tabs returned, Returning');
|
||||
return null;
|
||||
if (tabs.length < 1) {
|
||||
throw new Error('No tabs returned');
|
||||
}
|
||||
|
||||
const activeTab = tabs[0];
|
||||
|
||||
if (!activeTab || !activeTab.url) {
|
||||
console.debug('No ActiveTab, Returning');
|
||||
return null;
|
||||
throw new Error('No active tab');
|
||||
}
|
||||
|
||||
return new URL(activeTab.url);
|
||||
}
|
||||
|
||||
export async function checkAuth() {
|
||||
console.debug('Getting Session - ' + new Date().toLocaleTimeString());
|
||||
// retrieve the hosting data for the tenant from the API
|
||||
export async function getHostingData(session: IdnSession) {
|
||||
console.debug('Retrieving Hosting Data');
|
||||
const resp = await fetch(`${session.baseUrl}/beta/tenant-data/hosting-data`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.accessToken}`
|
||||
}
|
||||
});
|
||||
|
||||
if (resp.status === 401) return noHostingData;
|
||||
|
||||
const hostingData = (await resp.json()) satisfies HostingData;
|
||||
console.debug(hostingData);
|
||||
|
||||
return hostingData;
|
||||
}
|
||||
|
||||
// retrieve the tenant data for the tenant from the API
|
||||
export async function getTenantData(session: IdnSession) {
|
||||
console.debug('Retrieving Tenant Data');
|
||||
const resp = await fetch(`${session.baseUrl}/cc/api/user/get`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.accessToken}`
|
||||
}
|
||||
});
|
||||
|
||||
if (resp.status === 401) return noTenantData;
|
||||
|
||||
const tenantData = (await resp.json()) satisfies TenantData;
|
||||
console.debug(tenantData);
|
||||
|
||||
return tenantData;
|
||||
}
|
||||
|
||||
// Check for a current session
|
||||
export async function checkSession() {
|
||||
console.debug('Checking Session - ' + new Date().toLocaleTimeString());
|
||||
|
||||
let session;
|
||||
let tabUrl;
|
||||
|
||||
try {
|
||||
tabUrl = await getActiveTabURL();
|
||||
if (!tabUrl) {
|
||||
throw new Error('No Active Tab');
|
||||
}
|
||||
session = await axios.get(tabUrl.origin + '/ui/session');
|
||||
console.debug('Current page is a valid IDN Tenant');
|
||||
} catch (error) {
|
||||
const tenant = get(idnSession).tenant;
|
||||
if (tenant) {
|
||||
tabUrl = new URL(tenant);
|
||||
session = await axios.get(tenant + '/ui/session');
|
||||
console.debug('Using cached session');
|
||||
} else {
|
||||
console.debug('No Session, and Current Tab is not an IDN Tenant');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (window.chrome && chrome.runtime && chrome.runtime.id) {
|
||||
let tabUrl;
|
||||
|
||||
console.debug('Setting timeout for ' + session.data.refreshIn + ' milliseconds');
|
||||
setTimeout(() => checkAuth(), session.data.refreshIn);
|
||||
|
||||
const sessionData = {
|
||||
...session.data,
|
||||
expiration: new Date(Date.now() + session.data.refreshIn)
|
||||
};
|
||||
|
||||
try {
|
||||
const hostingData = await axios.get(`${session.data.baseUrl}/beta/tenant-data/hosting-data`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.data.accessToken}`
|
||||
try {
|
||||
tabUrl = await getActiveTabURL();
|
||||
session = await (await fetch(tabUrl.origin + '/ui/session')).json();
|
||||
console.debug('Current page is a valid IDN Tenant');
|
||||
} catch (error) {
|
||||
const tenant = get(idnSession).tenant;
|
||||
if (tenant) {
|
||||
tabUrl = new URL(tenant);
|
||||
const sessionResp = await fetch(tenant + '/ui/session').catch((err: Error) =>
|
||||
console.debug(err)
|
||||
);
|
||||
if (!sessionResp) return;
|
||||
session = await sessionResp.json().catch((err: Error) => console.debug(err));
|
||||
console.debug('Using cached session');
|
||||
} else {
|
||||
console.debug('No Session, and Current Tab is not an IDN Tenant');
|
||||
session = noSession;
|
||||
}
|
||||
});
|
||||
}
|
||||
console.debug('Checking Session again in ' + session.refreshIn + ' milliseconds');
|
||||
setTimeout(() => checkSession(), session.refreshIn);
|
||||
} else {
|
||||
console.debug('Using Dev Session');
|
||||
const tenant = import.meta.env.VITE_TENANT;
|
||||
|
||||
sessionData.tenant = tabUrl.origin;
|
||||
sessionData.org = hostingData.data.org;
|
||||
sessionData.pod = hostingData.data.pod;
|
||||
sessionData.layer = hostingData.data.layer;
|
||||
sessionData.region = hostingData.data.region;
|
||||
const accessTokenResp = await fetch(
|
||||
`https://${tenant}.api.identitynow.com/oauth/token?grant_type=client_credentials&client_id=${
|
||||
import.meta.env.VITE_CLIENT_ID
|
||||
}&client_secret=${import.meta.env.VITE_CLIENT_SECRET}`,
|
||||
{ method: 'POST' }
|
||||
);
|
||||
|
||||
idnSession.set(sessionData);
|
||||
} catch (error) {
|
||||
console.error('Error fetching hosting data:', error);
|
||||
const accessTokenData = await accessTokenResp.json();
|
||||
console.debug(accessTokenData);
|
||||
|
||||
session = {
|
||||
authType: 'OAuth2.0',
|
||||
baseUrl: `https://${tenant}.api.identitynow.com`,
|
||||
logoutUrl: `https://${tenant}.identitynow.com/logout`,
|
||||
accessToken: accessTokenData.access_token,
|
||||
refreshIn: accessTokenData.expires_in,
|
||||
pollUrl: `https://${tenant}.identitynow.com/ui/session`,
|
||||
strongAuth: accessTokenData.strong_auth,
|
||||
strongAuthUrl: `https://${tenant}.identitynow.com/api/user/strongAuthn`,
|
||||
csrfToken: ''
|
||||
};
|
||||
console.debug('Checking Session again in ' + session.refreshIn + ' milliseconds');
|
||||
setTimeout(() => checkSession(), session.refreshIn);
|
||||
}
|
||||
|
||||
console.debug('Session Data');
|
||||
console.debug(session);
|
||||
|
||||
hostingData.set(await getHostingData(session));
|
||||
tenantData.set(await getTenantData(session));
|
||||
idnSession.set({
|
||||
...session,
|
||||
expiration: new Date(Date.now() + session.refreshIn),
|
||||
tenant: new URL(session.pollUrl).origin
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,137 @@
|
||||
import { localStorageStore } from '@skeletonlabs/skeleton';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
|
||||
export const idnSession: Writable<IdnSession> = localStorageStore('tenantData', {
|
||||
tenant: 'https://whatever.com'
|
||||
});
|
||||
export const noSession: IdnSession = {
|
||||
tenant: '',
|
||||
authType: '',
|
||||
baseUrl: '',
|
||||
logoutUrl: '',
|
||||
accessToken: '',
|
||||
refreshIn: 30000,
|
||||
pollUrl: '',
|
||||
strongAuth: false,
|
||||
strongAuthUrl: '',
|
||||
csrfToken: '',
|
||||
expiration: new Date()
|
||||
};
|
||||
|
||||
export const noHostingData: HostingData = {
|
||||
org: 'No Data',
|
||||
pod: 'No Data',
|
||||
publicPod: 'No Data',
|
||||
layer: 'No Data',
|
||||
region: 'No Data'
|
||||
};
|
||||
|
||||
export const noTenantData: TenantData = {
|
||||
id: '',
|
||||
alias: '',
|
||||
uid: '',
|
||||
name: '',
|
||||
displayName: '',
|
||||
uuid: '',
|
||||
encryptionKey: null,
|
||||
encryptionCheck: null,
|
||||
status: '',
|
||||
pending: false,
|
||||
passwordResetSinceLastLogin: false,
|
||||
usageCertAttested: null,
|
||||
userFlags: {},
|
||||
enabled: false,
|
||||
altAuthVia: '',
|
||||
altAuthViaIntegrationData: null,
|
||||
kbaAnswers: 0,
|
||||
disablePasswordReset: false,
|
||||
ptaSourceId: null,
|
||||
supportsPasswordPush: false,
|
||||
attributes: {
|
||||
lastLoginTimestamp: 0,
|
||||
uid: '',
|
||||
firstname: '',
|
||||
cloudAuthoritativeSource: '',
|
||||
cloudStatus: '',
|
||||
displayName: '',
|
||||
internalCloudStatus: '',
|
||||
lastSyncDate: '',
|
||||
workPhone: '',
|
||||
email: '',
|
||||
lastname: ''
|
||||
},
|
||||
externalId: '',
|
||||
role: [],
|
||||
phone: null,
|
||||
email: '',
|
||||
personalEmail: null,
|
||||
employeeNumber: null,
|
||||
riskScore: 0,
|
||||
featureFlags: {},
|
||||
feature: [],
|
||||
orgEncryptionKey: '',
|
||||
orgEncryptionKeyId: '',
|
||||
meta: {},
|
||||
org: {
|
||||
name: '',
|
||||
scriptName: '',
|
||||
mode: '',
|
||||
numQuestions: 9,
|
||||
status: '',
|
||||
maxRegisteredUsers: 0,
|
||||
pod: '',
|
||||
pwdResetPersonalPhone: false,
|
||||
pwdResetPersonalEmail: false,
|
||||
pwdResetKba: false,
|
||||
pwdResetEmail: false,
|
||||
pwdResetDuo: false,
|
||||
pwdResetPhoneMask: false,
|
||||
authErrorText: null,
|
||||
strongAuthKba: false,
|
||||
strongAuthPersonalPhone: false,
|
||||
strongAuthPersonalEmail: false,
|
||||
integrations: [],
|
||||
productName: '',
|
||||
kbaReqForAuthn: 0,
|
||||
kbaReqAnswers: 0,
|
||||
lockoutAttemptThreshold: 0,
|
||||
lockoutTimeMinutes: 0,
|
||||
usageCertRequired: false,
|
||||
adminStrongAuthRequired: false,
|
||||
enableExternalPasswordChange: false,
|
||||
enablePasswordReplay: false,
|
||||
enableAutomaticPasswordReplay: false,
|
||||
notifyAuthenticationSettingChange: false,
|
||||
netmasks: null,
|
||||
countryCodes: null,
|
||||
whiteList: false,
|
||||
usernameEmptyText: null,
|
||||
usernameLabel: null,
|
||||
enableAutomationGeneration: false,
|
||||
emailTestMode: false,
|
||||
emailTestAddress: '',
|
||||
orgType: '',
|
||||
passwordReplayState: '',
|
||||
systemNotificationConfig: '',
|
||||
maxClusterDebugHours: '',
|
||||
brandName: '',
|
||||
logo: null,
|
||||
emailFromAddress: '',
|
||||
standardLogoUrl: null,
|
||||
narrowLogoUrl: null,
|
||||
actionButtonColor: '',
|
||||
activeLinkColor: '',
|
||||
navigationColor: ''
|
||||
},
|
||||
stepUpAuth: false,
|
||||
bxInstallPrompted: false,
|
||||
federatedLogin: false,
|
||||
auth: {
|
||||
service: '',
|
||||
encryption: ''
|
||||
},
|
||||
onNetwork: false,
|
||||
onTrustedGeo: false,
|
||||
loginUrl: ''
|
||||
};
|
||||
|
||||
export const idnSession: Writable<IdnSession> = localStorageStore('tenantData', noSession);
|
||||
export const hostingData: Writable<HostingData> = writable(noHostingData);
|
||||
export const tenantData: Writable<TenantData> = writable(noTenantData);
|
||||
|
||||
1
src/lib/utilities.ts
Normal file
1
src/lib/utilities.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const labelSort = (a: any, b: any) => a.label.localeCompare(b.label);
|
||||
@@ -7,17 +7,22 @@
|
||||
import '../app.postcss';
|
||||
|
||||
import { AppBar, AppShell, storeHighlightJs } from '@skeletonlabs/skeleton';
|
||||
|
||||
import { checkAuth } from '$lib/authentication';
|
||||
import { checkSession } from '$lib/authentication';
|
||||
import { idnSession } from '$lib/settings';
|
||||
import { arrow, autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';
|
||||
import { storePopup } from '@skeletonlabs/skeleton';
|
||||
import dayjs from 'dayjs';
|
||||
import hljs from 'highlight.js';
|
||||
import 'highlight.js/styles/github-dark.css';
|
||||
import { onMount } from 'svelte';
|
||||
import TenantLinks from '$lib/Components/TenantLinks.svelte';
|
||||
import { page } from '$app/stores';
|
||||
import Resources from '$lib/Components/Resources.svelte';
|
||||
|
||||
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
|
||||
storeHighlightJs.set(hljs);
|
||||
|
||||
onMount(async () => checkAuth());
|
||||
onMount(async () => checkSession());
|
||||
|
||||
let now = dayjs();
|
||||
let minutesUntil = dayjs($idnSession?.expiration).diff(now, 'minutes');
|
||||
@@ -35,10 +40,17 @@
|
||||
<AppBar>
|
||||
<svelte:fragment slot="lead">
|
||||
<div class="flex flex-row gap-4">
|
||||
<a class="btn btn-sm variant-filled" href="/">Tenant</a>
|
||||
<a class="btn btn-sm variant-filled" href="/session">Session</a>
|
||||
<a class:text-primary-400={$page.url.pathname === '/'} href="/">Tenant</a>
|
||||
<a class:text-primary-400={$page.url.pathname === '/api-client'} href="/api-client">
|
||||
API Client
|
||||
</a>
|
||||
<a class:text-primary-400={$page.url.pathname === '/session'} href="/session">Session</a>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<div class="flex flex-row justify-center gap-2">
|
||||
<TenantLinks {idnSession} />
|
||||
<Resources />
|
||||
</div>
|
||||
<svelte:fragment slot="trail">
|
||||
<div class="p-1 top-0 right-0 flex flex-row gap-2">
|
||||
{#if minutesUntil < 0 || secondsUntil < 0}
|
||||
@@ -53,7 +65,7 @@
|
||||
Strong Auth: {#if $idnSession.strongAuth === true}
|
||||
<span class="text-green-500">True</span>
|
||||
{:else}
|
||||
<span class="text-red-500">True</span>
|
||||
<span class="text-red-500">False</span>
|
||||
{/if}
|
||||
</p>
|
||||
<p class="text-xs text-white my-auto">
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { idnSession } from '$lib/settings';
|
||||
import CurrentUser from '$lib/Components/CurrentUser.svelte';
|
||||
import HostingData from '$lib/Components/HostingData.svelte';
|
||||
import Resources from '$lib/Components/Resources.svelte';
|
||||
import Support from '$lib/Components/Support.svelte';
|
||||
import StatusPage from '$lib/Components/StatusPage.svelte';
|
||||
import TenantData from '$lib/Components/TenantData.svelte';
|
||||
import TenantLinks from '$lib/Components/TenantLinks.svelte';
|
||||
import { idnSession, hostingData, tenantData } from '$lib/settings';
|
||||
import Search from '$lib/Components/Search.svelte';
|
||||
</script>
|
||||
|
||||
<div class="p-2 flex flex-row gap-2">
|
||||
<div class="p-1">
|
||||
<p class="underline text-lg">Tenant Info</p>
|
||||
<p class="text-sm">Tenant: {$idnSession.org}</p>
|
||||
<p class="text-sm">Region: {$idnSession.region}</p>
|
||||
<p class="text-sm">Pod: {$idnSession.pod}</p>
|
||||
</div>
|
||||
<div class="p-1">
|
||||
<p class="underline text-lg">Quick Links</p>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={$idnSession.tenant + '/ui/admin#admin:dashboard:overview'}
|
||||
>
|
||||
Tenant Dashboard
|
||||
</a>
|
||||
<div class="flex flex-col h-full">
|
||||
<Search />
|
||||
<div class="flex flex-row h-full gap-4 p-2 grow">
|
||||
<div class=" flex flex-col gap-4 grow">
|
||||
<TenantData {tenantData} />
|
||||
</div>
|
||||
|
||||
<div class=" flex flex-col gap-4 grow">
|
||||
<CurrentUser {tenantData} />
|
||||
<HostingData {hostingData} />
|
||||
</div>
|
||||
<div class=" flex flex-col gap-4 grow">
|
||||
<StatusPage />
|
||||
<Support />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
69
src/routes/api-client/+page.svelte
Normal file
69
src/routes/api-client/+page.svelte
Normal file
@@ -0,0 +1,69 @@
|
||||
<script lang="ts">
|
||||
import { Tab, TabGroup } from '@skeletonlabs/skeleton';
|
||||
import { JSONEditor } from 'svelte-jsoneditor';
|
||||
import 'svelte-jsoneditor/themes/jse-theme-dark.css';
|
||||
|
||||
import BetaSpec from './BetaSpec.json';
|
||||
import CustomSpec from './CustomSpec.json';
|
||||
import V3Spec from './V3Spec.json';
|
||||
|
||||
let APIVersions;
|
||||
|
||||
let specification = BetaSpec;
|
||||
let path = Object.entries(specification.paths)[0][0];
|
||||
|
||||
let requestContent = {
|
||||
text: undefined, // can be used to pass a stringified JSON document instead
|
||||
json: {
|
||||
request: 'All your base belong to us'
|
||||
}
|
||||
};
|
||||
|
||||
let responseContent = {
|
||||
text: undefined, // can be used to pass a stringified JSON document instead
|
||||
json: {
|
||||
response: 200
|
||||
}
|
||||
};
|
||||
|
||||
let tabSet: number = 0;
|
||||
|
||||
$: methods = Object.entries(specification.paths[path] || { get: 'content' });
|
||||
</script>
|
||||
|
||||
<div class="p-4 flex flex-col gap-4 h-full">
|
||||
<div class="flex flex-row">
|
||||
<select bind:value={specification} class="select w-fit rounded-r-none">
|
||||
<option value={BetaSpec}>Beta API</option>
|
||||
<option value={V3Spec}>V3 API</option>
|
||||
<option value={CustomSpec}>Custom</option>
|
||||
</select>
|
||||
<select bind:value={path} class="select rounded-l-none">
|
||||
{#each Object.entries(specification.paths) as [path, methods]}
|
||||
<option value={path}>{path}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<select class="select rounded-r-none w-fit">
|
||||
{#each methods as [method, content]}
|
||||
<option>{method.toUpperCase()}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<input bind:value={path} class="input rounded-l-none rounded-r-none pl-4" />
|
||||
<button class="btn variant-filled-surface rounded-l-none rounded-r-lg">Call</button>
|
||||
</div>
|
||||
<TabGroup justify="justify-center" class="grow">
|
||||
<Tab bind:group={tabSet} name="tab1" value={0}>Request</Tab>
|
||||
<Tab bind:group={tabSet} name="tab2" value={1}>Response</Tab>
|
||||
</TabGroup>
|
||||
{#if tabSet === 0}
|
||||
<div class="jse-theme-dark h-full">
|
||||
<JSONEditor bind:content={requestContent} />
|
||||
</div>
|
||||
{:else if tabSet === 1}
|
||||
<div class="jse-theme-dark h-full">
|
||||
<JSONEditor bind:content={responseContent} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
217521
src/routes/api-client/BetaSpec.json
Normal file
217521
src/routes/api-client/BetaSpec.json
Normal file
File diff suppressed because one or more lines are too long
12
src/routes/api-client/CustomSpec.json
Normal file
12
src/routes/api-client/CustomSpec.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"paths": {
|
||||
"/custom/api/endpoint": {
|
||||
"get": "content",
|
||||
"post": "content",
|
||||
"put": "content",
|
||||
"patch": "content",
|
||||
"delete": "content",
|
||||
"head": "content"
|
||||
}
|
||||
}
|
||||
}
|
||||
158903
src/routes/api-client/V3Spec.json
Normal file
158903
src/routes/api-client/V3Spec.json
Normal file
File diff suppressed because one or more lines are too long
@@ -3,6 +3,6 @@
|
||||
import { CodeBlock } from '@skeletonlabs/skeleton';
|
||||
</script>
|
||||
|
||||
<div class="p-1">
|
||||
<div class="p-4 h-full flex flex-col justify-center">
|
||||
<CodeBlock lineNumbers language="json" code={JSON.stringify($idnSession, null, ' ')} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user