Merge pull request #2 from LukeHagar/sveltekit-rewrite

Complete rewrite in sveltekit
This commit is contained in:
Luke Hagar
2023-05-19 10:47:26 -05:00
committed by GitHub
85 changed files with 675312 additions and 34051 deletions

13
.eslintignore Normal file
View 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
View 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
View 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
View File

@@ -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
View File

@@ -0,0 +1,3 @@
engine-strict=true
resolution-mode=highest
enable-pre-post-scripts=true

13
.prettierignore Normal file
View 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
View 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
View 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"
]
}

View File

@@ -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.

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 426 B

After

Width:  |  Height:  |  Size: 426 B

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 747 B

After

Width:  |  Height:  |  Size: 747 B

View File

Before

Width:  |  Height:  |  Size: 898 B

After

Width:  |  Height:  |  Size: 898 B

BIN
favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
images/anchor_12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

BIN
images/anchor_128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
images/anchor_32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 B

BIN
images/anchor_48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 B

32751
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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

File diff suppressed because it is too large Load Diff

6
postcss.config.cjs Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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
View 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
View 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
View 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];
}

View File

@@ -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;
}

View File

@@ -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

File diff suppressed because it is too large Load Diff

View 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

View 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>

View 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>

View 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>

View 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> -->

View 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>

View 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>

View 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}

View 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>

View 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>

View 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

File diff suppressed because it is too large Load Diff

135
src/lib/authentication.ts Normal file
View 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
View 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
View 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
View 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 };

View File

@@ -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

View File

@@ -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
View 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
View File

@@ -0,0 +1 @@
export const prerender = true;

44
src/routes/+page.svelte Normal file
View 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>

View 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>

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View 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>

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
static/images/anchor_12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
static/images/anchor_32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 B

BIN
static/images/anchor_48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 B

19
static/manifest.json Normal file
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
});

2802
yarn.lock Normal file

File diff suppressed because it is too large Load Diff

BIN
yq.exe Normal file

Binary file not shown.