Merge pull request #2 from LukeHagar/sveltekit-rewrite
Complete rewrite in sveltekit
13
.eslintignore
Normal file
@@ -0,0 +1,13 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
30
.eslintrc.cjs
Normal file
@@ -0,0 +1,30 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:svelte/recommended',
|
||||
'prettier'
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2020,
|
||||
extraFileExtensions: ['.svelte']
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.svelte'],
|
||||
parser: 'svelte-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
122
.github/workflows/build.yaml
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
name: Build and Upload Chrome Extension
|
||||
|
||||
run-name: ${{ github.actor }} is Building and Uploading a new Anchor Version 🚀
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
Build-And-Push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Extract branch name
|
||||
shell: bash
|
||||
run: echo "branch=$(echo ${GITHUB_REF#refs/heads/})" >>$GITHUB_OUTPUT
|
||||
id: extract_branch
|
||||
|
||||
# Checkout the main branch of this repo
|
||||
- name: Checkout PR branch
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: lukehagar/anchor
|
||||
path: anchor
|
||||
ref: ${{ steps.extract_branch.outputs.branch }}
|
||||
|
||||
# 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
|
||||
|
||||
- uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
repository: anchor/
|
||||
branch: ${{ steps.extract_branch.outputs.branch }}
|
||||
commit_message: Updated API Specifications
|
||||
file_pattern: 'src/routes/api-client/*.json'
|
||||
|
||||
- name: Install Dependencies
|
||||
id: installDeps
|
||||
if: steps.buildCC.outcome == 'success'
|
||||
run: |
|
||||
cd anchor
|
||||
yarn install
|
||||
|
||||
- name: Build Extension
|
||||
id: buildExtension
|
||||
if: steps.installDeps.outcome == 'success'
|
||||
run: |
|
||||
cd anchor
|
||||
yarn build
|
||||
zip -r anchor-${{ github.sha }}.zip build
|
||||
|
||||
- name: Archive chrome-extension artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: anchor-${{ github.sha }}
|
||||
path: anchor/anchor-${{ github.sha }}.zip
|
||||
|
||||
Upload-Extension:
|
||||
name: Upload extension
|
||||
runs-on: ubuntu-latest
|
||||
needs: Build-And-Push
|
||||
env:
|
||||
# you can optionally specify extension ID here, we do this so that
|
||||
# all of our environments (dev, staging, prod) have a consistent ID
|
||||
# we can reference. Otherwise the extension ID is autogenerated.
|
||||
EXTENSION_ID: opooagfnjoclbafkbgeeokllilaepiap
|
||||
|
||||
steps:
|
||||
- uses: actions/setup-node@v2-beta
|
||||
with:
|
||||
node-version: '16.10'
|
||||
|
||||
- name: Download bundle artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: anchor-${{ github.sha }}
|
||||
|
||||
- name: Install webstore cli
|
||||
run: |-
|
||||
npm install -g chrome-webstore-upload-cli
|
||||
|
||||
- name: Upload step
|
||||
run: |-
|
||||
chrome-webstore-upload upload --source anchor-${{ github.sha }}.zip --extension-id ${{ env.EXTENSION_ID }} --client-id ${{ secrets.CI_GOOGLE_CLIENT_ID }} --client-secret ${{ secrets.CI_GOOGLE_CLIENT_SECRET }} --refresh-token ${{ secrets.CI_GOOGLE_REFRESH_TOKEN }}
|
||||
31
.gitignore
vendored
@@ -1,23 +1,10 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
3
.npmrc
Normal file
@@ -0,0 +1,3 @@
|
||||
engine-strict=true
|
||||
resolution-mode=highest
|
||||
enable-pre-post-scripts=true
|
||||
13
.prettierignore
Normal file
@@ -0,0 +1,13 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
9
.prettierrc
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"pluginSearchDirs": ["."],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
98
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"prettier.documentSelectors": ["**/*.svelte"],
|
||||
"tailwindCSS.classAttributes": [
|
||||
"classes",
|
||||
"class",
|
||||
"accent",
|
||||
"active",
|
||||
"background",
|
||||
"badge",
|
||||
"bgBackdrop",
|
||||
"bgDark",
|
||||
"bgDrawer",
|
||||
"bgLight",
|
||||
"blur",
|
||||
"border",
|
||||
"button",
|
||||
"buttonAction",
|
||||
"buttonBack",
|
||||
"buttonClasses",
|
||||
"buttonComplete",
|
||||
"buttonDismiss",
|
||||
"buttonNeutral",
|
||||
"buttonNext",
|
||||
"buttonPositive",
|
||||
"buttonTextCancel",
|
||||
"buttonTextConfirm",
|
||||
"buttonTextNext",
|
||||
"buttonTextPrevious",
|
||||
"buttonTextSubmit",
|
||||
"caretClosed",
|
||||
"caretOpen",
|
||||
"chips",
|
||||
"color",
|
||||
"cursor",
|
||||
"display",
|
||||
"element",
|
||||
"fill",
|
||||
"fillDark",
|
||||
"fillLight",
|
||||
"flex",
|
||||
"gap",
|
||||
"gridColumns",
|
||||
"height",
|
||||
"hover",
|
||||
"invalid",
|
||||
"justify",
|
||||
"meter",
|
||||
"padding",
|
||||
"position",
|
||||
"regionBackdrop",
|
||||
"regionBody",
|
||||
"regionCaption",
|
||||
"regionCaret",
|
||||
"regionCell",
|
||||
"regionCone",
|
||||
"regionContent",
|
||||
"regionControl",
|
||||
"regionDefault",
|
||||
"regionDrawer",
|
||||
"regionFoot",
|
||||
"regionFooter",
|
||||
"regionHead",
|
||||
"regionHeader",
|
||||
"regionIcon",
|
||||
"regionInterface",
|
||||
"regionInterfaceText",
|
||||
"regionLabel",
|
||||
"regionLead",
|
||||
"regionLegend",
|
||||
"regionList",
|
||||
"regionNavigation",
|
||||
"regionPage",
|
||||
"regionPanel",
|
||||
"regionRowHeadline",
|
||||
"regionRowMain",
|
||||
"regionTrail",
|
||||
"ring",
|
||||
"rounded",
|
||||
"select",
|
||||
"shadow",
|
||||
"slotDefault",
|
||||
"slotFooter",
|
||||
"slotHeader",
|
||||
"slotLead",
|
||||
"slotMessage",
|
||||
"slotMeta",
|
||||
"slotPageContent",
|
||||
"slotPageFooter",
|
||||
"slotPageHeader",
|
||||
"slotSidebarLeft",
|
||||
"slotSidebarRight",
|
||||
"slotTrail",
|
||||
"spacing",
|
||||
"text",
|
||||
"track",
|
||||
"width"
|
||||
]
|
||||
}
|
||||
21
LICENSE.md
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 luke-hagar-sp
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
BIN
anchor.zip
|
Before Width: | Height: | Size: 426 B After Width: | Height: | Size: 426 B |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 747 B After Width: | Height: | Size: 747 B |
|
Before Width: | Height: | Size: 898 B After Width: | Height: | Size: 898 B |
BIN
favicon.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
images/anchor_12.png
Normal file
|
After Width: | Height: | Size: 426 B |
BIN
images/anchor_128.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
images/anchor_32.png
Normal file
|
After Width: | Height: | Size: 747 B |
BIN
images/anchor_48.png
Normal file
|
After Width: | Height: | Size: 898 B |
32751
package-lock.json
generated
93
package.json
@@ -1,44 +1,53 @@
|
||||
{
|
||||
"name": "anchor",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.9.3",
|
||||
"@emotion/styled": "^11.9.3",
|
||||
"@mui/material": "^5.8.7",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "INLINE_RUNTIME_CHUNK=false react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^2.7.1"
|
||||
}
|
||||
"name": "anchor",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
||||
"format": "prettier --plugin-search-dir . --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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",
|
||||
"@sveltejs/kit": "^1.5.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@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",
|
||||
"match-sorter": "^6.3.1",
|
||||
"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-check": "^3.0.1",
|
||||
"svelte-jsoneditor": "^0.17.3",
|
||||
"svelte-spotlight": "^1.0.14",
|
||||
"sveltekit-adapter-chrome-extension": "^2.0.0",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^4.3.0",
|
||||
"yaml": "^2.2.2"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
3086
pnpm-lock.yaml
generated
Normal file
6
postcss.config.cjs
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.8 KiB |
@@ -1,43 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
@@ -1,3 +0,0 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 67 KiB |
45
src/App.css
@@ -1,45 +0,0 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
display: block;
|
||||
width: 40%;
|
||||
pointer-events: none;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
min-width: 400px;
|
||||
}
|
||||
1067
src/App.js
@@ -1,8 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
164
src/app.d.ts
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
// and what to do when importing types
|
||||
declare namespace App {
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface Error {}
|
||||
// interface Platform {}
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
12
src/app.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover" data-theme="sailpoint">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
18
src/app.postcss
Normal file
@@ -0,0 +1,18 @@
|
||||
/*place global styles here */
|
||||
html,
|
||||
body {
|
||||
@apply !h-[600px] !w-[800px];
|
||||
}
|
||||
|
||||
.ais-Hits-list {
|
||||
@apply flex flex-col gap-1;
|
||||
}
|
||||
|
||||
.sl-results-list {
|
||||
@apply flex flex-col gap-1;
|
||||
}
|
||||
|
||||
.jse-main,
|
||||
.jse-theme-dark {
|
||||
@apply h-[335px];
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
17
src/index.js
@@ -1,17 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
161443
src/lib/BetaSpec.yaml
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 |
18
src/lib/Components/CurrentUser.svelte
Normal file
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export let tenantData: Writable<TenantData>;
|
||||
</script>
|
||||
|
||||
<h1 class="text-center pb-2">Current User</h1>
|
||||
<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>
|
||||
23
src/lib/Components/HostingData.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export let hostingData: Writable<HostingData>;
|
||||
</script>
|
||||
|
||||
<h1 class="text-center pb-2">Hosting Data</h1>
|
||||
<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>
|
||||
42
src/lib/Components/Resources.svelte
Normal file
@@ -0,0 +1,42 @@
|
||||
<script lang="ts">
|
||||
const resourcelinks: { 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'
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<h1 class="text-center">Resources</h1>
|
||||
<ul class="flex flex-col">
|
||||
{#each resourcelinks 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>
|
||||
103
src/lib/Components/Search.svelte
Normal file
@@ -0,0 +1,103 @@
|
||||
<!-- <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');
|
||||
|
||||
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>
|
||||
</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}
|
||||
</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> -->
|
||||
23
src/lib/Components/Spotlight/Item.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
export let result: any;
|
||||
export let selected: boolean;
|
||||
</script>
|
||||
|
||||
<a href={result.href} target="_blank">
|
||||
<div
|
||||
class="flex flex-col p-2 card {result.category === 'Docs'
|
||||
? 'variant-ringed-tertiary'
|
||||
: 'variant-ringed-success'}"
|
||||
>
|
||||
<div class="flex flex-row justify-between">
|
||||
<p class="max-w-[350px] truncate">{result.title || result.section}</p>
|
||||
<p class="text-xl opacity-70">{result.group}</p>
|
||||
</div>
|
||||
<p class="text-xs">
|
||||
{#if result.tags.length > 0}
|
||||
Tags: {result.tags.join(', ')}
|
||||
{/if}
|
||||
</p>
|
||||
<p class="opacity-60 text-xs truncate">{result.href}</p>
|
||||
</div>
|
||||
</a>
|
||||
86
src/lib/Components/Spotlight/SpotlightSearch.svelte
Normal file
@@ -0,0 +1,86 @@
|
||||
<script lang="ts">
|
||||
import { matchSorter } from 'match-sorter';
|
||||
import SvelteSpotlight from 'svelte-spotlight/SvelteSpotlight.svelte';
|
||||
import algoliasearch, { type SearchClient } from 'algoliasearch';
|
||||
import type { Hit, IndexHit } from '$lib/utilities';
|
||||
import { onMount } from 'svelte';
|
||||
import Item from './Item.svelte';
|
||||
|
||||
let isOpen: boolean = false;
|
||||
|
||||
let results: any = [];
|
||||
let query: string = '';
|
||||
let indices = ['prod_DEVELOPER_SAILPOINT_COM', 'discourse-posts'];
|
||||
|
||||
let preSelectedResult: Hit | undefined = undefined;
|
||||
let selectedResult: Hit | undefined = undefined;
|
||||
let client: SearchClient;
|
||||
|
||||
async function search(query: string) {
|
||||
if (client) {
|
||||
client.search<IndexHit>(indices.map((indexName) => ({ indexName, query }))).then((data) => {
|
||||
// @ts-ignore
|
||||
results = [];
|
||||
for (const result of data.results) {
|
||||
results = [
|
||||
...results,
|
||||
...result.hits.map((entry) => {
|
||||
if (entry.hierarchy) {
|
||||
return {
|
||||
objectID: entry.objectID,
|
||||
group: entry.hierarchy.lvl0,
|
||||
section: entry.hierarchy.lvl1,
|
||||
title: entry.hierarchy.lvl2,
|
||||
tags: entry.tags,
|
||||
href: entry.url,
|
||||
category: 'Docs'
|
||||
};
|
||||
} else if (entry.topic) {
|
||||
return {
|
||||
objectID: entry.objectID,
|
||||
group: 'Forum',
|
||||
section: entry.category.name,
|
||||
title: entry.topic.title,
|
||||
tags: entry.topic.tags,
|
||||
href: entry.topic.url,
|
||||
category: 'Forum'
|
||||
};
|
||||
}
|
||||
})
|
||||
];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$: search(query);
|
||||
|
||||
onMount(() => (client = algoliasearch('TB01H1DFAM', '726952a7a9389c484b6c96808a3e0010')));
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<button class="btn btn-sm variant-filled-surface" on:click={() => (isOpen = !isOpen)}>
|
||||
Search
|
||||
</button>
|
||||
|
||||
<SvelteSpotlight
|
||||
{results}
|
||||
bind:isOpen
|
||||
bind:preSelectedResult
|
||||
bind:selectedResult
|
||||
bind:query
|
||||
modalClass="w-[700px] max-h-[600px] overflow-hidden card !top-6"
|
||||
headerClass="p-4 justify-center items-center"
|
||||
inputClass="input bg-transparent"
|
||||
resultIdKey="objectID"
|
||||
resultsClass="py-2 px-2 overflow-y-scroll max-h-[400px]"
|
||||
contentClass="max-h-[400px]"
|
||||
>
|
||||
<Item slot="result" let:result let:selected {selected} {result} />
|
||||
<div slot="footer" class="flex flex-row justify-center gap-8 py-4">
|
||||
<p class="text-tertiary-500">Official Documentation</p>
|
||||
<p class="text-success-500">Forums</p>
|
||||
</div>
|
||||
<div slot="noResults" class="px-10 py-3 text-slate-500 text-sm">No results...</div>
|
||||
</SvelteSpotlight>
|
||||
</div>
|
||||
60
src/lib/Components/StatusPage.svelte
Normal file
@@ -0,0 +1,60 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
|
||||
let summaryResp: Promise<any>;
|
||||
|
||||
const getStatus = async () => {
|
||||
console.debug('Getting Status Summary');
|
||||
summaryResp = (await fetch('https://status.sailpoint.com/api/v2/summary.json')).json();
|
||||
console.debug(await summaryResp);
|
||||
};
|
||||
|
||||
let interval: any;
|
||||
|
||||
onMount(async () => {
|
||||
getStatus();
|
||||
interval = setInterval(() => getStatus(), 30000);
|
||||
});
|
||||
|
||||
onDestroy(() => clearInterval(interval));
|
||||
</script>
|
||||
|
||||
<h1 class="text-center">Status Page</h1>
|
||||
|
||||
{#await summaryResp}
|
||||
<div class="flex flex-row gap-2 justify-center">
|
||||
<div class="placeholder-circle w-16" />
|
||||
<p>Checking</p>
|
||||
</div>
|
||||
{:then summary}
|
||||
<div class="flex flex-row align-center gap-2 justify-center">
|
||||
{#if summary?.status?.indicator == 'none'}
|
||||
<a
|
||||
href="https://status.sailpoint.com"
|
||||
class="text-green-500 text-center hover:underline"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{summary?.status?.description}
|
||||
</a>
|
||||
{:else if summary?.status?.indicator == 'minor'}
|
||||
<a
|
||||
href="https://status.sailpoint.com"
|
||||
class="text-yellow-500 text-center hover:underline"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{summary?.status?.description}
|
||||
</a>
|
||||
{:else if summary?.status?.indicator == 'major'}
|
||||
<a
|
||||
href="https://status.sailpoint.com"
|
||||
class="text-red-500 text-center hover:underline"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{summary?.status?.description}
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
{/await}
|
||||
24
src/lib/Components/Support.svelte
Normal file
@@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
const supportLinks: { 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>
|
||||
|
||||
<h1 class="text-center">Support</h1>
|
||||
<ul class="flex flex-col">
|
||||
{#each supportLinks as link}
|
||||
<li class="flex flex-row gap-1 hover:underline hover:text-tertiary-600">
|
||||
<a target="_blank" rel="noreferrer" href={link.href}>
|
||||
{link.label}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
25
src/lib/Components/TenantData.svelte
Normal file
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export let tenantData: Writable<TenantData>;
|
||||
</script>
|
||||
|
||||
<h1 class="text-center pb-2">Tenant Data</h1>
|
||||
<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>
|
||||
35
src/lib/Components/TenantLinks.svelte
Normal file
@@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
import { idnSession } from '$lib/settings';
|
||||
const tenantLinks: { 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'
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<h1 class="text-center">Tenant Links</h1>
|
||||
<ul class="flex flex-col">
|
||||
{#each tenantLinks 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>
|
||||
121060
src/lib/V3Spec.yaml
Normal file
135
src/lib/authentication.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
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 < 1) {
|
||||
throw new Error('No tabs returned');
|
||||
}
|
||||
|
||||
const activeTab = tabs[0];
|
||||
|
||||
if (!activeTab || !activeTab.url) {
|
||||
throw new Error('No active tab');
|
||||
}
|
||||
|
||||
return new URL(activeTab.url);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
if (window.chrome && chrome.runtime && chrome.runtime.id) {
|
||||
let tabUrl;
|
||||
|
||||
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) {
|
||||
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;
|
||||
|
||||
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' }
|
||||
);
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
137
src/lib/settings.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { localStorageStore } from '@skeletonlabs/skeleton';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
|
||||
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);
|
||||
117
src/lib/theme-sailpoint.css
Normal file
@@ -0,0 +1,117 @@
|
||||
:root {
|
||||
/* =~= Theme Properties =~= */
|
||||
--theme-font-family-base: system-ui;
|
||||
--theme-font-family-heading: system-ui;
|
||||
--theme-font-color-base: 0 0 0;
|
||||
--theme-font-color-dark: 255 255 255;
|
||||
--theme-rounded-base: 9999px;
|
||||
--theme-rounded-container: 8px;
|
||||
--theme-border-base: 1px;
|
||||
/* =~= Theme On-X Colors =~= */
|
||||
--on-primary: 255 255 255;
|
||||
--on-secondary: 255 255 255;
|
||||
--on-tertiary: 255 255 255;
|
||||
--on-success: 255 255 255;
|
||||
--on-warning: 255 255 255;
|
||||
--on-error: 255 255 255;
|
||||
--on-surface: 255 255 255;
|
||||
/* =~= Theme Colors =~= */
|
||||
/* primary | #0033a1 */
|
||||
--color-primary-50: 217 224 241; /* ⬅ #d9e0f1 */
|
||||
--color-primary-100: 204 214 236; /* ⬅ #ccd6ec */
|
||||
--color-primary-200: 191 204 232; /* ⬅ #bfcce8 */
|
||||
--color-primary-300: 153 173 217; /* ⬅ #99add9 */
|
||||
--color-primary-400: 77 112 189; /* ⬅ #4d70bd */
|
||||
--color-primary-500: 0 51 161; /* ⬅ #0033a1 */
|
||||
--color-primary-600: 0 46 145; /* ⬅ #002e91 */
|
||||
--color-primary-700: 0 38 121; /* ⬅ #002679 */
|
||||
--color-primary-800: 0 31 97; /* ⬅ #001f61 */
|
||||
--color-primary-900: 0 25 79; /* ⬅ #00194f */
|
||||
/* secondary | #0071ce */
|
||||
--color-secondary-50: 217 234 248; /* ⬅ #d9eaf8 */
|
||||
--color-secondary-100: 204 227 245; /* ⬅ #cce3f5 */
|
||||
--color-secondary-200: 191 220 243; /* ⬅ #bfdcf3 */
|
||||
--color-secondary-300: 153 198 235; /* ⬅ #99c6eb */
|
||||
--color-secondary-400: 77 156 221; /* ⬅ #4d9cdd */
|
||||
--color-secondary-500: 0 113 206; /* ⬅ #0071ce */
|
||||
--color-secondary-600: 0 102 185; /* ⬅ #0066b9 */
|
||||
--color-secondary-700: 0 85 155; /* ⬅ #00559b */
|
||||
--color-secondary-800: 0 68 124; /* ⬅ #00447c */
|
||||
--color-secondary-900: 0 55 101; /* ⬅ #003765 */
|
||||
/* tertiary | #54c0e8 */
|
||||
--color-tertiary-50: 229 246 252; /* ⬅ #e5f6fc */
|
||||
--color-tertiary-100: 221 242 250; /* ⬅ #ddf2fa */
|
||||
--color-tertiary-200: 212 239 249; /* ⬅ #d4eff9 */
|
||||
--color-tertiary-300: 187 230 246; /* ⬅ #bbe6f6 */
|
||||
--color-tertiary-400: 135 211 239; /* ⬅ #87d3ef */
|
||||
--color-tertiary-500: 84 192 232; /* ⬅ #54c0e8 */
|
||||
--color-tertiary-600: 76 173 209; /* ⬅ #4cadd1 */
|
||||
--color-tertiary-700: 63 144 174; /* ⬅ #3f90ae */
|
||||
--color-tertiary-800: 50 115 139; /* ⬅ #32738b */
|
||||
--color-tertiary-900: 41 94 114; /* ⬅ #295e72 */
|
||||
/* success | #93d500 */
|
||||
--color-success-50: 239 249 217; /* ⬅ #eff9d9 */
|
||||
--color-success-100: 233 247 204; /* ⬅ #e9f7cc */
|
||||
--color-success-200: 228 245 191; /* ⬅ #e4f5bf */
|
||||
--color-success-300: 212 238 153; /* ⬅ #d4ee99 */
|
||||
--color-success-400: 179 226 77; /* ⬅ #b3e24d */
|
||||
--color-success-500: 147 213 0; /* ⬅ #93d500 */
|
||||
--color-success-600: 132 192 0; /* ⬅ #84c000 */
|
||||
--color-success-700: 110 160 0; /* ⬅ #6ea000 */
|
||||
--color-success-800: 88 128 0; /* ⬅ #588000 */
|
||||
--color-success-900: 72 104 0; /* ⬅ #486800 */
|
||||
/* warning | #EAB308 */
|
||||
--color-warning-50: 252 244 218; /* ⬅ #fcf4da */
|
||||
--color-warning-100: 251 240 206; /* ⬅ #fbf0ce */
|
||||
--color-warning-200: 250 236 193; /* ⬅ #faecc1 */
|
||||
--color-warning-300: 247 225 156; /* ⬅ #f7e19c */
|
||||
--color-warning-400: 240 202 82; /* ⬅ #f0ca52 */
|
||||
--color-warning-500: 234 179 8; /* ⬅ #EAB308 */
|
||||
--color-warning-600: 211 161 7; /* ⬅ #d3a107 */
|
||||
--color-warning-700: 176 134 6; /* ⬅ #b08606 */
|
||||
--color-warning-800: 140 107 5; /* ⬅ #8c6b05 */
|
||||
--color-warning-900: 115 88 4; /* ⬅ #735804 */
|
||||
/* error | #cc27b0 */
|
||||
--color-error-50: 247 223 243; /* ⬅ #f7dff3 */
|
||||
--color-error-100: 245 212 239; /* ⬅ #f5d4ef */
|
||||
--color-error-200: 242 201 235; /* ⬅ #f2c9eb */
|
||||
--color-error-300: 235 169 223; /* ⬅ #eba9df */
|
||||
--color-error-400: 219 104 200; /* ⬅ #db68c8 */
|
||||
--color-error-500: 204 39 176; /* ⬅ #cc27b0 */
|
||||
--color-error-600: 184 35 158; /* ⬅ #b8239e */
|
||||
--color-error-700: 153 29 132; /* ⬅ #991d84 */
|
||||
--color-error-800: 122 23 106; /* ⬅ #7a176a */
|
||||
--color-error-900: 100 19 86; /* ⬅ #641356 */
|
||||
/* surface | #415364 */
|
||||
--color-surface-50: 227 229 232; /* ⬅ #e3e5e8 */
|
||||
--color-surface-100: 217 221 224; /* ⬅ #d9dde0 */
|
||||
--color-surface-200: 208 212 216; /* ⬅ #d0d4d8 */
|
||||
--color-surface-300: 179 186 193; /* ⬅ #b3bac1 */
|
||||
--color-surface-400: 122 135 147; /* ⬅ #7a8793 */
|
||||
--color-surface-500: 65 83 100; /* ⬅ #415364 */
|
||||
--color-surface-600: 59 75 90; /* ⬅ #3b4b5a */
|
||||
--color-surface-700: 49 62 75; /* ⬅ #313e4b */
|
||||
--color-surface-800: 39 50 60; /* ⬅ #27323c */
|
||||
--color-surface-900: 32 41 49; /* ⬅ #202931 */
|
||||
}
|
||||
|
||||
/* Applied to body with `<body data-theme="sailpoint">` */
|
||||
/* Created with: https://csshero.org/mesher/ */
|
||||
[data-theme='sailpoint'] {
|
||||
/* prettier-ignore */
|
||||
background-color:hsla(197,0%,100%,1);
|
||||
background-image: radial-gradient(at 44% 23%, hsla(220, 100%, 31%, 0.36) 0px, transparent 50%),
|
||||
radial-gradient(at 93% 7%, hsla(207, 100%, 40%, 0.58) 0px, transparent 50%),
|
||||
radial-gradient(at 86% 77%, hsla(195, 76%, 61%, 0.55) 0px, transparent 50%),
|
||||
radial-gradient(at 45% 84%, hsla(310, 67%, 47%, 0.48) 0px, transparent 50%);
|
||||
background-repeat: no-repeat, no-repeat, no-repeat;
|
||||
}
|
||||
.dark [data-theme='sailpoint'] {
|
||||
/* prettier-ignore */
|
||||
/* background-color:hsla(205,19%,68%,0.54); */
|
||||
background-image: radial-gradient(at 44% 23%, hsla(220, 100%, 31%, 0.36) 0px, transparent 50%),
|
||||
radial-gradient(at 93% 7%, hsla(207, 100%, 40%, 0.58) 0px, transparent 50%),
|
||||
radial-gradient(at 86% 77%, hsla(195, 76%, 61%, 0.55) 0px, transparent 50%),
|
||||
radial-gradient(at 45% 84%, hsla(310, 67%, 47%, 0.48) 0px, transparent 50%);
|
||||
background-repeat: no-repeat, no-repeat, no-repeat;
|
||||
}
|
||||
5
src/lib/utilities.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const labelSort = (a: any, b: any) => a.label.localeCompare(b.label);
|
||||
|
||||
export type IndexHit = { index: string; hits: Hit[] } & Record<string, any>;
|
||||
|
||||
export type Hit = { name: string; objectID: string };
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
@@ -1,13 +0,0 @@
|
||||
const reportWebVitals = onPerfEntry => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
84
src/routes/+layout.svelte
Normal file
@@ -0,0 +1,84 @@
|
||||
<script lang="ts">
|
||||
// The ordering of these imports is critical to your app working properly
|
||||
import '$lib/theme-sailpoint.css';
|
||||
// If you have source.organizeImports set to true in VSCode, then it will auto change this ordering
|
||||
import '@skeletonlabs/skeleton/styles/skeleton.css';
|
||||
// Most of your app wide CSS should be put in this file
|
||||
import '../app.postcss';
|
||||
|
||||
import { page } from '$app/stores';
|
||||
import TenantLinks from '$lib/Components/TenantLinks.svelte';
|
||||
import { checkSession } from '$lib/authentication';
|
||||
import { idnSession } from '$lib/settings';
|
||||
import { arrow, autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';
|
||||
import { AppBar, AppShell, storeHighlightJs, 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 SpotlightSearch from '$lib/Components/Spotlight/SpotlightSearch.svelte';
|
||||
|
||||
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
|
||||
storeHighlightJs.set(hljs);
|
||||
|
||||
onMount(async () => checkSession());
|
||||
|
||||
let now = dayjs();
|
||||
let minutesUntil = dayjs($idnSession?.expiration).diff(now, 'minutes');
|
||||
let secondsUntil = dayjs($idnSession?.expiration).diff(now, 'seconds') - minutesUntil * 60;
|
||||
|
||||
setInterval(function () {
|
||||
now = dayjs();
|
||||
minutesUntil = dayjs($idnSession?.expiration).diff(now, 'minutes');
|
||||
secondsUntil = dayjs($idnSession?.expiration).diff(now, 'seconds') - minutesUntil * 60;
|
||||
}, 1000);
|
||||
</script>
|
||||
|
||||
<AppShell>
|
||||
<svelte:fragment slot="header">
|
||||
<AppBar>
|
||||
<svelte:fragment slot="lead">
|
||||
<div class="flex flex-row gap-4">
|
||||
<a class:text-tertiary-400={$page.url.pathname === '/'} href="/">Tenant</a>
|
||||
<a class:text-tertiary-400={$page.url.pathname === '/api-client'} href="/api-client">
|
||||
API Client
|
||||
</a>
|
||||
<a class:text-tertiary-400={$page.url.pathname === '/session'} href="/session">Session</a>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="trail">
|
||||
<div class="p-1 top-0 right-0 flex flex-row gap-2">
|
||||
<SpotlightSearch />
|
||||
{#if minutesUntil < 0 || secondsUntil < 0}
|
||||
<p class="text-xs text-white my-auto">
|
||||
Strong Auth: <span class="text-red-500">Expired</span>
|
||||
</p>
|
||||
<p class="text-xs text-white my-auto">
|
||||
Session Timer: <span class="text-red-500">Expired</span>
|
||||
</p>
|
||||
{:else}
|
||||
<p class="text-xs text-white my-auto">
|
||||
Strong Auth: {#if $idnSession.strongAuth === true}
|
||||
<span class="text-green-500">True</span>
|
||||
{:else}
|
||||
<span class="text-red-500">False</span>
|
||||
{/if}
|
||||
</p>
|
||||
<p class="text-xs text-white my-auto">
|
||||
Session Timer: {minutesUntil}:{#if secondsUntil < 10}
|
||||
{`0${secondsUntil}`}
|
||||
{:else}
|
||||
{secondsUntil}
|
||||
{/if}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</AppBar>
|
||||
</svelte:fragment>
|
||||
|
||||
<!-- Router Slot -->
|
||||
<slot />
|
||||
<!-- ---- / ---- -->
|
||||
</AppShell>
|
||||
1
src/routes/+layout.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const prerender = true;
|
||||
44
src/routes/+page.svelte
Normal file
@@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
import CurrentUser from '$lib/Components/CurrentUser.svelte';
|
||||
import HostingData from '$lib/Components/HostingData.svelte';
|
||||
import Resources from '$lib/Components/Resources.svelte';
|
||||
import StatusPage from '$lib/Components/StatusPage.svelte';
|
||||
import Support from '$lib/Components/Support.svelte';
|
||||
import TenantData from '$lib/Components/TenantData.svelte';
|
||||
import TenantLinks from '$lib/Components/TenantLinks.svelte';
|
||||
import { hostingData, tenantData } from '$lib/settings';
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2 p-2 h-full">
|
||||
<div class="flex flex-row gap-2">
|
||||
<div class="flex flex-col gap-2 grow">
|
||||
<div class="p-2 card variant-soft-surface">
|
||||
<TenantData {tenantData} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 grow">
|
||||
<div class="p-2 card variant-soft-surface grow">
|
||||
<HostingData {hostingData} />
|
||||
</div>
|
||||
<div class="p-2 card variant-soft-surface grow">
|
||||
<CurrentUser {tenantData} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grow flex flex-col gap-2">
|
||||
<div class="p-4 card variant-soft-surface grow">
|
||||
<StatusPage />
|
||||
</div>
|
||||
<div class="p-2 card variant-soft-surface grow">
|
||||
<Support />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row gap-2 grow">
|
||||
<div class="p-2 card variant-soft-surface grow">
|
||||
<TenantLinks />
|
||||
</div>
|
||||
<div class="p-2 card variant-soft-surface grow">
|
||||
<Resources />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
155
src/routes/api-client/+page.svelte
Normal file
@@ -0,0 +1,155 @@
|
||||
<script lang="ts">
|
||||
import { idnSession } from '$lib/settings';
|
||||
import { Tab, TabGroup } from '@skeletonlabs/skeleton';
|
||||
import { JSONEditor } from 'svelte-jsoneditor';
|
||||
import 'svelte-jsoneditor/themes/jse-theme-dark.css';
|
||||
|
||||
import { paths as V2Spec } from './V2Spec.json';
|
||||
import { paths as BetaSpec } from './BetaSpec.json';
|
||||
import { paths as V3Spec } from './V3Spec.json';
|
||||
import { paths as CCSpec } from './CCSpec.json';
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
function mapPath(path: string[]) {
|
||||
const [name, value] = path;
|
||||
return { name, value };
|
||||
}
|
||||
|
||||
async function makeAPICall() {
|
||||
const response = await axios({
|
||||
method: selectedAPIMethod,
|
||||
url: `${$idnSession.baseUrl}/${APICallPath}`,
|
||||
data: requestContent.json,
|
||||
headers: {
|
||||
authorization: `Bearer ${$idnSession.accessToken}`
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
return err;
|
||||
});
|
||||
responseContent = { text: undefined, json: response.data };
|
||||
console.log(responseContent);
|
||||
}
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
console.log(event);
|
||||
|
||||
if (event.isTrusted === true && event.key === 'Enter') {
|
||||
makeAPICall();
|
||||
}
|
||||
}
|
||||
|
||||
let APIVersions = [
|
||||
//@ts-ignore
|
||||
{ name: 'Beta', value: Object.entries(BetaSpec).map((path) => mapPath(path)) },
|
||||
//@ts-ignore
|
||||
{ name: 'V3', value: Object.entries(V3Spec).map((path) => mapPath(path)) },
|
||||
//@ts-ignore
|
||||
{ name: 'V2', value: Object.entries(V2Spec).map((path) => mapPath(path)) },
|
||||
//@ts-ignore
|
||||
{ name: 'CC', value: Object.entries(CCSpec).map((path) => mapPath(path)) },
|
||||
{
|
||||
name: 'Custom',
|
||||
value: [
|
||||
{
|
||||
name: 'Custom Path',
|
||||
value: { GET: '', POST: '', PUT: '', PATCH: '', DELETE: '', HEAD: '' }
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
let requestContent = {
|
||||
text: undefined, // can be used to pass a stringified JSON document instead
|
||||
json: ''
|
||||
};
|
||||
|
||||
let responseContent = {
|
||||
text: undefined, // can be used to pass a stringified JSON document instead
|
||||
json: ''
|
||||
};
|
||||
|
||||
let tabSet: number = 1;
|
||||
|
||||
let selectedAPIVersion = APIVersions[0];
|
||||
let selectedPath = selectedAPIVersion.value[0];
|
||||
|
||||
let APICallPath: string = `${selectedAPIVersion.name.toLowerCase()}${selectedPath.name}`;
|
||||
|
||||
let selectedAPIMethod = 'GET';
|
||||
</script>
|
||||
|
||||
<div class="p-2 flex flex-col gap-4 overflow-hidden overflow-y-auto">
|
||||
<div class="flex flex-row">
|
||||
<select
|
||||
placeholder="Select an API Version"
|
||||
class="max-w-fit !rounded-r-none px-4 py-2 select"
|
||||
bind:value={selectedAPIVersion}
|
||||
on:change={() => {
|
||||
selectedPath = selectedAPIVersion.value[0];
|
||||
if (['Beta', 'V3', 'V2'].includes(selectedAPIVersion.name)) {
|
||||
APICallPath = `${selectedAPIVersion.name.toLowerCase()}${selectedPath.name}`;
|
||||
} else if (['CC'].includes(selectedAPIVersion.name)) {
|
||||
APICallPath = `${selectedPath.name}`;
|
||||
} else {
|
||||
APICallPath = '';
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#each APIVersions as APIVersion}
|
||||
<option selected={selectedAPIVersion === APIVersion} value={APIVersion}>
|
||||
{APIVersion.name}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
<select
|
||||
placeholder="Choose the API Endpoint"
|
||||
class="!rounded-l-none px-4 select"
|
||||
bind:value={selectedPath}
|
||||
on:change={() => {
|
||||
if (['Beta', 'V3', 'V2'].includes(selectedAPIVersion.name)) {
|
||||
APICallPath = `${selectedAPIVersion.name.toLowerCase()}${selectedPath.name}`;
|
||||
} else if (['CC'].includes(selectedAPIVersion.name)) {
|
||||
APICallPath = `${selectedPath.name}`;
|
||||
} else {
|
||||
APICallPath = '';
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#each selectedAPIVersion.value as path}
|
||||
<option selected={path === selectedPath} value={path}>{path.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<select class="select rounded-r-none w-fit">
|
||||
{#each Object.entries(selectedPath.value) as [method, content]}
|
||||
<option>{method.toUpperCase()}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<input
|
||||
type="text"
|
||||
class="w-full !rounded-l-none rounded-r-none px-4 py-2 input"
|
||||
bind:value={APICallPath}
|
||||
/>
|
||||
<button on:click={makeAPICall} class="btn variant-filled-surface rounded-l-none rounded-r-lg">
|
||||
Call
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<TabGroup justify="justify-center">
|
||||
<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">
|
||||
<JSONEditor bind:content={requestContent} />
|
||||
</div>
|
||||
{:else if tabSet === 1}
|
||||
<div class="jse-theme-dark">
|
||||
<JSONEditor bind:content={responseContent} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
217503
src/routes/api-client/BetaSpec.json
Normal file
1475
src/routes/api-client/CCSpec.json
Normal file
1418
src/routes/api-client/V2Spec.json
Normal file
164739
src/routes/api-client/V3Spec.json
Normal file
8
src/routes/session/+page.svelte
Normal file
@@ -0,0 +1,8 @@
|
||||
<script>
|
||||
import { idnSession } from '$lib/settings';
|
||||
import { CodeBlock } from '@skeletonlabs/skeleton';
|
||||
</script>
|
||||
|
||||
<div class="p-4 h-full flex flex-col justify-center">
|
||||
<CodeBlock lineNumbers language="json" code={JSON.stringify($idnSession, null, ' ')} />
|
||||
</div>
|
||||
@@ -1,5 +0,0 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
BIN
static/favicon.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
static/images/anchor_12.png
Normal file
|
After Width: | Height: | Size: 426 B |
BIN
static/images/anchor_128.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
static/images/anchor_32.png
Normal file
|
After Width: | Height: | Size: 747 B |
BIN
static/images/anchor_48.png
Normal file
|
After Width: | Height: | Size: 898 B |
19
static/manifest.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "Anchor",
|
||||
"description": "Chrome extension built to aid in working with IdentityNow",
|
||||
"version": "0.0.3",
|
||||
"manifest_version": 3,
|
||||
"icons": {
|
||||
"12": "images/anchor_12.png",
|
||||
"32": "images/anchor_32.png",
|
||||
"48": "images/anchor_48.png",
|
||||
"128": "images/anchor_128.png"
|
||||
},
|
||||
"permissions": ["tabs"],
|
||||
"host_permissions": ["http://*/", "https://*/"],
|
||||
"action": {
|
||||
"default_popup": "index.html",
|
||||
"default_icon": "images/anchor_32.png",
|
||||
"default_title": "Anchor"
|
||||
}
|
||||
}
|
||||
26
svelte.config.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import adapter from 'sveltekit-adapter-chrome-extension';
|
||||
import { vitePreprocess } from '@sveltejs/kit/vite';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
|
||||
kit: {
|
||||
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||
adapter: adapter({
|
||||
// default options are shown
|
||||
pages: 'build',
|
||||
assets: 'build',
|
||||
fallback: null,
|
||||
precompress: false,
|
||||
manifest: 'manifest.json'
|
||||
}),
|
||||
appDir: 'app'
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
17
tailwind.config.cjs
Normal file
@@ -0,0 +1,17 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
'./src/**/*.{html,js,svelte,ts}',
|
||||
require('path').join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}')
|
||||
],
|
||||
theme: {
|
||||
extend: {}
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/forms'),
|
||||
require('@tailwindcss/typography'),
|
||||
...require('@skeletonlabs/skeleton/tailwind/skeleton.cjs')()
|
||||
]
|
||||
};
|
||||
17
tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
||||
6
vite.config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()]
|
||||
});
|
||||