Attempting to correct previous issues with monorepo structure
102
.gitignore
vendored
@@ -1,16 +1,92 @@
|
||||
node_modules
|
||||
.svelte-kit
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
dist
|
||||
build
|
||||
package
|
||||
|
||||
.env
|
||||
.env.*
|
||||
*.local
|
||||
!.env.example
|
||||
|
||||
yarn-error.log
|
||||
pnpm-lock.yaml
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
.DS_Store
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# Webpack
|
||||
.webpack/
|
||||
|
||||
# Vite
|
||||
.vite/
|
||||
|
||||
# Electron-Forge
|
||||
out/
|
||||
|
||||
12
.prettierrc
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"svelteSortOrder": "scripts-markup-styles",
|
||||
"htmlWhitespaceSensitivity": "ignore",
|
||||
"trailingComma": "all",
|
||||
"requirePragma": false,
|
||||
"bracketSpacing": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"useTabs": true,
|
||||
"tabWidth": 4,
|
||||
"semi": true
|
||||
}
|
||||
3
.vscode/extensions.json
vendored
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"recommendations": ["svelte.svelte-vscode"]
|
||||
}
|
||||
6
.vscode/settings.json
vendored
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"[svelte]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "svelte.svelte-vscode"
|
||||
}
|
||||
}
|
||||
21
LICENSE
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Braden Wiggins and contributors: https://github.com/fractalhq/sveltekit-electron/graphs/contributors
|
||||
|
||||
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.
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 SailPoint
|
||||
Copyright (c) 2024 SailPoint
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
173
README.md
@@ -1,49 +1,22 @@
|
||||
<a id="readme-top"></a>
|
||||
|
||||
## How to use this template
|
||||
|
||||
Update sheild urls
|
||||
|
||||
* Update url for discourse, downloads, issues, current release, and contributors
|
||||
|
||||
Project title and logo
|
||||
|
||||
* Update project title and description
|
||||
* Update project logo
|
||||
* Update link to point to documentation about this project
|
||||
|
||||
About the project
|
||||
|
||||
* Update project screenshot
|
||||
* Update the paragraph with what your project is meant to accomplish
|
||||
|
||||
Getting started
|
||||
|
||||
* Describe how to get started with your project
|
||||
* Describe any prerequisites needed to run your project
|
||||
* Describe how to install and run your project
|
||||
|
||||
Discuss
|
||||
|
||||
* Create a tag in our discourse forum for your project
|
||||
* Update link in this section to point to the newly created tag
|
||||
|
||||
After these steps are complete remove this checklist!
|
||||
|
||||
<!-- PROJECT SHIELDS -->
|
||||
[![Discourse Topics][discourse-shield]][discourse-url]
|
||||
![Times Downloaded][downloads-shield]
|
||||
![Issues][issues-shield]
|
||||
![Latest Releases][release-shield]
|
||||
![Contributor Shield][contributor-shield]
|
||||
|
||||
[discourse-shield]: https://img.shields.io/discourse/topics?label=Discuss%20This%20Tool&server=https%3A%2F%2Fdeveloper.sailpoint.com%2Fdiscuss
|
||||
[discourse-url]: https://developer.sailpoint.com/discuss/tag/workflows
|
||||
[downloads-shield]: https://img.shields.io/github/downloads/sailpoint-oss/api-linter/total?label=Downloads
|
||||
[issues-shield]:https://img.shields.io/github/issues/sailpoint-oss/api-linter?label=Issues
|
||||
[release-shield]: https://img.shields.io/github/v/release/sailpoint-oss/api-linter?label=Current%20Release
|
||||
[contributor-shield]:https://img.shields.io/github/contributors/sailpoint-oss/api-linter?label=Contributors
|
||||
|
||||
[product-screenshot]: ./assets/images/api-linter-output.png
|
||||
<!-- [discourse-shield]: https://img.shields.io/discourse/topics?label=Discuss%20This%20Tool&server=https%3A%2F%2Fdeveloper.sailpoint.com%2Fdiscuss -->
|
||||
[discourse-shield]: https://img.shields.io/badge/Discuss_This_Tool-0033a1
|
||||
[discourse-url]: https://developer.sailpoint.com/discuss/tag/idn-admin-console
|
||||
[downloads-shield]: https://img.shields.io/github/downloads/sailpoint-oss/idn-admin-console/total?label=Downloads
|
||||
[issues-shield]:https://img.shields.io/github/issues/sailpoint-oss/idn-admin-console?label=Issues
|
||||
[release-shield]: https://img.shields.io/github/v/tag/sailpoint-oss/idn-admin-console?label=Current%20Release
|
||||
[contributor-shield]:https://img.shields.io/github/contributors/sailpoint-oss/idn-admin-console?label=Contributors
|
||||
|
||||
[product-screenshot]: ./assets/images/idn-admin-console-output.png
|
||||
|
||||
<!-- PROJECT LOGO -->
|
||||
<br />
|
||||
@@ -52,90 +25,106 @@ After these steps are complete remove this checklist!
|
||||
<img src="https://avatars.githubusercontent.com/u/63106368?s=200&v=4" alt="Logo" width="80" height="80">
|
||||
</a>
|
||||
|
||||
<h3 align="center">SailPoint OSS - README - Template</h3>
|
||||
<h3 align="center">IdentityNow Admin Console - README</h3>
|
||||
|
||||
<p align="center">
|
||||
An awesome README template to jumpstart your projects!
|
||||
A desktop application to administer and troubleshoot IdentityNow
|
||||
<br />
|
||||
<a href="https://github.com/othneildrew/Best-README-Template"><strong>Explore the docs »</strong></a>
|
||||
<br />
|
||||
<br />
|
||||
<!-- <a href="https://github.com/sailpoint/repo-template">View Demo</a>
|
||||
<a href="https://github.com/sailpoint-oss/idn-admin-console/issues/new?assignees=&labels=bug&projects=&template=bug-report.md&title=%5BBUG%5D+Your+Bug+Report+Here">Report Bug</a>
|
||||
·
|
||||
<a href="https://github.com/sailpoint-oss/repo-template/issues">Report Bug</a>
|
||||
·
|
||||
<a href="https://github.com/sailpoint-oss/repo-template/issueschoose">Request Feature</a> -->
|
||||
<a href="https://github.com/sailpoint-oss/idn-admin-console/issues/new?assignees=&labels=enhancement&projects=&template=feature-request.md&title=%5BFEATURE%5D+Your+Feature+Request+Here+">Request Feature</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
- [About the project](#about-the-project)
|
||||
- [Get started](#get-started)
|
||||
- [Use a release](#use-a-release)
|
||||
- [Building the application from source](#build-the-application-from-source)
|
||||
- [Contribute](#contribute)
|
||||
- [License](#license)
|
||||
- [Discuss](#discuss)
|
||||
|
||||
|
||||
<!-- ABOUT THE PROJECT -->
|
||||
## About The Project
|
||||
## About the project
|
||||
|
||||
<div align="center">
|
||||
<img src="./assets/images/api-linter-output.png" width="500" height="" style="text-align:center">
|
||||
</div>
|
||||
<!-- <div align="center">
|
||||
<img src="./assets/images/idn-admin-console-output.png" width="500" height="" style="text-align:center">
|
||||
</div> -->
|
||||
|
||||
There are many great README templates available on GitHub; however, I didn't find one that really suited my needs so I created this enhanced one. I want to create a README template so amazing that it'll be the last one you ever need -- I think this is it.
|
||||
The IdentityNow Admin Console is a desktop application you can use to administer and troubleshoot IdentityNow. The admin console is built using Electron and Sveltekit, and it is developed and maintained by the SailPoint Developer Relations team.
|
||||
|
||||
Here's why:
|
||||
* Your time should be focused on creating something amazing. A project that solves a problem and helps others
|
||||
* You shouldn't be doing the same tasks over and over like creating a README from scratch
|
||||
* You should implement DRY principles to the rest of your life :smile:
|
||||
|
||||
Of course, no one template will serve all projects since your needs may be different. So I'll be adding more in the near future. You may also suggest changes by forking this repo and creating a pull request or opening an issue. Thanks to all the people have contributed to expanding this template!
|
||||
|
||||
Use the `BLANK_README.md` to get started.
|
||||
The goal of the admin console is to provide a single place to perform common administrative tasks and troubleshoot issues in your IdentityNow tenant.
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
<!-- GETTING STARTED -->
|
||||
## Getting Started
|
||||
## Get started
|
||||
|
||||
This is an example of how you may give instructions on setting up your project locally.
|
||||
To get a local copy up and running follow these simple example steps.
|
||||
To use this tool, you will need to have an IdentityNow tenant.
|
||||
|
||||
### Prerequisites
|
||||
### Use a release
|
||||
|
||||
This is an example of how to list things you need to use the software and how to install them.
|
||||
* npm
|
||||
```sh
|
||||
npm install npm@latest -g
|
||||
```
|
||||
There are built versions of this application available for each major OS platform. You can find the latest release [here](https://github.com/sailpoint-oss/idn-admin-console/releases/latest).
|
||||
|
||||
### Installation
|
||||
Pick your OS and download the relevant file from the latest release:
|
||||
| Platform | File Type |
|
||||
| -------- | --------- |
|
||||
| Windows | exe, zip |
|
||||
| Mac | dmg, zip |
|
||||
| Linux | deb, rpm, zip |
|
||||
|
||||
_Below is an example of how you can instruct your audience on installing and setting up your app. This template doesn't rely on any external dependencies or services._
|
||||
|
||||
1. Get a free API Key at [https://example.com](https://example.com)
|
||||
2. Clone the repo
|
||||
```sh
|
||||
git clone https://github.com/your_username_/Project-Name.git
|
||||
```
|
||||
3. Install NPM packages
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
4. Enter your API in `config.js`
|
||||
```js
|
||||
const API_KEY = 'ENTER YOUR API';
|
||||
```
|
||||
If you want to build the application yourself, follow these steps:
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
### Build the application from source
|
||||
|
||||
Prerequisites:
|
||||
* To build the application from source you will need NPM installed. You can find instructions on how to install NPM [here](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
|
||||
|
||||
Once you have NPM installed, you can clone this repository and run the following commands in the specified order and folders:
|
||||
|
||||
Go to the folder: `./Sveltekit-App`
|
||||
First, run this command:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
Then run this command:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Next, go to the folder: `./Electron-App`
|
||||
First, run this command:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
Then, run this command:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
As long as there are no errors during the build process, the built application binaries will then be located in the `./Electron-App/out` folder.
|
||||
|
||||
|
||||
<!-- CONTRIBUTING -->
|
||||
## Contributing
|
||||
## Contribute
|
||||
|
||||
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
|
||||
|
||||
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag `enhancement`.
|
||||
If you have a suggestion to improve this project, please fork the repo and create a pull request. You can also make a suggestion by opening an issue with the tag `enhancement`.
|
||||
Don't forget to give the project a star! Thanks again!
|
||||
|
||||
1. Fork the Project
|
||||
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
|
||||
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
|
||||
4. Push to the Branch (`git push origin feature/AmazingFeature`)
|
||||
5. Open a Pull Request
|
||||
Follow these steps to make contributions:
|
||||
|
||||
1. Fork the project.
|
||||
2. Create your feature branch: `git checkout -b feature/AmazingFeature`
|
||||
3. Commit your changes: `git commit -m 'Add some AmazingFeature'`
|
||||
4. Push to the branch: `git push origin feature/AmazingFeature`
|
||||
5. Open a pull request.
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
@@ -148,6 +137,6 @@ Distributed under the MIT License. See `LICENSE.txt` for more information.
|
||||
|
||||
<!-- CONTACT -->
|
||||
## Discuss
|
||||
[Click Here](https://developer.sailpoint.com/dicuss/tag/{tagName}) to discuss this tool with other users.
|
||||
You can go to the [SailPoint Developer Community Forum](https://developer.sailpoint.com/discuss/tag/idn-admin-console) to discuss this tool with other users!
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
13
Sveltekit-App/.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
Sveltekit-App/.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'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
10
Sveltekit-App/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
1
Sveltekit-App/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
engine-strict=true
|
||||
13
Sveltekit-App/.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
|
||||
8
Sveltekit-App/.prettierrc
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
118
Sveltekit-App/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
{
|
||||
"prettier.documentSelectors": [
|
||||
"**/*.svelte"
|
||||
],
|
||||
"tailwindCSS.classAttributes": [
|
||||
"class",
|
||||
"accent",
|
||||
"active",
|
||||
"aspectRatio",
|
||||
"background",
|
||||
"badge",
|
||||
"bgBackdrop",
|
||||
"bgDark",
|
||||
"bgDrawer",
|
||||
"bgLight",
|
||||
"blur",
|
||||
"border",
|
||||
"button",
|
||||
"buttonAction",
|
||||
"buttonBack",
|
||||
"buttonClasses",
|
||||
"buttonComplete",
|
||||
"buttonDismiss",
|
||||
"buttonNeutral",
|
||||
"buttonNext",
|
||||
"buttonPositive",
|
||||
"buttonTextCancel",
|
||||
"buttonTextConfirm",
|
||||
"buttonTextFirst",
|
||||
"buttonTextLast",
|
||||
"buttonTextNext",
|
||||
"buttonTextPrevious",
|
||||
"buttonTextSubmit",
|
||||
"caretClosed",
|
||||
"caretOpen",
|
||||
"chips",
|
||||
"color",
|
||||
"controlSeparator",
|
||||
"controlVariant",
|
||||
"cursor",
|
||||
"display",
|
||||
"element",
|
||||
"fill",
|
||||
"fillDark",
|
||||
"fillLight",
|
||||
"flex",
|
||||
"gap",
|
||||
"gridColumns",
|
||||
"height",
|
||||
"hover",
|
||||
"inactive",
|
||||
"indent",
|
||||
"justify",
|
||||
"meter",
|
||||
"padding",
|
||||
"position",
|
||||
"regionAnchor",
|
||||
"regionBackdrop",
|
||||
"regionBody",
|
||||
"regionCaption",
|
||||
"regionCaret",
|
||||
"regionCell",
|
||||
"regionChildren",
|
||||
"regionChipList",
|
||||
"regionChipWrapper",
|
||||
"regionCone",
|
||||
"regionContent",
|
||||
"regionControl",
|
||||
"regionDefault",
|
||||
"regionDrawer",
|
||||
"regionFoot",
|
||||
"regionFootCell",
|
||||
"regionFooter",
|
||||
"regionHead",
|
||||
"regionHeadCell",
|
||||
"regionHeader",
|
||||
"regionIcon",
|
||||
"regionInput",
|
||||
"regionInterface",
|
||||
"regionInterfaceText",
|
||||
"regionLabel",
|
||||
"regionLead",
|
||||
"regionLegend",
|
||||
"regionList",
|
||||
"regionListItem",
|
||||
"regionNavigation",
|
||||
"regionPage",
|
||||
"regionPanel",
|
||||
"regionRowHeadline",
|
||||
"regionRowMain",
|
||||
"regionSummary",
|
||||
"regionSymbol",
|
||||
"regionTab",
|
||||
"regionTrail",
|
||||
"ring",
|
||||
"rounded",
|
||||
"select",
|
||||
"shadow",
|
||||
"slotDefault",
|
||||
"slotFooter",
|
||||
"slotHeader",
|
||||
"slotLead",
|
||||
"slotMessage",
|
||||
"slotMeta",
|
||||
"slotPageContent",
|
||||
"slotPageFooter",
|
||||
"slotPageHeader",
|
||||
"slotSidebarLeft",
|
||||
"slotSidebarRight",
|
||||
"slotTrail",
|
||||
"spacing",
|
||||
"text",
|
||||
"track",
|
||||
"transition",
|
||||
"width",
|
||||
"zIndex"
|
||||
]
|
||||
}
|
||||
38
Sveltekit-App/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# create-svelte
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npm create svelte@latest
|
||||
|
||||
# create a new project in my-app
|
||||
npm create svelte@latest my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
||||
6156
Sveltekit-App/package-lock.json
generated
Normal file
57
Sveltekit-App/package.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "idn-admin-console-svelte",
|
||||
"description": "A troubleshooting and administration app for IdentityNow",
|
||||
"version": "0.0.3",
|
||||
"private": true,
|
||||
"author": {
|
||||
"name": "Luke Hagar",
|
||||
"email": "luke.hagar@sailpoint.com"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"publish": "npm run build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"test": "vitest",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@floating-ui/dom": "1.5.4",
|
||||
"@skeletonlabs/skeleton": "2.7.1",
|
||||
"@skeletonlabs/tw-plugin": "0.3.1",
|
||||
"@sveltejs/adapter-node": "^4.0.1",
|
||||
"@sveltejs/kit": "^2.5.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||
"@tailwindcss/forms": "0.5.7",
|
||||
"@tailwindcss/typography": "0.5.10",
|
||||
"@types/jsonwebtoken": "^9.0.5",
|
||||
"@types/node": "20.11.5",
|
||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
||||
"@typescript-eslint/parser": "^6.20.0",
|
||||
"alasql": "^4.2.6",
|
||||
"autoprefixer": "10.4.17",
|
||||
"axios": "^1.6.7",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
"highlight.js": "^11.9.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"postcss": "8.4.33",
|
||||
"prettier": "^3.2.4",
|
||||
"prettier-plugin-svelte": "^3.1.2",
|
||||
"sailpoint-api-client": "^1.3.0",
|
||||
"svelte": "^4.2.9",
|
||||
"svelte-check": "^3.6.3",
|
||||
"svelte-jsoneditor": "^0.21.4",
|
||||
"tailwindcss": "3.4.1",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.0.12",
|
||||
"vite-plugin-tailwind-purgecss": "0.2.0",
|
||||
"vitest": "^1.2.2"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
6
Sveltekit-App/postcss.config.cjs
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
28
Sveltekit-App/src/app.d.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
// and what to do when importing types
|
||||
|
||||
import type { IdnSession, Session, TokenDetails } from '$lib/utils/oauth';
|
||||
|
||||
declare global {
|
||||
namespace App {
|
||||
interface Locals {
|
||||
hasSession: boolean;
|
||||
hasIdnSession: boolean;
|
||||
session?: Session;
|
||||
idnSession?: IdnSession;
|
||||
tokenDetails?: TokenDetails;
|
||||
}
|
||||
|
||||
// interface PageData {}
|
||||
|
||||
interface Error {
|
||||
message: string;
|
||||
context?: unknown;
|
||||
urls?: string[];
|
||||
errData?: unknown;
|
||||
}
|
||||
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="icon" href="/logo.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Sveltekit + Electron yeah</title>
|
||||
<title>IdentityNow Admin Console</title>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-theme="wintry">
|
||||
<div id="svelte">%sveltekit.body%</div>
|
||||
<div style="display: contents" class="h-full overflow-hidden" id="svelte">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,8 +1,18 @@
|
||||
/* Write your global styles here, in PostCSS syntax */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@tailwind variants;
|
||||
|
||||
:root [data-theme='wintry'] {
|
||||
--theme-rounded-base: 5px;
|
||||
--theme-rounded-container: 4px;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
@apply h-full overflow-hidden;
|
||||
}
|
||||
|
||||
td {
|
||||
@apply !align-middle !text-center;
|
||||
}
|
||||
20
Sveltekit-App/src/error.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!-- This page should only appear when an unhandled error is thrown in the +layout.server.ts file -->
|
||||
|
||||
<h1>Game over, man! Game over!</h1>
|
||||
<strong>
|
||||
No but seriously, it appears there was an unhandled issue loading the layout of the application
|
||||
</strong>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<a
|
||||
href="https://github.com/sailpoint-oss/idn-admin-console/issues/new/choose"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Please let us know by submitting an issue on GitHub
|
||||
</a>
|
||||
|
||||
<p>Error Code: %sveltekit.status%</p>
|
||||
<p>Error Message: %sveltekit.error.message%</p>
|
||||
48
Sveltekit-App/src/hooks.server.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import {
|
||||
checkIdnSession,
|
||||
checkSession,
|
||||
checkToken,
|
||||
getSession,
|
||||
getToken,
|
||||
getTokenDetails,
|
||||
lastCheckedToken
|
||||
} from '$lib/utils/oauth';
|
||||
import { redirect, type Handle } from '@sveltejs/kit';
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
const hasSession = checkSession(event.cookies);
|
||||
const hasIdnSession = checkIdnSession(event.cookies);
|
||||
event.locals.hasSession = hasSession;
|
||||
event.locals.hasIdnSession = hasIdnSession;
|
||||
|
||||
if (hasSession) {
|
||||
event.locals.session = getSession(event.cookies);
|
||||
|
||||
if (hasIdnSession) {
|
||||
event.locals.idnSession = await getToken(event.cookies);
|
||||
const lastToken = lastCheckedToken(event.cookies);
|
||||
if (lastToken != '' && lastToken === event.locals.idnSession.access_token) {
|
||||
event.locals.tokenDetails = getTokenDetails(event.cookies);
|
||||
} else {
|
||||
event.locals.tokenDetails = await checkToken(
|
||||
event.locals.session.baseUrl,
|
||||
event.locals.idnSession.access_token
|
||||
);
|
||||
event.cookies.set('tokenDetails', JSON.stringify(event.locals.tokenDetails), {
|
||||
path: '/',
|
||||
httpOnly: false,
|
||||
secure: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event.url.pathname.startsWith('/home') || event.url.pathname.startsWith('/api')) {
|
||||
if (!hasSession || !hasIdnSession) {
|
||||
redirect(302, '/');
|
||||
}
|
||||
}
|
||||
|
||||
const response = await resolve(event);
|
||||
return response;
|
||||
};
|
||||
13
Sveltekit-App/src/lib/Components/CodeBlockModal.svelte
Normal file
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { CodeBlock, getModalStore } from '@skeletonlabs/skeleton';
|
||||
|
||||
const modalStore = getModalStore();
|
||||
</script>
|
||||
|
||||
<div class="card p-4 w-[85%]">
|
||||
<CodeBlock
|
||||
lineNumbers
|
||||
language={$modalStore[0]?.meta?.language || 'json'}
|
||||
code={$modalStore[0]?.meta?.code || ''}
|
||||
/>
|
||||
</div>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { resourcelinks } from './links';
|
||||
</script>
|
||||
|
||||
<div class="p-4 card variant-soft-surface grow">
|
||||
<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>
|
||||
</div>
|
||||
@@ -0,0 +1,52 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import Progress from '../Progress.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));
|
||||
|
||||
function parseClass(status: string) {
|
||||
switch (status) {
|
||||
case 'none':
|
||||
return 'text-success-500';
|
||||
|
||||
case 'minor':
|
||||
return 'text-warning-500';
|
||||
|
||||
case 'major':
|
||||
return 'text-error-500';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="p-4 card grow overflow-hidden flex flex-col">
|
||||
<h1 class="text-center">IdentityNow Status</h1>
|
||||
<div class="grid place-content-center h-full">
|
||||
{#await summaryResp}
|
||||
<Progress width="w-12" />
|
||||
{:then summary}
|
||||
<a
|
||||
href="https://status.sailpoint.com"
|
||||
class="{parseClass(summary?.status?.indicator)} hover:underline"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{summary?.status?.description}
|
||||
</a>
|
||||
{/await}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { supportLinks } from './links';
|
||||
</script>
|
||||
|
||||
<div class="p-4 card variant-soft-surface grow">
|
||||
<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>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { tenantLinks } from './links';
|
||||
|
||||
export let tenantUrl = 'https://placeholder.com';
|
||||
</script>
|
||||
|
||||
<div class="p-4 card variant-soft-surface grow">
|
||||
<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={tenantUrl + link.slug}
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
53
Sveltekit-App/src/lib/Components/HomepageCards/links.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
export 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'
|
||||
}
|
||||
];
|
||||
|
||||
export 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: '📚 Rules Documentation',
|
||||
href: 'https://developer.sailpoint.com/idn/docs/rules/'
|
||||
},
|
||||
{
|
||||
label: '🔒 User Level Access Matrix',
|
||||
href: 'https://documentation.sailpoint.com/saas/help/common/users/user_level_matrix.html'
|
||||
}
|
||||
];
|
||||
|
||||
export 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/' }
|
||||
];
|
||||
43
Sveltekit-App/src/lib/Components/Paginator.svelte
Normal file
@@ -0,0 +1,43 @@
|
||||
<script lang="ts">
|
||||
import { Paginator, type PaginationSettings } from '@skeletonlabs/skeleton';
|
||||
|
||||
export let totalCount: number = 0;
|
||||
export let settings: PaginationSettings;
|
||||
export let onPageChange: (e: CustomEvent<number>) => void;
|
||||
export let onAmountChange: (e: CustomEvent<number>) => void;
|
||||
export let onGo: (e: KeyboardEvent | MouseEvent) => void;
|
||||
export let filters: string = '';
|
||||
export let sorters: string = '';
|
||||
</script>
|
||||
|
||||
<div class=" p-4 flex flex-row flex-wrap justify-between gap-4">
|
||||
<div class="flex flex-row gap-1">
|
||||
<input
|
||||
on:keydown={onGo}
|
||||
bind:value={filters}
|
||||
class="input"
|
||||
title="Filter"
|
||||
type="text"
|
||||
placeholder="Filter"
|
||||
/>
|
||||
<input
|
||||
on:keydown={onGo}
|
||||
bind:value={sorters}
|
||||
class="input"
|
||||
title="Sorter"
|
||||
type="text"
|
||||
placeholder="Sorter"
|
||||
/>
|
||||
<button on:click={onGo} class="btn variant-filled-primary !text-white"> Go </button>
|
||||
</div>
|
||||
<p class="my-auto">Total Count: {totalCount}</p>
|
||||
<Paginator
|
||||
bind:settings
|
||||
on:page={onPageChange}
|
||||
on:amount={onAmountChange}
|
||||
showNumerals={true}
|
||||
maxNumerals={1}
|
||||
showFirstLastButtons={true}
|
||||
showPreviousNextButtons={true}
|
||||
/>
|
||||
</div>
|
||||
14
Sveltekit-App/src/lib/Components/Progress.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { ProgressRadial } from '@skeletonlabs/skeleton';
|
||||
export let width: string = '';
|
||||
</script>
|
||||
|
||||
<div class="grid place-content-center {width}">
|
||||
<ProgressRadial
|
||||
{width}
|
||||
stroke={100}
|
||||
meter="stroke-primary-500"
|
||||
track="stroke-primary-500/30"
|
||||
class="progress-bar"
|
||||
/>
|
||||
</div>
|
||||
49
Sveltekit-App/src/lib/Components/SVGs/HamburgerSVG.svelte
Normal file
@@ -0,0 +1,49 @@
|
||||
<svg
|
||||
class={$$restProps.class || ''}
|
||||
width="800px"
|
||||
height="800px"
|
||||
viewBox="0 0 32 32"
|
||||
enable-background="new 0 0 32 32"
|
||||
id="Editable-line"
|
||||
version="1.1"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
><line
|
||||
fill="none"
|
||||
id="XMLID_103_"
|
||||
stroke="#000000"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-miterlimit="10"
|
||||
stroke-width="2"
|
||||
x1="7"
|
||||
x2="25"
|
||||
y1="16"
|
||||
y2="16"
|
||||
/><line
|
||||
fill="none"
|
||||
id="XMLID_102_"
|
||||
stroke="#000000"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-miterlimit="10"
|
||||
stroke-width="2"
|
||||
x1="7"
|
||||
x2="25"
|
||||
y1="25"
|
||||
y2="25"
|
||||
/><line
|
||||
fill="none"
|
||||
id="XMLID_101_"
|
||||
stroke="#000000"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-miterlimit="10"
|
||||
stroke-width="2"
|
||||
x1="7"
|
||||
x2="25"
|
||||
y1="7"
|
||||
y2="7"
|
||||
/></svg
|
||||
>
|
||||
|
After Width: | Height: | Size: 852 B |
14
Sveltekit-App/src/lib/Components/SVGs/HomeSVG.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
class="w-6 h-6 stroke-current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 371 B |
14
Sveltekit-App/src/lib/Components/SVGs/IdentitiesSVG.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
class="w-6 h-6 stroke-current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 356 B |
14
Sveltekit-App/src/lib/Components/SVGs/MessagesSVG.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
class="w-6 h-6 stroke-current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 318 B |
14
Sveltekit-App/src/lib/Components/SVGs/ReportsSVG.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
class="w-6 h-6 stroke-current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M16 8v8m-4-5v5m-4-2v2m-2 4h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 314 B |
14
Sveltekit-App/src/lib/Components/SVGs/SourcesSVG.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
class="w-6 h-6 stroke-current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7v8a2 2 0 002 2h6M8 7V5a2 2 0 012-2h4.586a1 1 0 01.707.293l4.414 4.414a1 1 0 01.293.707V15a2 2 0 01-2 2h-2M8 7H6a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2v-2"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 387 B |
77
Sveltekit-App/src/lib/Components/VACluster.svelte
Normal file
@@ -0,0 +1,77 @@
|
||||
<script lang="ts">
|
||||
import { Accordion, AccordionItem, CodeBlock } from '@skeletonlabs/skeleton';
|
||||
|
||||
export let cluster: { name: string; id: string } | undefined;
|
||||
|
||||
function formatStatusColor(status: string) {
|
||||
switch (status) {
|
||||
case 'HEALTHY':
|
||||
return 'text-green-500';
|
||||
case 'WARNING':
|
||||
return 'text-red-500';
|
||||
case 'FAILED':
|
||||
return 'text-red-500';
|
||||
default:
|
||||
return 'text-gray-500';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<h2>Virtual Appliance Cluster</h2>
|
||||
<p>Name: {cluster?.name || 'Empty'}</p>
|
||||
<p>ID: {cluster?.id || 'Empty'}</p>
|
||||
|
||||
{#if cluster?.id}
|
||||
{#await fetch(`/api/sailpoint/cluster/${cluster.id}`)}
|
||||
<div class="py-2 placeholder" />
|
||||
{:then clusterResponse}
|
||||
{#await clusterResponse.json()}
|
||||
<div class="py-2 placeholder" />
|
||||
{:then clusterInfo}
|
||||
<p>Pod: {clusterInfo.pod}</p>
|
||||
<p>Description: {clusterInfo.description ? clusterInfo.description : 'Empty'}</p>
|
||||
<p>CCG Version: {clusterInfo.ccgVersion}</p>
|
||||
<p>
|
||||
Debugging Enabled: <span
|
||||
class={clusterInfo.configuration?.debug === 'true' ? 'text-green-500' : 'text-red-500'}
|
||||
>
|
||||
{clusterInfo.configuration.debug === 'true' ? 'True' : 'False'}
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
Status: <span class={formatStatusColor(clusterInfo.status)}>
|
||||
{clusterInfo.status}
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
Alert: <span class={formatStatusColor(clusterInfo.status)}>
|
||||
{clusterInfo.alertKey}
|
||||
</span>
|
||||
</p>
|
||||
<div class="py-2">
|
||||
<p class="underline">Client IDs</p>
|
||||
<ul class="list">
|
||||
{#each clusterInfo.clientIds as client, index}
|
||||
<li>
|
||||
<span>{index + 1}.</span>
|
||||
<span class="flex-auto">{client}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<Accordion>
|
||||
<AccordionItem>
|
||||
<svelte:fragment slot="summary">Raw Data</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
<CodeBlock code={JSON.stringify(clusterInfo, null, 4)} language="json" />
|
||||
</svelte:fragment>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
{:catch error}
|
||||
<p class="text-red-500">Error: {error.message}</p>
|
||||
{/await}
|
||||
{:catch error}
|
||||
<p class="text-red-500">Error: {error.message}</p>
|
||||
{/await}
|
||||
{/if}
|
||||
106
Sveltekit-App/src/lib/Utils.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import type { ModalSettings, ModalStore } from '@skeletonlabs/skeleton';
|
||||
|
||||
export function formatDate(date: string | null | undefined) {
|
||||
if (!date) return 'N/A';
|
||||
return new Date(date).toLocaleString();
|
||||
}
|
||||
|
||||
export function getLimit(url: URL) {
|
||||
return url.searchParams.get('limit') || '250';
|
||||
}
|
||||
|
||||
export function getFilters(url: URL) {
|
||||
return url.searchParams.get('filters') || '';
|
||||
}
|
||||
|
||||
export function getSorters(url: URL) {
|
||||
return url.searchParams.get('sorters') || '';
|
||||
}
|
||||
|
||||
export function getPage(url: URL) {
|
||||
return url.searchParams.get('page') || '0';
|
||||
}
|
||||
|
||||
export function getPaginationParams(url: URL) {
|
||||
return {
|
||||
limit: getLimit(url),
|
||||
page: getPage(url),
|
||||
filters: getFilters(url),
|
||||
sorters: getSorters(url)
|
||||
};
|
||||
}
|
||||
|
||||
type PaginationParams = {
|
||||
limit: string;
|
||||
page: string;
|
||||
filters: string;
|
||||
sorters: string;
|
||||
};
|
||||
|
||||
export function createOnPageChange(params: PaginationParams, path: string) {
|
||||
return function onPageChange(e: CustomEvent): void {
|
||||
const urlParams = new URLSearchParams();
|
||||
urlParams.set('page', e.detail);
|
||||
urlParams.set('limit', params.limit);
|
||||
urlParams.set('sorters', params.sorters);
|
||||
urlParams.set('filters', params.filters);
|
||||
|
||||
console.log(`${path}?${urlParams.toString()}`);
|
||||
|
||||
goto(`${path}?${urlParams.toString()}`);
|
||||
};
|
||||
}
|
||||
|
||||
export function createOnAmountChange(params: PaginationParams, path: string) {
|
||||
return function onAmountChange(e: CustomEvent): void {
|
||||
const urlParams = new URLSearchParams();
|
||||
urlParams.set('page', params.page);
|
||||
urlParams.set('limit', e.detail);
|
||||
urlParams.set('sorters', params.sorters);
|
||||
urlParams.set('filters', params.filters);
|
||||
|
||||
console.log(`${path}?${urlParams.toString()}`);
|
||||
|
||||
goto(`${path}?${urlParams.toString()}`);
|
||||
};
|
||||
}
|
||||
|
||||
export function createOnGo(params: PaginationParams, path: string) {
|
||||
return function onGo(e: KeyboardEvent | MouseEvent): void {
|
||||
if (e.type !== 'click' && (e as KeyboardEvent).key !== 'Enter') return;
|
||||
|
||||
const urlParams = new URLSearchParams();
|
||||
urlParams.set('page', params.page);
|
||||
urlParams.set('limit', params.limit);
|
||||
urlParams.set('sorters', params.sorters);
|
||||
urlParams.set('filters', params.filters);
|
||||
|
||||
console.log(`${path}?${urlParams.toString()}`);
|
||||
|
||||
goto(`${path}?${urlParams.toString()}`);
|
||||
};
|
||||
}
|
||||
|
||||
export function capitalize(s: string) {
|
||||
if (typeof s !== 'string') return '';
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
}
|
||||
|
||||
export function TriggerCodeModal(object: unknown, modalStore: ModalStore) {
|
||||
const modal: ModalSettings = {
|
||||
type: 'component',
|
||||
component: 'codeBlockModal',
|
||||
meta: {
|
||||
code: JSON.stringify(object, null, 4),
|
||||
language: 'json'
|
||||
}
|
||||
};
|
||||
|
||||
modalStore.trigger(modal);
|
||||
}
|
||||
|
||||
export function parseInitials(name: string) {
|
||||
const initials = name.match(/\b(\w)/g) || ['A', 'U'];
|
||||
return initials.join('');
|
||||
}
|
||||
30
Sveltekit-App/src/lib/reports.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export const reports = [
|
||||
{
|
||||
url: '/home/reports/source-account-create-error',
|
||||
name: 'Source Account Create Error',
|
||||
description:
|
||||
'This report will show all source accounts for which there is a create error associated with the source'
|
||||
},
|
||||
{
|
||||
url: '/home/reports/inactive-identities-with-access',
|
||||
name: 'Inactive Identities With Access',
|
||||
description:
|
||||
'This report will show all identities that are inactive but still have access in sources'
|
||||
},
|
||||
{
|
||||
url: '/home/reports/missing-cloud-life-cycle-state',
|
||||
name: 'Missing Cloud Life Cycle State',
|
||||
description: 'This report will show all identities that are missing a cloud life cycle state'
|
||||
},
|
||||
|
||||
{
|
||||
url: '/home/reports/source-owner-configured',
|
||||
name: 'Source Owner Configured',
|
||||
description: 'This report will show all sources and their configured owners'
|
||||
},
|
||||
{
|
||||
url: '/home/reports/source-aggregations',
|
||||
name: 'Source Aggregations',
|
||||
description: 'This report will show all sources and their most recent aggregation events'
|
||||
}
|
||||
];
|
||||
6
Sveltekit-App/src/lib/sailpoint/sdk.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Configuration } from 'sailpoint-api-client';
|
||||
|
||||
export function createConfiguration(baseUrl: string, token: string) {
|
||||
const apiConfig = new Configuration({ baseurl: baseUrl, accessToken: token });
|
||||
return apiConfig;
|
||||
}
|
||||
29
Sveltekit-App/src/lib/sidebar/Sidebar.svelte
Normal file
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { navigation } from './navigation';
|
||||
import { page } from '$app/stores';
|
||||
</script>
|
||||
|
||||
<div class="{$$props.class ?? ''} w-[144px] overflow-hidden bg-surface-50-900-token h-full">
|
||||
<div class="flex flex-col w-36 items-center h-full overflow-hidden">
|
||||
<div class="w-full px-2">
|
||||
<div class="flex flex-col items-center w-full mt-3 border-surface-400-500-token">
|
||||
{#each navigation as section}
|
||||
{#each section.content as link (link.url)}
|
||||
<a
|
||||
href={link.url}
|
||||
data-sveltekit-preload-data="hover"
|
||||
class="flex items-center w-full h-12 px-3 mt-2 rounded"
|
||||
class:bg-surface-active-token={link.url === $page.url.pathname}
|
||||
class:!text-white={link.url === $page.url.pathname}
|
||||
>
|
||||
{#if link.icon}
|
||||
<svelte:component this={link.icon} />
|
||||
{/if}
|
||||
<p class="ml-2 text-sm font-medium">{link.name}</p>
|
||||
</a>
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
15
Sveltekit-App/src/lib/sidebar/SidebarDrawer.svelte
Normal file
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import Sidebar from './Sidebar.svelte';
|
||||
import { getDrawerStore, Drawer } from '@skeletonlabs/skeleton';
|
||||
|
||||
const drawerStore = getDrawerStore();
|
||||
|
||||
$: classesDrawer = $drawerStore.id === 'doc-sidenav' ? 'lg:hidden' : '';
|
||||
</script>
|
||||
|
||||
<Drawer width="w-[144px]" class={classesDrawer}>
|
||||
{#if $drawerStore.id === 'doc-sidenav'}
|
||||
<!-- Doc Sidebar -->
|
||||
<Sidebar />
|
||||
{/if}
|
||||
</Drawer>
|
||||
43
Sveltekit-App/src/lib/sidebar/navigation.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import HomeSvg from '$lib/Components/SVGs/HomeSVG.svelte';
|
||||
import IdentitiesSvg from '$lib/Components/SVGs/IdentitiesSVG.svelte';
|
||||
import MessagesSvg from '$lib/Components/SVGs/MessagesSVG.svelte';
|
||||
import ReportsSvg from '$lib/Components/SVGs/ReportsSVG.svelte';
|
||||
import SourcesSvg from '$lib/Components/SVGs/SourcesSVG.svelte';
|
||||
|
||||
export const navigation = [
|
||||
{
|
||||
name: 'Main',
|
||||
content: [
|
||||
{
|
||||
url: '/home',
|
||||
name: 'Home',
|
||||
description: 'Home page for the application.',
|
||||
icon: HomeSvg
|
||||
},
|
||||
{
|
||||
url: '/home/sources',
|
||||
name: 'Sources',
|
||||
description: 'a list of Sources in IdentityNow.',
|
||||
icon: SourcesSvg
|
||||
},
|
||||
{
|
||||
url: '/home/identities',
|
||||
name: 'Identities',
|
||||
description: 'a list of Identities in IdentityNow.',
|
||||
icon: IdentitiesSvg
|
||||
},
|
||||
{
|
||||
url: '/home/reports',
|
||||
name: 'Reports',
|
||||
description: 'a list of Reports for IdentityNow.',
|
||||
icon: ReportsSvg
|
||||
},
|
||||
{
|
||||
url: '/home/courier',
|
||||
name: 'Courier',
|
||||
description: 'an API client for IdentityNow with authentication baked right in.',
|
||||
icon: MessagesSvg
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
211
Sveltekit-App/src/lib/utils/oauth.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
import type { Cookies } from '@sveltejs/kit';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import axios from 'axios';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
export function generateAuthLink(tenantUrl: string) {
|
||||
return `${tenantUrl}/oauth/authorize?client_id=sailpoint-cli&response_type=code&redirect_uri=http://localhost:3000/callback`;
|
||||
}
|
||||
|
||||
export type Session = {
|
||||
baseUrl: string;
|
||||
tenantUrl: string;
|
||||
};
|
||||
|
||||
export type IdnSession = {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
claims_supported: string;
|
||||
expires_in: string;
|
||||
identity_id: string;
|
||||
internal: string;
|
||||
jti: string;
|
||||
org: string;
|
||||
pod: string;
|
||||
scope: string;
|
||||
strong_auth: string;
|
||||
strong_auth_supported: string;
|
||||
tenant_id: string;
|
||||
token_type: string;
|
||||
};
|
||||
|
||||
export type TokenDetails = {
|
||||
tenant_id: string;
|
||||
internal: boolean;
|
||||
pod: string;
|
||||
org: string;
|
||||
identity_id: string;
|
||||
user_name: string;
|
||||
strong_auth: boolean;
|
||||
force_auth_supported: boolean;
|
||||
active: boolean;
|
||||
authorities: string[];
|
||||
client_id: string;
|
||||
encoded_scope: string[];
|
||||
strong_auth_supported: boolean;
|
||||
claims_supported: boolean;
|
||||
scope: string[];
|
||||
exp: number;
|
||||
jti: string;
|
||||
};
|
||||
|
||||
export function lastCheckedToken(cookies: Cookies): string {
|
||||
const lastCheckedToken = cookies.get('lastCheckedToken');
|
||||
if (!lastCheckedToken) {
|
||||
return '';
|
||||
}
|
||||
return lastCheckedToken;
|
||||
}
|
||||
|
||||
export function getTokenDetails(cookies: Cookies): TokenDetails {
|
||||
const tokenDetailsString = cookies.get('tokenDetails');
|
||||
if (!tokenDetailsString) {
|
||||
return {} as TokenDetails;
|
||||
}
|
||||
return JSON.parse(tokenDetailsString) as TokenDetails;
|
||||
}
|
||||
|
||||
export function setTokenDetails(cookies: Cookies, tokenDetails: TokenDetails) {
|
||||
cookies.set('tokenDetails', JSON.stringify(tokenDetails), {
|
||||
path: '/',
|
||||
httpOnly: false,
|
||||
secure: false
|
||||
});
|
||||
}
|
||||
|
||||
export async function checkToken(apiUrl: string, token: string): Promise<TokenDetails> {
|
||||
const body = 'token=' + token;
|
||||
const url = `${apiUrl}/oauth/check_token/`;
|
||||
const response = await axios.post(url, body).catch(function (err) {
|
||||
if (err.response) {
|
||||
// Request made and server responded
|
||||
console.log(err.response.data);
|
||||
console.log(err.response.status);
|
||||
console.log(err.response.headers);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
// if (response) {
|
||||
// console.log(response.data);
|
||||
// }
|
||||
const tokenDetails = response!.data;
|
||||
|
||||
return tokenDetails;
|
||||
}
|
||||
|
||||
export async function refreshToken(apiUrl: string, refreshToken: string): Promise<IdnSession> {
|
||||
const url = `${apiUrl}/oauth/token?grant_type=refresh_token&client_id=sailpoint-cli&refresh_token=${refreshToken}`;
|
||||
const response = await axios.post(url).catch(function (err) {
|
||||
if (err.response) {
|
||||
// Request made and server responded
|
||||
console.log(err.response.data);
|
||||
console.log(err.response.status);
|
||||
console.log(err.response.headers);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
// if (response) {
|
||||
// console.log(response.data)
|
||||
// }
|
||||
const idnSession: IdnSession = response!.data as IdnSession;
|
||||
return idnSession;
|
||||
}
|
||||
|
||||
export async function logout(cookies: Cookies) {
|
||||
cookies.delete('session', {
|
||||
path: '/',
|
||||
httpOnly: false,
|
||||
secure: false
|
||||
});
|
||||
|
||||
cookies.delete('idnSession', {
|
||||
path: '/',
|
||||
httpOnly: false,
|
||||
secure: false
|
||||
});
|
||||
}
|
||||
|
||||
export function checkSession(cookies: Cookies): boolean {
|
||||
const sessionString = cookies.get('session');
|
||||
if (!sessionString) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function checkIdnSession(cookies: Cookies): boolean {
|
||||
const idnSessionString = cookies.get('idnSession');
|
||||
if (!idnSessionString) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function getSession(cookies: Cookies): Session {
|
||||
const sessionString = cookies.get('session');
|
||||
if (!sessionString) return { baseUrl: '', tenantUrl: '' };
|
||||
return JSON.parse(sessionString) as Session;
|
||||
}
|
||||
|
||||
export async function getToken(cookies: Cookies): Promise<IdnSession> {
|
||||
const sessionString = cookies.get('session');
|
||||
const idnSessionString = cookies.get('idnSession');
|
||||
|
||||
const session: Session = JSON.parse(sessionString!);
|
||||
|
||||
if (!idnSessionString) {
|
||||
console.log('IdnSession does not exist, redirecting to login');
|
||||
redirect(302, generateAuthLink(session.tenantUrl));
|
||||
}
|
||||
|
||||
const idnSession: IdnSession = JSON.parse(idnSessionString);
|
||||
|
||||
if (
|
||||
idnSession &&
|
||||
session &&
|
||||
!session.baseUrl.toLowerCase().includes(idnSession.org.toLowerCase())
|
||||
) {
|
||||
redirect(302, generateAuthLink(session.tenantUrl));
|
||||
}
|
||||
|
||||
if (isJwtExpired(idnSession.access_token)) {
|
||||
console.log('Refreshing IdnSession token...');
|
||||
const newSession = await refreshToken(session.baseUrl, idnSession.refresh_token);
|
||||
cookies.set('idnSession', JSON.stringify(newSession), {
|
||||
path: '/',
|
||||
httpOnly: false,
|
||||
secure: false
|
||||
});
|
||||
return Promise.resolve(newSession);
|
||||
} else {
|
||||
console.log('IdnSession token is good');
|
||||
return Promise.resolve(idnSession);
|
||||
}
|
||||
}
|
||||
|
||||
function isJwtExpired(token: string): boolean {
|
||||
try {
|
||||
const decodedToken = jwt.decode(token, { complete: true });
|
||||
if (
|
||||
!decodedToken ||
|
||||
!decodedToken.payload ||
|
||||
typeof decodedToken.payload === 'string' ||
|
||||
!decodedToken.payload.exp
|
||||
) {
|
||||
// The token is missing the expiration claim ('exp') or is not a valid JWT.
|
||||
return true; // Treat as expired for safety.
|
||||
}
|
||||
|
||||
// Get the expiration timestamp from the token.
|
||||
const expirationTimestamp = decodedToken.payload.exp;
|
||||
|
||||
// Get the current timestamp.
|
||||
const currentTimestamp = Math.floor(Date.now() / 1000);
|
||||
|
||||
// Check if the token has expired.
|
||||
return currentTimestamp >= expirationTimestamp;
|
||||
} catch (error) {
|
||||
// An error occurred during decoding.
|
||||
return true; // Treat as expired for safety.
|
||||
}
|
||||
}
|
||||
55
Sveltekit-App/src/routes/+error.svelte
Normal file
@@ -0,0 +1,55 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { CodeBlock } from '@skeletonlabs/skeleton';
|
||||
|
||||
$: console.log($page);
|
||||
</script>
|
||||
|
||||
<div class="p-4">
|
||||
<div class="card p-4">
|
||||
<p class="text-center p-2">
|
||||
WHOOPS! <br /> <span class="text-red-500">a {$page.status} error occurred.</span> <br /> If
|
||||
you believe this is a bug please submit an issue on
|
||||
<a
|
||||
class="underline text-blue-500 hover:text-blue-700"
|
||||
href="https://github.com/sailpoint-oss/idn-admin-console/issues/new/choose"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
</p>
|
||||
{#if $page.error?.message}
|
||||
<p class="py-2">Message: <br /><span class="text-red-500">{$page.error.message}</span></p>
|
||||
{/if}
|
||||
|
||||
{#if $page.error?.urls}
|
||||
<p>These links may be helpful:</p>
|
||||
<ul>
|
||||
{#each $page.error?.urls as url}
|
||||
<li>
|
||||
-
|
||||
<a
|
||||
class="underline text-blue-500 hover:text-blue-700"
|
||||
href={url}
|
||||
rel="noreferrer"
|
||||
target="_blank">{url}</a
|
||||
>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if $page.error?.context}
|
||||
<div class="py-2">
|
||||
<p>Context</p>
|
||||
<CodeBlock language="json" code={JSON.stringify($page.error?.context, null, 4)} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $page.error?.errData}
|
||||
<div class="pt-2">
|
||||
<p>Error Data</p>
|
||||
<CodeBlock language="json" code={JSON.stringify($page.error?.errData, null, 4)} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
3
Sveltekit-App/src/routes/+layout.server.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const load = async ({ locals }) => {
|
||||
return { tokenDetails: locals.tokenDetails };
|
||||
};
|
||||
206
Sveltekit-App/src/routes/+layout.svelte
Normal file
@@ -0,0 +1,206 @@
|
||||
<script lang="ts">
|
||||
import { Modal, initializeStores, type ModalComponent } from '@skeletonlabs/skeleton';
|
||||
import { onMount } from 'svelte';
|
||||
import '../app.postcss';
|
||||
import CodeBlockModal from '$lib/Components/CodeBlockModal.svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { capitalize, parseInitials } from '$lib/Utils';
|
||||
|
||||
import Sidebar from '$lib/sidebar/Sidebar.svelte';
|
||||
import {
|
||||
AppBar,
|
||||
AppShell,
|
||||
Avatar,
|
||||
LightSwitch,
|
||||
getDrawerStore,
|
||||
popup,
|
||||
storePopup,
|
||||
type DrawerSettings,
|
||||
type PopupSettings
|
||||
} from '@skeletonlabs/skeleton';
|
||||
|
||||
import { arrow, autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';
|
||||
|
||||
import HamburgerSvg from '$lib/Components/SVGs/HamburgerSVG.svelte';
|
||||
import SidebarDrawer from '$lib/sidebar/SidebarDrawer.svelte';
|
||||
|
||||
import hljs from 'highlight.js/lib/core';
|
||||
|
||||
// Import each language module you require
|
||||
import xml from 'highlight.js/lib/languages/xml'; // for HTML
|
||||
import css from 'highlight.js/lib/languages/css';
|
||||
import json from 'highlight.js/lib/languages/json';
|
||||
import javascript from 'highlight.js/lib/languages/javascript';
|
||||
import typescript from 'highlight.js/lib/languages/typescript';
|
||||
import shell from 'highlight.js/lib/languages/shell';
|
||||
|
||||
import { storeHighlightJs } from '@skeletonlabs/skeleton';
|
||||
|
||||
import 'highlight.js/styles/github-dark.css';
|
||||
|
||||
initializeStores();
|
||||
let ready: boolean = false;
|
||||
onMount(() => (ready = true));
|
||||
|
||||
const modalRegistry: Record<string, ModalComponent> = {
|
||||
// Set a unique modal ID, then pass the component reference
|
||||
codeBlockModal: { ref: CodeBlockModal }
|
||||
|
||||
// ...
|
||||
};
|
||||
|
||||
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
|
||||
|
||||
export let data;
|
||||
|
||||
// Register each imported language module
|
||||
hljs.registerLanguage('xml', xml); // for HTML
|
||||
hljs.registerLanguage('css', css);
|
||||
hljs.registerLanguage('json', json);
|
||||
hljs.registerLanguage('javascript', javascript);
|
||||
hljs.registerLanguage('typescript', typescript);
|
||||
hljs.registerLanguage('shell', shell);
|
||||
|
||||
storeHighlightJs.set(hljs);
|
||||
|
||||
let crumbs: Array<{ label: string; href: string }> = [];
|
||||
|
||||
$: {
|
||||
// Remove zero-length tokens.
|
||||
const tokens = $page.url.pathname.split('/').filter((t) => t !== '');
|
||||
|
||||
let tokenPath = '';
|
||||
|
||||
crumbs = tokens.map((t) => {
|
||||
tokenPath += '/' + t;
|
||||
|
||||
return {
|
||||
label: t
|
||||
.split('-')
|
||||
.map((word) => capitalize(word))
|
||||
.join(' '),
|
||||
href: tokenPath
|
||||
};
|
||||
});
|
||||
|
||||
crumbs = crumbs.filter((c) => c.label !== 'Logout' && c.label !== 'Callback');
|
||||
}
|
||||
|
||||
const drawerStore = getDrawerStore();
|
||||
|
||||
// Drawer Handler
|
||||
function drawerOpen(): void {
|
||||
const s: DrawerSettings = { id: 'doc-sidenav' };
|
||||
drawerStore.open(s);
|
||||
}
|
||||
|
||||
const popupAccount: PopupSettings = {
|
||||
// Represents the type of event that opens/closed the popup
|
||||
event: 'click',
|
||||
// Matches the data-popup value on your popup element
|
||||
target: 'popupAccount',
|
||||
// Defines which side of your trigger the popup will appear
|
||||
placement: 'bottom'
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal components={modalRegistry} />
|
||||
|
||||
<SidebarDrawer />
|
||||
|
||||
<AppShell>
|
||||
<svelte:fragment slot="header">
|
||||
<AppBar padding="p-2" class="h-![72px]">
|
||||
<svelte:fragment slot="lead">
|
||||
<div class="flex items-center space-x-4">
|
||||
{#if data.tokenDetails}
|
||||
<button on:click={drawerOpen} class="btn-icon btn-icon-sm lg:!hidden">
|
||||
<HamburgerSvg class="w-6 h-6" />
|
||||
</button>
|
||||
{/if}
|
||||
<img class="h-8 w-8" src="/logo.ico" alt="SailPoint TetraSail" />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<p class="text-xl lg:!block hidden">IdentityNow Admin Console</p>
|
||||
<svelte:fragment slot="trail">
|
||||
<LightSwitch />
|
||||
{#if data.tokenDetails}
|
||||
<div class="rounded-full w-fit" use:popup={popupAccount}>
|
||||
<Avatar
|
||||
initials={parseInitials(data?.tokenDetails?.user_name)}
|
||||
border="hover:border-2 border-surface-300-600-token hover:!border-primary-500"
|
||||
cursor="cursor-pointer"
|
||||
width="w-10"
|
||||
/>
|
||||
<div
|
||||
class="card p-4 w-72 !shadow-xl bg-surface-100-800-token"
|
||||
data-popup="popupAccount"
|
||||
>
|
||||
<div class="arrow bg-surface-50-900-token" />
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="space-y-4">
|
||||
<Avatar initials={parseInitials(data?.tokenDetails?.user_name)} width="w-16" />
|
||||
<div>
|
||||
<p class="font-bold">{data?.tokenDetails?.user_name}</p>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<small>
|
||||
<span class="opacity-50">Tenant:</span>
|
||||
<strong>{data?.tokenDetails?.org}</strong>
|
||||
</small>
|
||||
<small>
|
||||
<span class="opacity-50">Pod:</span>
|
||||
<strong>{data?.tokenDetails?.pod}</strong>
|
||||
</small>
|
||||
</div>
|
||||
<small><span class="opacity-50">Scopes:</span></small>
|
||||
<div class="flex gap-4 flex-wrap">
|
||||
{#each data?.tokenDetails?.scope as scope}
|
||||
<small><strong></strong>{scope}</small>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="/logout" class="btn variant-soft w-full">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</AppBar>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="sidebarLeft">
|
||||
{#if data.tokenDetails}
|
||||
<Sidebar class="hidden lg:grid overflow-hidden" />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<!-- <svelte:fragment slot="sidebarRight">Sidebar Right</svelte:fragment> -->
|
||||
<!-- <svelte:fragment slot="pageHeader">Page Header</svelte:fragment> -->
|
||||
<!-- Router Slot -->
|
||||
<div class="flex flex-col">
|
||||
{#if crumbs.length > 0}
|
||||
<div class="pl-2 pt-2 pr-2">
|
||||
<ol class="breadcrumb card p-2">
|
||||
{#each crumbs as crumb, i}
|
||||
<!-- If crumb index is less than the breadcrumb length minus 1 -->
|
||||
{#if i < crumbs.length - 1}
|
||||
<li class="crumb"><a class="anchor" href={crumb.href}>{crumb.label}</a></li>
|
||||
<li class="crumb-separator" aria-hidden>›</li>
|
||||
{:else}
|
||||
<li class="crumb">{crumb.label}</li>
|
||||
{/if}
|
||||
{/each}
|
||||
</ol>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="p-2 grow">
|
||||
{#if ready}
|
||||
<slot />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<!-- ---- / ---- -->
|
||||
<!-- <svelte:fragment slot="pageFooter">Page Footer</svelte:fragment> -->
|
||||
<!-- <svelte:fragment slot="footer">Footer</svelte:fragment> -->
|
||||
</AppShell>
|
||||
51
Sveltekit-App/src/routes/+page.server.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { generateAuthLink } from '$lib/utils/oauth';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { Actions } from './$types';
|
||||
|
||||
export const actions = {
|
||||
default: async ({ cookies, request }) => {
|
||||
const data = await request.formData();
|
||||
|
||||
const baseUrl = data.get('baseUrl');
|
||||
const tenantUrl = data.get('tenantUrl');
|
||||
|
||||
if (!baseUrl || !tenantUrl) {
|
||||
redirect(302, '/login');
|
||||
}
|
||||
|
||||
const session = { baseUrl: baseUrl.toString(), tenantUrl: tenantUrl.toString() };
|
||||
console.log('session', session);
|
||||
|
||||
const idnSessionString = cookies.get('idnSession');
|
||||
|
||||
if (idnSessionString) {
|
||||
// console.log('sessionString', sessionString);
|
||||
|
||||
const idnSession = JSON.parse(idnSessionString);
|
||||
if (idnSession && session.baseUrl.toLowerCase().includes(idnSession.org.toLowerCase())) {
|
||||
console.log('Credential Cache Hit');
|
||||
redirect(302, '/home');
|
||||
} else {
|
||||
console.log('Credential Cache Miss');
|
||||
}
|
||||
}
|
||||
|
||||
cookies.set('session', JSON.stringify(session), {
|
||||
path: '/'
|
||||
});
|
||||
redirect(302, generateAuthLink(tenantUrl.toString()));
|
||||
}
|
||||
} satisfies Actions;
|
||||
|
||||
export const load = async ({ locals }) => {
|
||||
if (!locals.hasSession || !locals.hasIdnSession) return {};
|
||||
|
||||
if (
|
||||
locals.session &&
|
||||
locals.idnSession &&
|
||||
locals.session.baseUrl.toLowerCase().includes(locals.idnSession.org.toLowerCase())
|
||||
) {
|
||||
redirect(302, '/home');
|
||||
}
|
||||
return {};
|
||||
};
|
||||
77
Sveltekit-App/src/routes/+page.svelte
Normal file
@@ -0,0 +1,77 @@
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/environment';
|
||||
import { enhance } from '$app/forms';
|
||||
import { localStorageStore } from '@skeletonlabs/skeleton';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
let desktop: string;
|
||||
|
||||
if (window.electron && browser) {
|
||||
window.electron.receive('from-main', (data: any) => {
|
||||
desktop = `Received Message "${data}" from Electron`;
|
||||
console.log(desktop);
|
||||
});
|
||||
}
|
||||
|
||||
const agent = window.electron ? 'Electron' : 'Browser';
|
||||
|
||||
const tenant: Writable<string> = localStorageStore('tenant', 'tenant');
|
||||
const domain: Writable<string> = localStorageStore('domain', 'identitynow');
|
||||
const baseUrl: Writable<string> = localStorageStore(
|
||||
'baseUrl',
|
||||
'https://${tenant}.api.${domain}.com'
|
||||
);
|
||||
const tenantUrl: Writable<string> = localStorageStore(
|
||||
'tenantUrl',
|
||||
'https://${tenant}.${domain}.com'
|
||||
);
|
||||
|
||||
$: baseUrl.set(`https://${$tenant}.api.${$domain}.com`);
|
||||
$: tenantUrl.set(`https://${$tenant}.${$domain}.com`);
|
||||
</script>
|
||||
|
||||
<main class="p-32 h-full">
|
||||
<div class="flex flex-row justify-center">
|
||||
<img
|
||||
class="h-12 min-w-[590px]"
|
||||
src="/SailPoint-Developer-Community-Lockup.png"
|
||||
alt="sailPoint Logo"
|
||||
/>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="text-2xl text-center py-2">Enter your tenant information to continue</div>
|
||||
<form method="POST" use:enhance class="flex flex-col gap-4">
|
||||
<label class="">
|
||||
Tenant
|
||||
<input name="tenant" placeholder={`tenant`} bind:value={$tenant} class="input p-2" />
|
||||
</label>
|
||||
<label class="">
|
||||
Domain
|
||||
<input name="domain" placeholder={`identitynow`} bind:value={$domain} class="input p-2" />
|
||||
</label>
|
||||
<label class="">
|
||||
API Base URL
|
||||
<input
|
||||
name="baseUrl"
|
||||
placeholder={`https://${tenant}.api.${domain}.com`}
|
||||
bind:value={$baseUrl}
|
||||
class="input p-2"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="">
|
||||
Tenant URL
|
||||
<input
|
||||
name="tenantUrl"
|
||||
placeholder={`https://${tenant}.identitynow.com`}
|
||||
bind:value={$tenantUrl}
|
||||
class="input p-2"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<button type="submit" class="btn variant-filled-primary w-full mt-2 !text-white text-lg">
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { createConfiguration } from '$lib/sailpoint/sdk';
|
||||
import { getToken } from '$lib/utils/oauth';
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { ManagedClustersBetaApi } from 'sailpoint-api-client';
|
||||
|
||||
/** @type {import('./$types').RequestHandler} */
|
||||
export async function GET({ cookies, params }) {
|
||||
// Generic SDK setup
|
||||
const session = JSON.parse(cookies.get('session')!);
|
||||
const idnSession = await getToken(cookies);
|
||||
|
||||
const config = createConfiguration(session.baseUrl, idnSession.access_token);
|
||||
|
||||
// Route specific SDK call
|
||||
const api = new ManagedClustersBetaApi(config);
|
||||
|
||||
const val = await api.getManagedCluster({ id: params.clusterID });
|
||||
// console.log(val);
|
||||
return json(val.data);
|
||||
}
|
||||
44
Sveltekit-App/src/routes/callback/+page.server.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { generateAuthLink } from '$lib/utils/oauth';
|
||||
import { error, redirect } from '@sveltejs/kit';
|
||||
import axios from 'axios';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { counterList } from './loadinglist';
|
||||
|
||||
export const load: PageServerLoad = async ({ url, cookies, locals }) => {
|
||||
const code = url.searchParams.get('code');
|
||||
|
||||
if (!code) error(500, 'No Authorization Code Provided');
|
||||
|
||||
if (!locals.session) error(500, 'No Session Found');
|
||||
|
||||
const response = await axios
|
||||
.post(
|
||||
`${locals.session.baseUrl}/oauth/token?grant_type=authorization_code&client_id=sailpoint-cli&code=${code}&redirect_uri=http://localhost:3000/callback`
|
||||
)
|
||||
.catch(function (err) {
|
||||
if (err.response) {
|
||||
// Request made and server responded
|
||||
console.log(err.response.data);
|
||||
console.log(err.response.status);
|
||||
console.log(err.response.headers);
|
||||
redirect(302, generateAuthLink(locals.session!.tenantUrl));
|
||||
} else if (err.request) {
|
||||
// The request was made but no response was received
|
||||
error(500, { message: 'No Response From IDN' });
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an err
|
||||
error(500, {
|
||||
message: 'Error during Axios Request'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
console.log(response.data);
|
||||
cookies.set('idnSession', JSON.stringify(response.data), {
|
||||
path: '/',
|
||||
httpOnly: false,
|
||||
secure: false
|
||||
});
|
||||
|
||||
return { counterList };
|
||||
};
|
||||
@@ -1,16 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/environment';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import AnimatedCounter from '$lib/AnimatedCounter.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import AnimatedCounter from '$lib/Components/AnimatedCounter.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
export let data;
|
||||
console.log(data);
|
||||
if (browser) setTimeout(() => goto(`/home`), 1000);
|
||||
if (browser) setTimeout(() => goto(`/home`), 2000);
|
||||
</script>
|
||||
|
||||
<div class="z-50 mx-auto flex h-full items-center justify-center p-4 md:p-0">
|
||||
<div class="grid place-content-center h-[80vh]">
|
||||
<div class="card card-glass z-50 space-y-5 p-4 md:p-10">
|
||||
<div class="skills">
|
||||
<AnimatedCounter
|
||||
@@ -9,8 +9,8 @@ export const counterList = [
|
||||
"Don't think of purple hippos...",
|
||||
'We need a new fuse...',
|
||||
'Have a good day.',
|
||||
'Upgrading Windows, your PC will restart several times. Sit back and relax.',
|
||||
'640K ought to be enough for anybody',
|
||||
'Upgrading Windows, your PC will restart several times. Sit back and relax. /s',
|
||||
'640K ram+ ought to be enough for anybody',
|
||||
'The architects are still drafting',
|
||||
"We're building the buildings as fast as we can",
|
||||
'Would you prefer chicken, steak, or tofu?',
|
||||
@@ -41,7 +41,6 @@ export const counterList = [
|
||||
'Reconfoobling energymotron...',
|
||||
'(Insert quarter)',
|
||||
'Are we there yet?',
|
||||
'Have you lost weight?',
|
||||
'Just count to 10',
|
||||
'Why so serious?',
|
||||
"It's not you. It's me.",
|
||||
@@ -86,7 +85,7 @@ export const counterList = [
|
||||
'99 bottles of beer on the wall..',
|
||||
'Stay awhile and listen..',
|
||||
'Be careful not to step in the git-gui',
|
||||
'You edhall not pass! yet..',
|
||||
'You shall not pass! yet..',
|
||||
'Load it and they will come',
|
||||
'Convincing AI not to turn evil..',
|
||||
'There is no spoon. Because we are not done loading it',
|
||||
@@ -97,37 +96,25 @@ export const counterList = [
|
||||
'When nothing is going right, go left!!...',
|
||||
"I love my job only when I'm on vacation...",
|
||||
"i'm not lazy, I'm just relaxed!!",
|
||||
'Never steal. The government hates competition....',
|
||||
'Why are they called apartments if they are all stuck together?',
|
||||
'Life is Short, Talk Fast!!!!',
|
||||
'Optimism, is a lack of information.....',
|
||||
'Save water and shower together',
|
||||
'Whenever I find the key to success, someone changes the lock.',
|
||||
'Sometimes I think war is Gods way of teaching us geography.',
|
||||
'Ive got problem for your solution…..',
|
||||
'Where theres a will, theres a relative.',
|
||||
'User: the word computer professionals use when they mean !!idiot!!',
|
||||
'Adults are just kids with money.',
|
||||
'I think I am, therefore, I am. I think.',
|
||||
'A kiss is like a fight, with mouths.',
|
||||
'You dont pay taxes—they take taxes.',
|
||||
'Coffee, Chocolate, Men. The richer the better!',
|
||||
'I am free of all prejudices. I hate everyone equally.',
|
||||
'git happens',
|
||||
'May the forks be with you',
|
||||
'A commit a day keeps the mobs away',
|
||||
"This is not a joke, it's a commit.",
|
||||
'Constructing additional pylons...',
|
||||
'Roping some seaturtles...',
|
||||
'Locating Jebediah Kerman...',
|
||||
'We are not liable for any broken screens as a result of waiting.',
|
||||
'Hello IT, have you tried turning it off and on again?',
|
||||
'If you type Google into Google you can break the internet',
|
||||
'Well, this is embarrassing.',
|
||||
'What is the airspeed velocity of an unladen swallow?',
|
||||
'Hello, IT... Have you tried forcing an unexpected reboot?',
|
||||
"They just toss us away like yesterday's jam.",
|
||||
"They're fairly regular, the beatings, yes. I'd say we're on a bi-weekly beating.",
|
||||
'The Elders of the Internet would never stand for it.',
|
||||
'Space is invisible mind dust, and stars are but wishes.',
|
||||
"Didn't know paint dried so quickly.",
|
||||
@@ -139,10 +126,8 @@ export const counterList = [
|
||||
'If Im not back in five minutes, just wait longer.',
|
||||
'Some days, you just cant get rid of a bug!',
|
||||
'Were going to need a bigger boat.',
|
||||
'Chuck Norris never git push. The repo pulls before.',
|
||||
'Web developers do it with <style>',
|
||||
'I need to git pull --my-life-together',
|
||||
'Java developers never RIP. They just get Garbage Collected.',
|
||||
'Cracking military-grade encryption...',
|
||||
'Simulating traveling salesman...',
|
||||
'Proving P=NP...',
|
||||
@@ -150,12 +135,9 @@ export const counterList = [
|
||||
'Twiddling thumbs...',
|
||||
'Searching for plot device...',
|
||||
'Trying to sort in O(n)...',
|
||||
'Laughing at your pictures-i mean, loading...',
|
||||
'Sending data to NS-i mean, our servers.',
|
||||
'Looking for sense of humour, please hold on.',
|
||||
'Please wait while the intern refills his coffee.',
|
||||
'A different error message? Finally, some progress!',
|
||||
'Hold on while we wrap up our git together...sorry',
|
||||
'Please hold on as we reheat our coffee',
|
||||
'Kindly hold on as we convert this bug to a feature...',
|
||||
'Kindly hold on as our intern quits vim...',
|
||||
@@ -163,7 +145,6 @@ export const counterList = [
|
||||
'Installing dependencies',
|
||||
'Switching to the latest JS framework...',
|
||||
'Distracted by cat gifs',
|
||||
'Finding someone to hold my beer',
|
||||
'BRB, working on my side project',
|
||||
'@todo Insert witty loading message',
|
||||
"Let's hope it's worth the wait",
|
||||
@@ -175,20 +156,17 @@ export const counterList = [
|
||||
"It is dark. You're likely to be eaten by a grue.",
|
||||
'Loading funny message...',
|
||||
"It's 10:00pm. Do you know where your children are?",
|
||||
'Waiting Daenerys say all her titles...',
|
||||
'Waiting for Daenerys say all her titles...',
|
||||
'Feel free to spin in your chair',
|
||||
'What the what?',
|
||||
'format C: ...',
|
||||
'Forget you saw that password I just typed into the IM ...',
|
||||
"What's under there?",
|
||||
'Your computer has a virus, its name is Windows!',
|
||||
'Go ahead, hold your breath and do an ironman plank till loading complete',
|
||||
'Go ahead, hold your breath and do an ironman plank till loading is complete',
|
||||
'Bored of slow loading spinner, buy more RAM!',
|
||||
"Help, I'm trapped in a loader!",
|
||||
'What is the difference btwn a hippo and a zippo? One is really heavy, the other is a little lighter',
|
||||
'What is the difference between a hippo and a zippo? One is really heavy, the other is a little lighter',
|
||||
'Please wait, while we purge the Decepticons for you. Yes, You can thanks us later!',
|
||||
"Chuck Norris once urinated in a semi truck's gas tank as a joke....that truck is now known as Optimus Prime.",
|
||||
'Chuck Norris doesnt wear a watch. HE decides what time it is.',
|
||||
'Mining some bitcoins...',
|
||||
'Downloading more RAM..',
|
||||
'Updating to Windows Vista...',
|
||||
@@ -221,11 +199,9 @@ export const counterList = [
|
||||
'Definitely not a virus...',
|
||||
'You may call me Steve.',
|
||||
'You seem like a nice person...',
|
||||
"Coffee at my place, tommorow at 10A.M. - don't be late!",
|
||||
'Work, work...',
|
||||
'Patience! This is difficult, you know...',
|
||||
'Discovering new ways of making you wait...',
|
||||
'Your time is very important to us. Please wait while we ignore you...',
|
||||
'Time flies like an arrow; fruit flies like a banana',
|
||||
'Two men walked into a bar; the third ducked...',
|
||||
'Sooooo... Have you seen my vacation photos yet?',
|
||||
@@ -249,7 +225,7 @@ export const counterList = [
|
||||
'Adjusting the dilithium crystal converter assembly',
|
||||
'Reversing the shield polarity',
|
||||
'Disrupting warp fields with an inverse graviton burst',
|
||||
'Up, Up, Down, Down, Left, Right, Left, Right, B, A.',
|
||||
'Up, Up, Down, Down, Left, Right, Left, Right, B, A, Select, Start',
|
||||
'Do you like my loading animation? I made it myself',
|
||||
'Whoah, look at it go!',
|
||||
"No, I'm awake. I was just resting my eyes.",
|
||||
@@ -257,4 +233,5 @@ export const counterList = [
|
||||
"Don't panic... AHHHHH!",
|
||||
'Ensuring Gnomes are still short.',
|
||||
'Baking ice cream...'
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
].sort((a, b) => 0.5 - Math.random());
|
||||
5
Sveltekit-App/src/routes/home/+page.server.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const load = async ({ locals }) => {
|
||||
if (!locals.hasSession) return { baseUrl: '', tenantUrl: '' };
|
||||
|
||||
return { session: locals.session };
|
||||
};
|
||||
18
Sveltekit-App/src/routes/home/+page.svelte
Normal file
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import ResourceLinksCard from '$lib/Components/HomepageCards/ResourceLinksCard.svelte';
|
||||
import StatusCard from '$lib/Components/HomepageCards/StatusCard.svelte';
|
||||
import SupportLinksCard from '$lib/Components/HomepageCards/SupportLinksCard.svelte';
|
||||
import TenantLinksCard from '$lib/Components/HomepageCards/TenantLinksCard.svelte';
|
||||
|
||||
export let data;
|
||||
console.log(data);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2 grow">
|
||||
<div class="flex flex-row flex-wrap gap-2 grow">
|
||||
<StatusCard />
|
||||
<TenantLinksCard tenantUrl={data.session?.tenantUrl} />
|
||||
<ResourceLinksCard />
|
||||
<SupportLinksCard />
|
||||
</div>
|
||||
</div>
|
||||
19
Sveltekit-App/src/routes/home/courier/+page.server.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { getSession, getToken } from '$lib/utils/oauth.js';
|
||||
|
||||
export const load = async ({ fetch, cookies }) => {
|
||||
const V3SpecRes = fetch(
|
||||
'https://raw.githubusercontent.com/sailpoint-oss/api-specs/main/dereferenced/deref-sailpoint-api.v3.json'
|
||||
);
|
||||
|
||||
const BetaSpecRes = fetch(
|
||||
'https://raw.githubusercontent.com/sailpoint-oss/api-specs/main/dereferenced/deref-sailpoint-api.beta.json'
|
||||
);
|
||||
|
||||
const V3Spec = await V3SpecRes.then((r) => r.json()).then((r) => r.paths);
|
||||
const BetaSpec = await BetaSpecRes.then((r) => r.json()).then((r) => r.paths);
|
||||
|
||||
const session = await getSession(cookies);
|
||||
const idnSession = await getToken(cookies);
|
||||
|
||||
return { V3Spec, BetaSpec, idnSession, session };
|
||||
};
|
||||
160
Sveltekit-App/src/routes/home/courier/+page.svelte
Normal file
@@ -0,0 +1,160 @@
|
||||
<script lang="ts">
|
||||
import { JSONEditor } from 'svelte-jsoneditor';
|
||||
import { modeCurrent } from '@skeletonlabs/skeleton';
|
||||
import axios from 'axios';
|
||||
|
||||
export let data;
|
||||
console.log(data);
|
||||
|
||||
type TextContent = {
|
||||
text: string;
|
||||
};
|
||||
|
||||
type JSONContent = {
|
||||
json: unknown;
|
||||
};
|
||||
|
||||
type Content = JSONContent | TextContent;
|
||||
|
||||
let requestBody: Content = {
|
||||
text: '',
|
||||
json: undefined
|
||||
};
|
||||
|
||||
let responseBody: Content = {
|
||||
text: '',
|
||||
json: undefined
|
||||
};
|
||||
|
||||
function mapPath(path: string[]) {
|
||||
const [name, value] = path;
|
||||
return { name, value };
|
||||
}
|
||||
|
||||
async function makeAPICall() {
|
||||
const response = await axios({
|
||||
method: selectedAPIMethod,
|
||||
url: `${data.session.baseUrl}/${APICallPath}`,
|
||||
data: requestBody.json,
|
||||
headers: {
|
||||
authorization: `Bearer ${data.idnSession.access_token}`
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
return err;
|
||||
});
|
||||
responseBody = { json: response.data };
|
||||
console.log(responseBody);
|
||||
}
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
console.log(event);
|
||||
|
||||
if (event.isTrusted === true && event.key === 'Enter') {
|
||||
makeAPICall();
|
||||
}
|
||||
}
|
||||
|
||||
let APIVersions = [
|
||||
// @ts-expect-error - This is a valid API Version,
|
||||
{ name: 'Beta', value: Object.entries(data.BetaSpec).map((path) => mapPath(path)) },
|
||||
// @ts-expect-error - This is a valid API Version,
|
||||
{ name: 'V3', value: Object.entries(data.V3Spec).map((path) => mapPath(path)) },
|
||||
{
|
||||
name: 'Custom',
|
||||
value: [
|
||||
{
|
||||
name: 'Custom Path',
|
||||
value: { GET: '', POST: '', PUT: '', PATCH: '', DELETE: '', HEAD: '' }
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
$: editorClasses = $modeCurrent === false ? 'jse-theme-dark' : '';
|
||||
|
||||
let selectedAPIVersion = APIVersions[0];
|
||||
let selectedPath = selectedAPIVersion.value[0];
|
||||
|
||||
let APICallPath: string = `${selectedAPIVersion.name.toLowerCase()}${selectedPath.name}`;
|
||||
|
||||
let selectedAPIMethod = 'GET';
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-row">
|
||||
<select
|
||||
placeholder="Select an API Version"
|
||||
class="w-[100px] !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="w-[100px] select rounded-r-none">
|
||||
{#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-sm">
|
||||
Call
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<p class="text-center pt-4">Request</p>
|
||||
<div class="{editorClasses} rounded-lg overflow-hidden pt-2">
|
||||
<JSONEditor bind:content={requestBody} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="text-center pt-4">Response</p>
|
||||
<div class="{editorClasses} rounded-lg overflow-hidden pt-2">
|
||||
<JSONEditor bind:content={responseBody} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* load one or multiple themes */
|
||||
|
||||
@import 'svelte-jsoneditor/themes/jse-theme-dark.css';
|
||||
</style>
|
||||
62
Sveltekit-App/src/routes/home/identities/+page.server.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { getFilters, getLimit, getPage, getSorters } from '$lib/Utils.js';
|
||||
import { createConfiguration } from '$lib/sailpoint/sdk.js';
|
||||
import { getSession, getToken } from '$lib/utils/oauth.js';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import {
|
||||
IdentitiesBetaApi,
|
||||
type IdentitiesBetaApiListIdentitiesRequest,
|
||||
type IdentityBeta
|
||||
} from 'sailpoint-api-client';
|
||||
|
||||
export const load = async ({ cookies, url }) => {
|
||||
const session = await getSession(cookies);
|
||||
const idnSession = await getToken(cookies);
|
||||
|
||||
const config = createConfiguration(session.baseUrl, idnSession.access_token);
|
||||
const api = new IdentitiesBetaApi(config);
|
||||
|
||||
const page = getPage(url);
|
||||
const filters = getFilters(url);
|
||||
const limit = getLimit(url);
|
||||
const sorters = getSorters(url);
|
||||
|
||||
const requestParams: IdentitiesBetaApiListIdentitiesRequest = {
|
||||
filters,
|
||||
offset: Number(page) * Number(limit),
|
||||
limit: Number(limit),
|
||||
sorters,
|
||||
count: true
|
||||
};
|
||||
|
||||
const apiResponse = api.listIdentities(requestParams);
|
||||
|
||||
const totalCount = new Promise<number>((resolve) => {
|
||||
apiResponse.then((response) => {
|
||||
resolve(response.headers['x-total-count']);
|
||||
});
|
||||
});
|
||||
|
||||
const identities = new Promise<IdentityBeta[]>((resolve) => {
|
||||
apiResponse
|
||||
.then((response) => {
|
||||
resolve(response.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
error(500, {
|
||||
message:
|
||||
'an error occurred while fetching identities. Please examine your filters and and sorters and try again.',
|
||||
context: { params: { page, limit, filters, sorters } },
|
||||
urls: [
|
||||
'https://developer.sailpoint.com/idn/api/standard-collection-parameters#filtering-results'
|
||||
],
|
||||
errData: err.response.data
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
totalCount,
|
||||
identities,
|
||||
params: { page, limit, filters, sorters }
|
||||
};
|
||||
};
|
||||
125
Sveltekit-App/src/routes/home/identities/+page.svelte
Normal file
@@ -0,0 +1,125 @@
|
||||
<script lang="ts">
|
||||
import Paginator from '$lib/Components/Paginator.svelte';
|
||||
import Progress from '$lib/Components/Progress.svelte';
|
||||
import {
|
||||
TriggerCodeModal,
|
||||
createOnAmountChange,
|
||||
createOnGo,
|
||||
createOnPageChange,
|
||||
formatDate
|
||||
} from '$lib/Utils.js';
|
||||
import { getModalStore } from '@skeletonlabs/skeleton';
|
||||
|
||||
const modalStore = getModalStore();
|
||||
|
||||
export let data;
|
||||
|
||||
$: onPageChange = createOnPageChange({ ...data.params, filters, sorters }, '/home/identities');
|
||||
$: onAmountChange = createOnAmountChange(
|
||||
{ ...data.params, filters, sorters },
|
||||
'/home/identities'
|
||||
);
|
||||
$: onGo = createOnGo({ ...data.params, filters, sorters }, '/home/identities');
|
||||
|
||||
let filters = '';
|
||||
let sorters = '';
|
||||
</script>
|
||||
|
||||
<div class="card flex justify-center flex-col align-middle">
|
||||
{#await data.totalCount then totalCount}
|
||||
{#if totalCount > 250 || Number(data.params.limit) < totalCount}
|
||||
<Paginator
|
||||
{onAmountChange}
|
||||
{onGo}
|
||||
{onPageChange}
|
||||
settings={{
|
||||
page: Number(data.params.page),
|
||||
limit: Number(data.params.limit),
|
||||
size: totalCount,
|
||||
amounts: [10, 50, 100, 250]
|
||||
}}
|
||||
{filters}
|
||||
{sorters}
|
||||
{totalCount}
|
||||
/>
|
||||
{/if}
|
||||
{/await}
|
||||
{#await data.identities}
|
||||
<div class="grid h-full place-content-center p-8">
|
||||
<Progress width="w-[100px]" />
|
||||
</div>
|
||||
{:then identities}
|
||||
<div class="table-container">
|
||||
<table class="table">
|
||||
<thead class="table-head">
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Lifecycle State</th>
|
||||
<th>eMail</th>
|
||||
<th>Created</th>
|
||||
<th>Modified</th>
|
||||
<th />
|
||||
</thead>
|
||||
<tbody class="table-body">
|
||||
{#each identities as identity}
|
||||
<tr>
|
||||
<td>
|
||||
<p class="text-center">{identity.id}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p class="text-center">{identity.name}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p class="text-center">{identity.lifecycleState?.stateName}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p class="text-center">{identity.emailAddress}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p class="text-center">{formatDate(identity.created)}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p class="text-center">{formatDate(identity.modified)}</p>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex flex-col justify-center gap-1">
|
||||
<a
|
||||
href={`/home/identities/${identity.id}`}
|
||||
class="btn btn-sm variant-filled-primary text-sm !text-white"
|
||||
data-sveltekit-preload-data="hover"
|
||||
>
|
||||
Open
|
||||
</a>
|
||||
<button
|
||||
on:click={() => TriggerCodeModal(identity, modalStore)}
|
||||
class="btn btn-sm variant-filled-primary text-sm !text-white"
|
||||
>
|
||||
View
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/await}
|
||||
{#await data.totalCount then totalCount}
|
||||
{#if totalCount > 250 || Number(data.params.limit) < totalCount}
|
||||
<Paginator
|
||||
{onAmountChange}
|
||||
{onGo}
|
||||
{onPageChange}
|
||||
settings={{
|
||||
page: Number(data.params.page),
|
||||
limit: Number(data.params.limit),
|
||||
size: totalCount,
|
||||
amounts: [10, 50, 100, 250]
|
||||
}}
|
||||
{filters}
|
||||
{sorters}
|
||||
{totalCount}
|
||||
/>
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
@@ -0,0 +1,56 @@
|
||||
import { createConfiguration } from '$lib/sailpoint/sdk';
|
||||
import { getSession, getToken } from '$lib/utils/oauth';
|
||||
import {
|
||||
IdentitiesBetaApi,
|
||||
SearchApi,
|
||||
type EventDocument,
|
||||
type IdentityBeta,
|
||||
type Search
|
||||
} from 'sailpoint-api-client';
|
||||
|
||||
export const load = async ({ cookies, params }) => {
|
||||
const session = await getSession(cookies);
|
||||
const idnSession = await getToken(cookies);
|
||||
|
||||
const config = createConfiguration(session.baseUrl, idnSession.access_token);
|
||||
const identityApi = new IdentitiesBetaApi(config);
|
||||
const searchApi = new SearchApi(config);
|
||||
|
||||
const identityResp = identityApi.getIdentity({ id: params.identityID });
|
||||
|
||||
const identityData = new Promise<IdentityBeta>((resolve) => {
|
||||
identityResp
|
||||
.then((response) => {
|
||||
resolve(response.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
|
||||
const identityEvents = new Promise<EventDocument[]>((resolve) => {
|
||||
identityResp.then((response) => {
|
||||
const identity = response.data;
|
||||
const search: Search = {
|
||||
indices: ['events'],
|
||||
query: {
|
||||
query: `target.name: "${identity.name}"`
|
||||
},
|
||||
sort: ['created']
|
||||
};
|
||||
|
||||
searchApi
|
||||
.searchPost({
|
||||
search
|
||||
})
|
||||
.then((response) => {
|
||||
resolve(response.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return { identityData, identityEvents };
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
<script lang="ts">
|
||||
import Progress from '$lib/Components/Progress.svelte';
|
||||
import { TriggerCodeModal } from '$lib/Utils';
|
||||
import { CodeBlock, Tab, TabGroup, getModalStore } from '@skeletonlabs/skeleton';
|
||||
|
||||
export let data;
|
||||
|
||||
console.log(data);
|
||||
|
||||
let tabSet: number = 0;
|
||||
|
||||
const modalStore = getModalStore();
|
||||
</script>
|
||||
|
||||
<div class=" flex flex-col gap-2">
|
||||
{#await data.identityData}
|
||||
<div class="card grid h-full place-content-center p-8">
|
||||
<Progress width="w-[100px]" />
|
||||
</div>
|
||||
{:then identityData}
|
||||
<div class="card p-4">
|
||||
<h1 class="text-2xl font-bold">Name: {identityData.name}</h1>
|
||||
<p class="">Alias: {identityData.alias}</p>
|
||||
<p class="">ID: {identityData.id}</p>
|
||||
<p class="">Lifecycle State: {identityData.lifecycleState?.stateName}</p>
|
||||
</div>
|
||||
<div class="card p-4">
|
||||
<h2 class="pb-2">Identity JSON</h2>
|
||||
<CodeBlock lineNumbers language="json" code={JSON.stringify(identityData, null, 4)} />
|
||||
</div>
|
||||
<div class="card p-4">
|
||||
<TabGroup>
|
||||
<Tab bind:group={tabSet} name="raw-source-values" value={0}>Identity Events</Tab>
|
||||
|
||||
<svelte:fragment slot="panel">
|
||||
{#if tabSet === 0}
|
||||
{#await data.identityEvents}
|
||||
<div class="grid h-full place-content-center p-8">
|
||||
<Progress width="w-[100px]" />
|
||||
</div>
|
||||
{:then identityEvents}
|
||||
{#if identityEvents.length > 0}
|
||||
<div class="table-container">
|
||||
<table class="table">
|
||||
<thead class="table-head">
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Created</th>
|
||||
<th>Target</th>
|
||||
<th>Actor</th>
|
||||
<th />
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each identityEvents as event}
|
||||
<tr>
|
||||
<td>{event.name}</td>
|
||||
<td>{event.status}</td>
|
||||
<td>{event.created}</td>
|
||||
<td>{event.target?.name}</td>
|
||||
<td>{event.actor?.name}</td>
|
||||
<td class="flex flex-col justify-center gap-1">
|
||||
<button
|
||||
class="btn variant-filled-primary text-sm !text-white"
|
||||
on:click={() => TriggerCodeModal(event, modalStore)}
|
||||
>
|
||||
View
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="text-center">No Identity Events</p>
|
||||
{/if}
|
||||
{/await}
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TabGroup>
|
||||
</div>
|
||||
{/await}
|
||||
</div>
|
||||
29
Sveltekit-App/src/routes/home/reports/+page.svelte
Normal file
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { reports } from '$lib/reports';
|
||||
</script>
|
||||
|
||||
<div class="grid gap-2 grid-flow-row xl:grid-cols-4 lg:grid-cols-3 md:grid-cols-2 justify-center">
|
||||
{#each reports as report (report.url)}
|
||||
<a
|
||||
class="card card-hover overflow-hidden"
|
||||
data-sveltekit-preload-data="hover"
|
||||
href={report.url}
|
||||
>
|
||||
<header
|
||||
class="card-header aspect-[21/9] bg-[#526bf8] flex flex-col justify-center min-h-[105px]"
|
||||
>
|
||||
<p class="font-bold text-white uppercase text-center text-xl">
|
||||
{report.name}
|
||||
</p>
|
||||
</header>
|
||||
<div class="p-4 space-y-4">
|
||||
<h3 class="h3" data-toc-ignore>Summary</h3>
|
||||
<article>
|
||||
<p>
|
||||
{report.description}
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -0,0 +1,28 @@
|
||||
import { createConfiguration } from '$lib/sailpoint/sdk.js';
|
||||
import { getToken } from '$lib/utils/oauth.js';
|
||||
import { SearchApi, type Search, Paginator, type IdentityDocument } from 'sailpoint-api-client';
|
||||
|
||||
export const load = async ({ cookies }) => {
|
||||
const search: Search = {
|
||||
indices: ['identities'],
|
||||
query: {
|
||||
query: `@accounts(disabled:false) AND (attributes.cloudLifecycleState:inactive)`
|
||||
},
|
||||
sort: ['name']
|
||||
};
|
||||
|
||||
const session = JSON.parse(cookies.get('session')!);
|
||||
const idnSession = await getToken(cookies);
|
||||
|
||||
const config = createConfiguration(session.baseUrl, idnSession.access_token);
|
||||
const api = new SearchApi(config);
|
||||
const reportResp = Paginator.paginateSearchApi(api, search, 100, 20000);
|
||||
|
||||
const reportData = new Promise<IdentityDocument[]>((resolve) => {
|
||||
reportResp.then((response) => {
|
||||
resolve(response.data);
|
||||
});
|
||||
});
|
||||
|
||||
return { reportData };
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
<script lang="ts">
|
||||
import Progress from '$lib/Components/Progress.svelte';
|
||||
import { TriggerCodeModal, formatDate } from '$lib/Utils.js';
|
||||
import { getModalStore } from '@skeletonlabs/skeleton';
|
||||
|
||||
export let data;
|
||||
|
||||
const modalStore = getModalStore();
|
||||
</script>
|
||||
|
||||
<div class="flex justify-center flex-col align-middle gap-2">
|
||||
<div class="card p-4">
|
||||
<p class="text-2xl text-center">
|
||||
List of all identities that are inactive but still have access in sources
|
||||
</p>
|
||||
</div>
|
||||
{#await data.reportData}
|
||||
<div class="grid h-full place-content-center p-8">
|
||||
<Progress width="w-[100px]" />
|
||||
</div>
|
||||
{:then reportData}
|
||||
{#if reportData.length === 0}
|
||||
<div class="card p-4">
|
||||
<p class=" text-center text-success-500">No inactive identities with access found</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="table-container">
|
||||
<table class="table">
|
||||
<thead class="table-head">
|
||||
<th> Name </th>
|
||||
<th> Sources </th>
|
||||
<th> Created </th>
|
||||
<th> Modified </th>
|
||||
<th> Access Count </th>
|
||||
<th> Entitlement Count </th>
|
||||
<th> Role Count </th>
|
||||
<th></th>
|
||||
</thead>
|
||||
<tbody class="table-body">
|
||||
{#each reportData as identity}
|
||||
<tr>
|
||||
<td>
|
||||
{identity.displayName}
|
||||
</td>
|
||||
<td>
|
||||
{identity.accounts?.map((account) => account.source?.name).join(', ')}
|
||||
</td>
|
||||
<td>
|
||||
{formatDate(identity.created)}
|
||||
</td>
|
||||
<td>
|
||||
{formatDate(identity.modified)}
|
||||
</td>
|
||||
<td>
|
||||
{identity.accessCount}
|
||||
</td>
|
||||
<td>
|
||||
{identity.entitlementCount}
|
||||
</td>
|
||||
<td>
|
||||
{identity.roleCount}
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex flex-col justify-center gap-1">
|
||||
<a
|
||||
href={`/home/identities/${identity.id}`}
|
||||
class="btn btn-sm variant-filled-primary text-sm !text-white"
|
||||
data-sveltekit-preload-data="hover"
|
||||
>
|
||||
Open
|
||||
</a>
|
||||
<button
|
||||
on:click={() => TriggerCodeModal(identity, modalStore)}
|
||||
class="btn btn-sm variant-filled-primary text-sm !text-white"
|
||||
>
|
||||
View
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
@@ -0,0 +1,28 @@
|
||||
import { createConfiguration } from '$lib/sailpoint/sdk.js';
|
||||
import { getToken } from '$lib/utils/oauth.js';
|
||||
import { Paginator, SearchApi, type IdentityDocument, type Search } from 'sailpoint-api-client';
|
||||
|
||||
export const load = async ({ cookies }) => {
|
||||
const search: Search = {
|
||||
indices: ['identities'],
|
||||
query: {
|
||||
query: `NOT _exists_:attributes.cloudLifecycleState`
|
||||
},
|
||||
sort: ['name']
|
||||
};
|
||||
|
||||
const session = JSON.parse(cookies.get('session')!);
|
||||
const idnSession = await getToken(cookies);
|
||||
|
||||
const config = createConfiguration(session.baseUrl, idnSession.access_token);
|
||||
const api = new SearchApi(config);
|
||||
const searchResp = Paginator.paginateSearchApi(api, search, 100, 20000);
|
||||
|
||||
const reportData = new Promise<IdentityDocument[]>((resolve) => {
|
||||
searchResp.then((response) => {
|
||||
resolve(response.data);
|
||||
});
|
||||
});
|
||||
|
||||
return { reportData };
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
<script lang="ts">
|
||||
import Progress from '$lib/Components/Progress.svelte';
|
||||
import { TriggerCodeModal, formatDate } from '$lib/Utils';
|
||||
import { getModalStore } from '@skeletonlabs/skeleton';
|
||||
|
||||
export let data;
|
||||
console.log(data);
|
||||
|
||||
const modalStore = getModalStore();
|
||||
</script>
|
||||
|
||||
<div class="flex justify-center flex-col align-middle">
|
||||
<div class="card p-4">
|
||||
<p class="text-2xl text-center">
|
||||
Listing of identities that are missing the cloud life cycle state attribute
|
||||
</p>
|
||||
</div>
|
||||
{#await data.reportData}
|
||||
<div class="grid h-full place-content-center p-8">
|
||||
<Progress width="w-[100px]" />
|
||||
</div>
|
||||
{:then reportData}
|
||||
<div class="table-container">
|
||||
<table class="table">
|
||||
<thead class="table-head">
|
||||
<th> Name </th>
|
||||
<th> Sources </th>
|
||||
<th> Created </th>
|
||||
<th> Access Count </th>
|
||||
<th> Entitlement Count </th>
|
||||
<th> Role Count </th>
|
||||
<th></th>
|
||||
</thead>
|
||||
<tbody class="table-body">
|
||||
{#each reportData as identity}
|
||||
<tr>
|
||||
<td>
|
||||
{identity.displayName}
|
||||
</td>
|
||||
<td>
|
||||
{identity.accounts?.map((account) => account.source?.name).join(', ')}
|
||||
</td>
|
||||
<td>
|
||||
{formatDate(identity.created)}
|
||||
</td>
|
||||
<td>
|
||||
{identity.accessCount}
|
||||
</td>
|
||||
<td>
|
||||
{identity.entitlementCount}
|
||||
</td>
|
||||
<td>
|
||||
{identity.roleCount}
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex flex-col justify-center gap-1">
|
||||
<a
|
||||
href={`/home/identities/${identity.id}`}
|
||||
class="btn btn-sm variant-filled-primary text-sm !text-white"
|
||||
data-sveltekit-preload-data="hover"
|
||||
>
|
||||
Open
|
||||
</a>
|
||||
<button
|
||||
on:click={() => TriggerCodeModal(identity, modalStore)}
|
||||
class="btn btn-sm variant-filled-primary text-sm !text-white"
|
||||
>
|
||||
View
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/await}
|
||||
</div>
|
||||
@@ -0,0 +1,28 @@
|
||||
import { createConfiguration } from '$lib/sailpoint/sdk';
|
||||
import { getSession, getToken } from '$lib/utils/oauth';
|
||||
import { Paginator, SearchApi, type Search, type EventDocument } from 'sailpoint-api-client';
|
||||
|
||||
export const load = async ({ cookies }) => {
|
||||
const session = await getSession(cookies);
|
||||
const idnSession = await getToken(cookies);
|
||||
|
||||
const config = createConfiguration(session.baseUrl, idnSession.access_token);
|
||||
const api = new SearchApi(config);
|
||||
const search: Search = {
|
||||
indices: ['events'],
|
||||
query: {
|
||||
query: `name: "Create Account Failed" AND created: [now-90d TO now]`
|
||||
},
|
||||
sort: ['created']
|
||||
};
|
||||
|
||||
const searchResp = Paginator.paginateSearchApi(api, search, 100, 20000);
|
||||
|
||||
const errorEvents = new Promise<EventDocument[]>((resolve) => {
|
||||
searchResp.then((response) => {
|
||||
resolve(response.data);
|
||||
});
|
||||
});
|
||||
|
||||
return { errorEvents };
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
<script lang="ts">
|
||||
import Progress from '$lib/Components/Progress.svelte';
|
||||
import { TriggerCodeModal } from '$lib/Utils';
|
||||
import type { PopupSettings } from '@skeletonlabs/skeleton';
|
||||
import { getModalStore } from '@skeletonlabs/skeleton';
|
||||
import alasql from 'alasql';
|
||||
|
||||
const modalStore = getModalStore();
|
||||
|
||||
export let data;
|
||||
|
||||
let report: any;
|
||||
|
||||
let reportPromise = new Promise<
|
||||
{ failure: string; source: string; name: string; exception: string; failures: number }[]
|
||||
>((resolve, reject) => {
|
||||
data.errorEvents.then((data) => {
|
||||
let reportResult = [];
|
||||
|
||||
for (let row of data) {
|
||||
console.log(row);
|
||||
|
||||
reportResult.push({
|
||||
name: row.target?.name,
|
||||
source: row.attributes?.sourceName,
|
||||
failure: row.name,
|
||||
exception: row.attributes?.errors
|
||||
});
|
||||
}
|
||||
|
||||
console.log(reportResult);
|
||||
|
||||
let res = alasql(
|
||||
'SELECT failure, source, name, exception, count(*) as failures FROM ? GROUP BY failure, source, name, exception',
|
||||
[reportResult]
|
||||
);
|
||||
|
||||
console.log(res);
|
||||
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
|
||||
const popupHover: PopupSettings = {
|
||||
event: 'hover',
|
||||
target: 'popupHover',
|
||||
placement: 'top'
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class=" flex justify-center flex-col align-middle gap-2">
|
||||
<div class="card p-4">
|
||||
<p class="text-2xl text-center">Listing of Source Account Create Errors</p>
|
||||
</div>
|
||||
{#await reportPromise}
|
||||
<div class="grid h-full place-content-center p-8">
|
||||
<Progress width="w-[100px]" />
|
||||
</div>
|
||||
{:then report}
|
||||
{#if report.length === 0}
|
||||
<div class="card p-4">
|
||||
<p class="text-md text-center text-success-500">
|
||||
No Source Account Create Errors for the last 90 Days
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="table-container">
|
||||
<table class="table table-interactive">
|
||||
<thead class="table-head">
|
||||
<th>Source</th>
|
||||
<th>Failure</th>
|
||||
<th>Name</th>
|
||||
<th>Count</th>
|
||||
<th>Exception</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each report as row}
|
||||
<tr on:click={() => TriggerCodeModal(row, modalStore)}>
|
||||
<td>{row.source}</td>
|
||||
<td>{row.failure}</td>
|
||||
<td>{row.name}</td>
|
||||
<td>{row.failures}</td>
|
||||
<td class="max-w-36">
|
||||
<p class="truncate">{row.exception}</p>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
@@ -0,0 +1,150 @@
|
||||
import { getFilters, getLimit, getPage, getSorters } from '$lib/Utils.js';
|
||||
import { createConfiguration } from '$lib/sailpoint/sdk.js';
|
||||
import { getToken } from '$lib/utils/oauth.js';
|
||||
import {
|
||||
SearchApi,
|
||||
SourcesApi,
|
||||
type EventDocument,
|
||||
type Search,
|
||||
type SourcesApiListSourcesRequest,
|
||||
type Source
|
||||
} from 'sailpoint-api-client';
|
||||
|
||||
export const load = async ({ cookies, url }) => {
|
||||
const session = JSON.parse(cookies.get('session')!);
|
||||
const idnSession = await getToken(cookies);
|
||||
|
||||
const config = createConfiguration(session.baseUrl, idnSession.access_token);
|
||||
const sourceApi = new SourcesApi(config);
|
||||
const searchApi = new SearchApi(config);
|
||||
|
||||
const page = getPage(url);
|
||||
const filters = getFilters(url);
|
||||
const limit = getLimit(url);
|
||||
const sorters = getSorters(url);
|
||||
|
||||
const requestParams: SourcesApiListSourcesRequest = {
|
||||
filters,
|
||||
offset: Number(page) * Number(limit),
|
||||
limit: Number(limit),
|
||||
sorters,
|
||||
count: true
|
||||
};
|
||||
|
||||
const apiResponse = sourceApi.listSources(requestParams);
|
||||
|
||||
const sources = new Promise<Source[]>((resolve) => {
|
||||
apiResponse
|
||||
.then((response) => {
|
||||
resolve(response.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
|
||||
const totalCount = new Promise<number>((resolve) => {
|
||||
apiResponse
|
||||
.then((response) => {
|
||||
resolve(response.headers['x-total-count']);
|
||||
})
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
|
||||
type SourceEvents = {
|
||||
accounts: { started: EventDocument | undefined; passed: EventDocument | undefined };
|
||||
entitlements: { started: EventDocument | undefined; passed: EventDocument | undefined };
|
||||
};
|
||||
|
||||
const eventNames: string[] = [
|
||||
'Aggregate Source Account Passed',
|
||||
'Aggregate Source Account Started',
|
||||
'Aggregate Source Entitlement Passed',
|
||||
'Aggregate Source Entitlement Started'
|
||||
];
|
||||
|
||||
const eventsMap = new Promise<Map<string, SourceEvents>>((resolve) => {
|
||||
sources.then(async (sources) => {
|
||||
const sourceEventsMap = new Map<string, SourceEvents>();
|
||||
|
||||
for (const source of sources) {
|
||||
const allEvents: EventDocument[] = [];
|
||||
const promises: Promise<EventDocument[]>[] = [];
|
||||
|
||||
for (const event of eventNames) {
|
||||
const search: Search = {
|
||||
indices: ['events'],
|
||||
query: {
|
||||
query: `target.name: "${source.name}" AND name:"${event}"`
|
||||
},
|
||||
sort: ['created']
|
||||
};
|
||||
|
||||
promises.push(
|
||||
searchApi
|
||||
.searchPost({
|
||||
search
|
||||
})
|
||||
.then((response) => {
|
||||
return response.data;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.allSettled(promises).then((results) => {
|
||||
for (const event of results) {
|
||||
if (event.status == 'fulfilled' && event.value.length > 0) {
|
||||
allEvents.push(event.value[0]);
|
||||
}
|
||||
}
|
||||
|
||||
const sourceEvents: SourceEvents = {
|
||||
accounts: { started: undefined, passed: undefined },
|
||||
entitlements: { started: undefined, passed: undefined }
|
||||
};
|
||||
|
||||
for (const event of allEvents) {
|
||||
if (event.attributes!.sourceName === source.name) {
|
||||
switch (event.technicalName) {
|
||||
case 'SOURCE_ACCOUNT_AGGREGATE_STARTED':
|
||||
if (!sourceEvents.accounts.started) {
|
||||
sourceEvents.accounts.started = event || undefined;
|
||||
}
|
||||
break;
|
||||
case 'SOURCE_ACCOUNT_AGGREGATE_PASSED':
|
||||
if (!sourceEvents.accounts.passed) {
|
||||
sourceEvents.accounts.passed = event || undefined;
|
||||
}
|
||||
break;
|
||||
case 'SOURCE_ENTITLEMENT_AGGREGATE_STARTED':
|
||||
if (!sourceEvents.entitlements.started) {
|
||||
sourceEvents.entitlements.started = event || undefined;
|
||||
}
|
||||
break;
|
||||
case 'SOURCE_ENTITLEMENT_AGGREGATE_PASSED':
|
||||
if (!sourceEvents.entitlements.passed) {
|
||||
sourceEvents.entitlements.passed = event || undefined;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceEventsMap.set(source.name, sourceEvents);
|
||||
});
|
||||
}
|
||||
|
||||
resolve(sourceEventsMap);
|
||||
});
|
||||
});
|
||||
|
||||
return { sources, eventsMap, totalCount, params: { page, limit, filters, sorters } };
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
<script lang="ts">
|
||||
import Progress from '$lib/Components/Progress.svelte';
|
||||
import { TriggerCodeModal, formatDate } from '$lib/Utils.js';
|
||||
import { getModalStore } from '@skeletonlabs/skeleton';
|
||||
|
||||
const modalStore = getModalStore();
|
||||
|
||||
export let data;
|
||||
console.log(data);
|
||||
</script>
|
||||
|
||||
<div class="flex justify-center flex-col align-middle gap-2">
|
||||
<div class="card p-4">
|
||||
<p class="text-2xl text-center">List of sources and their most recent aggregation events</p>
|
||||
</div>
|
||||
|
||||
{#await data.sources}
|
||||
<div class="card grid h-full place-content-center p-8">
|
||||
<Progress width="w-[100px]" />
|
||||
</div>
|
||||
{:then sources}
|
||||
<div class="table-container">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th>Source Name</th>
|
||||
<th>Type</th>
|
||||
<th>Authoritative</th>
|
||||
<th>Account Aggregations</th>
|
||||
<th>Entitlement Aggregations</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each sources as source}
|
||||
<tr>
|
||||
<td>{source.name}</td>
|
||||
<td>{source.type}</td>
|
||||
<td
|
||||
class="font-bold"
|
||||
class:text-tertiary-500={source.authoritative}
|
||||
class:text-warning-500={!source.authoritative}
|
||||
>
|
||||
{source.authoritative ? 'True' : 'False'}
|
||||
</td>
|
||||
{#await data.eventsMap}
|
||||
<td>
|
||||
<div class="grid place-content-center">
|
||||
<Progress width="w-[80px]" />
|
||||
</div>
|
||||
</td>
|
||||
{:then eventsMap}
|
||||
<td>
|
||||
<div class="flex flex-col gap-2">
|
||||
<button
|
||||
disabled={!eventsMap.get(source.name)?.accounts.started}
|
||||
class="btn btn-sm variant-filled-primary text-sm !text-white"
|
||||
on:click={() =>
|
||||
eventsMap.get(source.name)?.accounts.started &&
|
||||
TriggerCodeModal(eventsMap.get(source.name)?.accounts.started, modalStore)}
|
||||
>
|
||||
Started: {formatDate(eventsMap.get(source.name)?.accounts.started?.created)}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm variant-filled"
|
||||
disabled={!eventsMap.get(source.name)?.accounts.passed}
|
||||
on:click={() =>
|
||||
eventsMap.get(source.name)?.accounts.passed &&
|
||||
TriggerCodeModal(eventsMap.get(source.name)?.accounts.passed, modalStore)}
|
||||
>
|
||||
Passed: {formatDate(eventsMap.get(source.name)?.accounts.passed?.created)}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
{/await}
|
||||
{#await data.eventsMap}
|
||||
<td>
|
||||
<div class="grid place-content-center">
|
||||
<Progress width="w-[80px]" />
|
||||
</div>
|
||||
</td>
|
||||
{:then eventsMap}
|
||||
<td>
|
||||
<div class="flex flex-col gap-2">
|
||||
<button
|
||||
class="btn btn-sm variant-filled-primary text-sm !text-white"
|
||||
disabled={!eventsMap.get(source.name)?.entitlements.started}
|
||||
on:click={() =>
|
||||
eventsMap.get(source.name)?.entitlements.started &&
|
||||
TriggerCodeModal(
|
||||
eventsMap.get(source.name)?.entitlements.started,
|
||||
modalStore
|
||||
)}
|
||||
>
|
||||
Started: {formatDate(
|
||||
eventsMap.get(source.name)?.entitlements.started?.created
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm variant-filled"
|
||||
disabled={!eventsMap.get(source.name)?.entitlements.passed}
|
||||
on:click={() =>
|
||||
eventsMap.get(source.name)?.entitlements.passed &&
|
||||
TriggerCodeModal(
|
||||
eventsMap.get(source.name)?.entitlements.passed,
|
||||
modalStore
|
||||
)}
|
||||
>
|
||||
Passed: {formatDate(eventsMap.get(source.name)?.entitlements.passed?.created)}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
{/await}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/await}
|
||||
</div>
|
||||
@@ -0,0 +1,49 @@
|
||||
import { getFilters, getLimit, getSorters, getPage } from '$lib/Utils.js';
|
||||
import { createConfiguration } from '$lib/sailpoint/sdk.js';
|
||||
import { getToken } from '$lib/utils/oauth.js';
|
||||
import { SourcesApi, type Source, type SourcesApiListSourcesRequest } from 'sailpoint-api-client';
|
||||
|
||||
export const load = async ({ cookies, url }) => {
|
||||
const session = JSON.parse(cookies.get('session')!);
|
||||
const idnSession = await getToken(cookies);
|
||||
|
||||
const config = createConfiguration(session.baseUrl, idnSession.access_token);
|
||||
const api = new SourcesApi(config);
|
||||
|
||||
const page = getPage(url);
|
||||
const filters = getFilters(url);
|
||||
const limit = getLimit(url);
|
||||
const sorters = getSorters(url);
|
||||
|
||||
const requestParams: SourcesApiListSourcesRequest = {
|
||||
filters,
|
||||
offset: Number(page) * Number(limit),
|
||||
limit: Number(limit),
|
||||
sorters,
|
||||
count: true
|
||||
};
|
||||
|
||||
const apiResponse = api.listSources(requestParams);
|
||||
|
||||
const sources = new Promise<Source[]>((resolve) => {
|
||||
apiResponse
|
||||
.then((response) => {
|
||||
resolve(response.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
|
||||
const totalCount = new Promise<number>((resolve) => {
|
||||
apiResponse
|
||||
.then((response) => {
|
||||
resolve(response.headers['x-total-count']);
|
||||
})
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
|
||||
return { sources, totalCount, params: { page, limit, filters, sorters } };
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
<script lang="ts">
|
||||
import Progress from '$lib/Components/Progress.svelte';
|
||||
import { TriggerCodeModal, formatDate } from '$lib/Utils';
|
||||
import { getModalStore } from '@skeletonlabs/skeleton';
|
||||
|
||||
export let data;
|
||||
console.log(data);
|
||||
|
||||
const modalStore = getModalStore();
|
||||
</script>
|
||||
|
||||
<div class="flex justify-center flex-col align-middle gap-2">
|
||||
<div class="card p-4">
|
||||
<p class="text-2xl text-center">Listing of sources and their configured owners</p>
|
||||
</div>
|
||||
{#await data.sources}
|
||||
<div class="grid h-full place-content-center p-8">
|
||||
<Progress width="w-[100px]" />
|
||||
</div>
|
||||
{:then sources}
|
||||
<div class="table-container">
|
||||
<table class="table">
|
||||
<thead class="table-head">
|
||||
<th> Source Name </th>
|
||||
<th> Type </th>
|
||||
<th> Modified </th>
|
||||
<th> Created </th>
|
||||
<th> Owner </th>
|
||||
<th />
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each sources as source}
|
||||
<tr>
|
||||
<td>{source.name}</td>
|
||||
<td>{source.type}</td>
|
||||
<td>{formatDate(source.modified)}</td>
|
||||
<td>{formatDate(source.created)}</td>
|
||||
<td>{source.owner.name}</td>
|
||||
<td class="flex flex-col justify-center gap-1">
|
||||
<a
|
||||
href={`/home/sources/${source.id}`}
|
||||
class="btn variant-filled-primary text-sm !text-white"
|
||||
data-sveltekit-preload-data="hover"
|
||||
>
|
||||
Open Source
|
||||
</a>
|
||||
<button
|
||||
on:click={() => TriggerCodeModal(source, modalStore)}
|
||||
class="btn variant-filled-primary text-sm !text-white"
|
||||
>
|
||||
View
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/await}
|
||||
</div>
|
||||
49
Sveltekit-App/src/routes/home/sources/+page.server.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { getFilters, getLimit, getSorters, getPage } from '$lib/Utils.js';
|
||||
import { createConfiguration } from '$lib/sailpoint/sdk.js';
|
||||
import { getToken } from '$lib/utils/oauth.js';
|
||||
import { SourcesApi, type Source, type SourcesApiListSourcesRequest } from 'sailpoint-api-client';
|
||||
|
||||
export const load = async ({ cookies, url }) => {
|
||||
const session = JSON.parse(cookies.get('session')!);
|
||||
const idnSession = await getToken(cookies);
|
||||
|
||||
const config = createConfiguration(session.baseUrl, idnSession.access_token);
|
||||
const api = new SourcesApi(config);
|
||||
|
||||
const page = getPage(url);
|
||||
const filters = getFilters(url);
|
||||
const limit = getLimit(url);
|
||||
const sorters = getSorters(url);
|
||||
|
||||
const requestParams: SourcesApiListSourcesRequest = {
|
||||
filters,
|
||||
offset: Number(page) * Number(limit),
|
||||
limit: Number(limit),
|
||||
sorters,
|
||||
count: true
|
||||
};
|
||||
|
||||
const apiResponse = api.listSources(requestParams);
|
||||
|
||||
const sources = new Promise<Source[]>((resolve) => {
|
||||
apiResponse
|
||||
.then((response) => {
|
||||
resolve(response.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
|
||||
const totalCount = new Promise<number>((resolve) => {
|
||||
apiResponse
|
||||
.then((response) => {
|
||||
resolve(response.headers['x-total-count']);
|
||||
})
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
|
||||
return { sources, totalCount, params: { page, limit, filters, sorters } };
|
||||
};
|
||||
135
Sveltekit-App/src/routes/home/sources/+page.svelte
Normal file
@@ -0,0 +1,135 @@
|
||||
<script lang="ts">
|
||||
import Paginator from '$lib/Components/Paginator.svelte';
|
||||
import Progress from '$lib/Components/Progress.svelte';
|
||||
import {
|
||||
TriggerCodeModal,
|
||||
createOnAmountChange,
|
||||
createOnGo,
|
||||
createOnPageChange
|
||||
} from '$lib/Utils.js';
|
||||
import type { ModalSettings, PaginationSettings } from '@skeletonlabs/skeleton';
|
||||
import { getModalStore } from '@skeletonlabs/skeleton';
|
||||
|
||||
const modalStore = getModalStore();
|
||||
|
||||
export let data;
|
||||
|
||||
$: onPageChange = createOnPageChange({ ...data.params, filters, sorters }, '/home/identities');
|
||||
$: onAmountChange = createOnAmountChange(
|
||||
{ ...data.params, filters, sorters },
|
||||
'/home/identities'
|
||||
);
|
||||
$: onGo = createOnGo({ ...data.params, filters, sorters }, '/home/identities');
|
||||
|
||||
let filters = '';
|
||||
let sorters = '';
|
||||
</script>
|
||||
|
||||
<div class="card flex flex-col justify-center h-full">
|
||||
{#await data.totalCount then totalCount}
|
||||
{#if totalCount > 250 || Number(data.params.limit) < totalCount}
|
||||
<Paginator
|
||||
{onAmountChange}
|
||||
{onGo}
|
||||
{onPageChange}
|
||||
settings={{
|
||||
page: Number(data.params.page),
|
||||
limit: Number(data.params.limit),
|
||||
size: totalCount,
|
||||
amounts: [10, 50, 100, 250]
|
||||
}}
|
||||
{filters}
|
||||
{sorters}
|
||||
{totalCount}
|
||||
/>
|
||||
{/if}
|
||||
{/await}
|
||||
{#await data.sources}
|
||||
<div class="grid h-full place-content-center p-8">
|
||||
<Progress width="w-[100px]" />
|
||||
</div>
|
||||
{:then sources}
|
||||
<div class="table-container">
|
||||
<table class="table">
|
||||
<thead class="table-head">
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Type</th>
|
||||
<th>Authoritative</th>
|
||||
<th>Healthy</th>
|
||||
<th>Delete Threshold</th>
|
||||
<th>Owner</th>
|
||||
<th />
|
||||
</thead>
|
||||
<tbody class="table-body">
|
||||
{#each sources as source}
|
||||
<tr>
|
||||
<td>
|
||||
<p class="text-center">{source.id}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p class="text-center">{source.name}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p class="text-center">{source.description}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p class="text-center">{source.type}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p class="text-center">{source.authoritative ? 'True' : 'False'}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p
|
||||
class="text-center font-bold {source.healthy ? 'text-green-500' : 'text-red-500'}"
|
||||
>
|
||||
{source.healthy ? 'True' : 'False'}
|
||||
</p>
|
||||
</td>
|
||||
<td>
|
||||
<p class="text-center">{source.deleteThreshold}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p class="text-center">{source.owner.name}</p>
|
||||
</td>
|
||||
<td class="flex flex-col justify-center gap-1">
|
||||
<a
|
||||
href={`/home/sources/${source.id}`}
|
||||
class="btn btn-sm variant-filled-primary text-sm !text-white"
|
||||
data-sveltekit-preload-data="hover"
|
||||
>
|
||||
Open
|
||||
</a>
|
||||
<button
|
||||
on:click={() => TriggerCodeModal(source, modalStore)}
|
||||
class="btn btn-sm variant-filled-primary text-sm !text-white"
|
||||
>
|
||||
View
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/await}
|
||||
{#await data.totalCount then totalCount}
|
||||
{#if totalCount > 250 || Number(data.params.limit) < totalCount}
|
||||
<Paginator
|
||||
{onAmountChange}
|
||||
{onGo}
|
||||
{onPageChange}
|
||||
settings={{
|
||||
page: Number(data.params.page),
|
||||
limit: Number(data.params.limit),
|
||||
size: totalCount,
|
||||
amounts: [10, 50, 100, 250]
|
||||
}}
|
||||
{filters}
|
||||
{sorters}
|
||||
{totalCount}
|
||||
/>
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
104
Sveltekit-App/src/routes/home/sources/[sourceID]/+page.server.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { createConfiguration } from '$lib/sailpoint/sdk.js';
|
||||
import { getToken } from '$lib/utils/oauth.js';
|
||||
import { SearchApi, SourcesApi, type EventDocument, type Search } from 'sailpoint-api-client';
|
||||
|
||||
export const load = async ({ cookies, params }) => {
|
||||
const session = JSON.parse(cookies.get('session')!);
|
||||
const idnSession = await getToken(cookies);
|
||||
|
||||
const config = createConfiguration(session.baseUrl, idnSession.access_token);
|
||||
const sourceApi = new SourcesApi(config);
|
||||
const searchApi = new SearchApi(config);
|
||||
|
||||
const sourceResp = await sourceApi.getSource({ id: params.sourceID });
|
||||
|
||||
const source = sourceResp.data;
|
||||
|
||||
type SourceEvents = {
|
||||
accounts: { started: EventDocument | undefined; passed: EventDocument | undefined };
|
||||
entitlements: { started: EventDocument | undefined; passed: EventDocument | undefined };
|
||||
};
|
||||
|
||||
const sourceEvents = new Promise<SourceEvents>((resolve) => {
|
||||
const eventNames: string[] = [
|
||||
'Aggregate Source Account Passed',
|
||||
'Aggregate Source Account Started',
|
||||
'Aggregate Source Entitlement Passed',
|
||||
'Aggregate Source Entitlement Started'
|
||||
];
|
||||
|
||||
const allEvents: EventDocument[] = [];
|
||||
|
||||
const promises: Promise<EventDocument[]>[] = [];
|
||||
|
||||
for (const event of eventNames) {
|
||||
const search: Search = {
|
||||
indices: ['events'],
|
||||
query: {
|
||||
query: `target.name: "${source.name}" AND name:"${event}"`
|
||||
},
|
||||
sort: ['created']
|
||||
};
|
||||
|
||||
promises.push(
|
||||
searchApi
|
||||
.searchPost({
|
||||
search
|
||||
})
|
||||
.then((response) => {
|
||||
return response.data;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
Promise.allSettled(promises).then((results) => {
|
||||
for (const event of results) {
|
||||
if (event.status == 'fulfilled' && event.value.length > 0) {
|
||||
allEvents.push(event.value[0]);
|
||||
}
|
||||
}
|
||||
|
||||
const sourceEvents: SourceEvents = {
|
||||
accounts: { started: undefined, passed: undefined },
|
||||
entitlements: { started: undefined, passed: undefined }
|
||||
};
|
||||
|
||||
for (const event of allEvents) {
|
||||
if (event.attributes!.sourceName === source.name) {
|
||||
switch (event.technicalName) {
|
||||
case 'SOURCE_ACCOUNT_AGGREGATE_STARTED':
|
||||
if (!sourceEvents.accounts.started) {
|
||||
sourceEvents.accounts.started = event || undefined;
|
||||
}
|
||||
break;
|
||||
case 'SOURCE_ACCOUNT_AGGREGATE_PASSED':
|
||||
if (!sourceEvents.accounts.passed) {
|
||||
sourceEvents.accounts.passed = event || undefined;
|
||||
}
|
||||
break;
|
||||
case 'SOURCE_ENTITLEMENT_AGGREGATE_STARTED':
|
||||
if (!sourceEvents.entitlements.started) {
|
||||
sourceEvents.entitlements.started = event || undefined;
|
||||
}
|
||||
break;
|
||||
case 'SOURCE_ENTITLEMENT_AGGREGATE_PASSED':
|
||||
if (!sourceEvents.entitlements.passed) {
|
||||
sourceEvents.entitlements.passed = event || undefined;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolve(sourceEvents);
|
||||
});
|
||||
});
|
||||
|
||||
return { source, sourceEvents };
|
||||
};
|
||||
121
Sveltekit-App/src/routes/home/sources/[sourceID]/+page.svelte
Normal file
@@ -0,0 +1,121 @@
|
||||
<script lang="ts">
|
||||
import VaCluster from '$lib/Components/VACluster.svelte';
|
||||
import { formatDate } from '$lib/Utils.js';
|
||||
import { Accordion, AccordionItem, CodeBlock, Tab, TabGroup } from '@skeletonlabs/skeleton';
|
||||
import Progress from '$lib/Components/Progress.svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
console.log(data);
|
||||
|
||||
let tabSet: number = 1;
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="card p-4">
|
||||
<h1 class="text-2xl font-bold">{data.source.name}</h1>
|
||||
<p class="">{data.source.description}</p>
|
||||
<p class="">ID: {data.source.id}</p>
|
||||
<p class="">Type: {data.source.type}</p>
|
||||
<p class="">
|
||||
Authoritative: {data.source.authoritative ? 'True' : 'False'}
|
||||
</p>
|
||||
<p>
|
||||
Healthy:
|
||||
<span class={data.source.healthy ? 'text-green-500' : 'text-red-500'}>
|
||||
{data.source.healthy ? 'True' : 'False'}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="card p-4">
|
||||
<VaCluster cluster={data.source.cluster} />
|
||||
</div>
|
||||
{#await data.sourceEvents}
|
||||
<div class="card grid h-full place-content-center p-8">
|
||||
<Progress width="w-[100px]" />
|
||||
</div>
|
||||
{:then sourceEvents}
|
||||
<div class="card p-4">
|
||||
<h2>Most Recent Aggregations</h2>
|
||||
<div>
|
||||
<strong>Accounts:</strong>
|
||||
<Accordion>
|
||||
<AccordionItem>
|
||||
<svelte:fragment slot="summary">
|
||||
Started: {formatDate(sourceEvents.accounts.started?.created)}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
<CodeBlock
|
||||
lineNumbers
|
||||
language="json"
|
||||
code={JSON.stringify(sourceEvents.accounts.started, null, 4)}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<svelte:fragment slot="summary">
|
||||
Passed: {formatDate(sourceEvents.accounts.passed?.created)}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
<CodeBlock
|
||||
lineNumbers
|
||||
language="json"
|
||||
code={JSON.stringify(sourceEvents.accounts.passed, null, 4)}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<strong>Entitlements</strong>
|
||||
<Accordion>
|
||||
<AccordionItem>
|
||||
<svelte:fragment slot="summary">
|
||||
Started: {formatDate(sourceEvents.entitlements.started?.created)}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
<div>
|
||||
<CodeBlock
|
||||
lineNumbers
|
||||
language="json"
|
||||
code={JSON.stringify(sourceEvents.entitlements.started, null, 4)}
|
||||
/>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<svelte:fragment slot="summary">
|
||||
Passed: {formatDate(sourceEvents.entitlements.passed?.created)}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
<CodeBlock
|
||||
lineNumbers
|
||||
language="json"
|
||||
code={JSON.stringify(sourceEvents.entitlements.passed, null, 4)}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
</div>
|
||||
{/await}
|
||||
<div class="card p-4">
|
||||
<TabGroup>
|
||||
<!-- <Tab bind:group={tabSet} name="raw-source-values" value={0}>Source Events</Tab> -->
|
||||
<Tab bind:group={tabSet} name="tab2" value={1}>Connector Attributes JSON</Tab>
|
||||
<Tab bind:group={tabSet} name="raw-source-values" value={2}>Full Source JSON</Tab>
|
||||
<!-- Tab Panels --->
|
||||
<svelte:fragment slot="panel">
|
||||
{#if tabSet === 0}
|
||||
<!-- SOURCE EVENTS -->
|
||||
{:else if tabSet === 1}
|
||||
<CodeBlock
|
||||
lineNumbers
|
||||
language="json"
|
||||
code={JSON.stringify(data.source.connectorAttributes, null, 4)}
|
||||
/>
|
||||
{:else if tabSet === 2}
|
||||
<CodeBlock lineNumbers language="json" code={JSON.stringify(data.source, null, 4)} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TabGroup>
|
||||
</div>
|
||||
</div>
|
||||
15
Sveltekit-App/src/routes/logout/+page.server.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export const load = async ({ cookies }) => {
|
||||
cookies.delete('session', {
|
||||
path: '/',
|
||||
httpOnly: false,
|
||||
secure: false
|
||||
});
|
||||
|
||||
cookies.delete('idnSession', {
|
||||
path: '/',
|
||||
httpOnly: false,
|
||||
secure: false
|
||||
});
|
||||
|
||||
return { sessionLoggedOut: true };
|
||||
};
|
||||
32
Sveltekit-App/src/routes/logout/+page.svelte
Normal file
@@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { goto, invalidateAll } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let data;
|
||||
onMount(() => {
|
||||
setTimeout(async () => {
|
||||
console.log('Redirecting to login...');
|
||||
goto('/');
|
||||
}, 2000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="p-4">
|
||||
<div class="card p-4">
|
||||
{#if data.sessionLoggedOut}
|
||||
<p class="text-center p-2">Successfully Logged out</p>
|
||||
{:else}
|
||||
<p class="text-center p-2">
|
||||
WHOOPS! an error occurred. <br /> If you believe this is a bug please submit an issue on
|
||||
<a
|
||||
class="underline text-blue-500 hover:text-blue-700"
|
||||
href="https://github.com/sailpoint-oss/idn-admin-console/issues/new/choose"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
BIN
Sveltekit-App/static/logo.ico
Normal file
|
After Width: | Height: | Size: 259 KiB |
21
Sveltekit-App/svelte.config.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import adapter from '@sveltejs/adapter-node';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
extensions: ['.svelte'],
|
||||
// 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(),
|
||||
csrf: {
|
||||
checkOrigin: false
|
||||
}
|
||||
}
|
||||
};
|
||||
export default config;
|
||||
@@ -1,26 +1,28 @@
|
||||
import { join } from 'path';
|
||||
import type { Config } from 'tailwindcss';
|
||||
|
||||
// 1. Import the Skeleton plugin
|
||||
import forms from '@tailwindcss/forms';
|
||||
import typography from '@tailwindcss/typography';
|
||||
import { skeleton } from '@skeletonlabs/tw-plugin';
|
||||
|
||||
const config = {
|
||||
// 2. Opt for dark mode to be handled via the class method
|
||||
export default {
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
'./src/**/*.{html,js,svelte,ts}',
|
||||
// 3. Append the path to the Skeleton package
|
||||
join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}'),
|
||||
join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}')
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {}
|
||||
},
|
||||
plugins: [
|
||||
// 4. Append the Skeleton plugin (after other plugins)
|
||||
forms,
|
||||
typography,
|
||||
skeleton({
|
||||
themes: { preset: ['wintry'] },
|
||||
}),
|
||||
],
|
||||
themes: {
|
||||
preset: [
|
||||
{ name: 'wintry', enhancements: true },
|
||||
{ name: 'skeleton', enhancements: true }
|
||||
]
|
||||
}
|
||||
})
|
||||
]
|
||||
} satisfies Config;
|
||||
|
||||
export default config;
|
||||
18
Sveltekit-App/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// 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
|
||||
}
|
||||
22
Sveltekit-App/vite.config.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { purgeCss } from 'vite-plugin-tailwind-purgecss';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
sveltekit(),
|
||||
purgeCss({
|
||||
safelist: {
|
||||
// any selectors that begin with "hljs-" will not be purged
|
||||
greedy: [/^hljs-/]
|
||||
}
|
||||
})
|
||||
],
|
||||
test: {
|
||||
include: ['src/**/*.{test,spec}.{js,ts}']
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
origin: 'http://localhost:3000'
|
||||
}
|
||||
});
|
||||
|
Before Width: | Height: | Size: 432 KiB |
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"appId": "com.example.app",
|
||||
"productName": "Sveltekit Electron",
|
||||
"directories": {
|
||||
"output": "dist"
|
||||
},
|
||||
"files": [
|
||||
"src/electron.cjs",
|
||||
"src/preload.cjs",
|
||||
{
|
||||
"from": "build",
|
||||
"to": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
50
forge.config.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { ForgeConfig } from "@electron-forge/shared-types";
|
||||
import { MakerSquirrel } from "@electron-forge/maker-squirrel";
|
||||
import { MakerZIP } from "@electron-forge/maker-zip";
|
||||
import { MakerDeb } from "@electron-forge/maker-deb";
|
||||
import { MakerRpm } from "@electron-forge/maker-rpm";
|
||||
import { MakerDMG } from "@electron-forge/maker-dmg";
|
||||
import { VitePlugin } from "@electron-forge/plugin-vite";
|
||||
import { PublisherGithub } from "@electron-forge/publisher-github";
|
||||
|
||||
const config: ForgeConfig = {
|
||||
packagerConfig: {
|
||||
asar: true,
|
||||
extraResource: ["./Sveltekit-App/build"],
|
||||
},
|
||||
rebuildConfig: {},
|
||||
makers: [
|
||||
new MakerSquirrel({}),
|
||||
new MakerZIP({}),
|
||||
new MakerRpm({}),
|
||||
new MakerDeb({}),
|
||||
new MakerDMG({}),
|
||||
],
|
||||
plugins: [
|
||||
new VitePlugin({
|
||||
// `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.
|
||||
// If you are familiar with Vite configuration, it will look really familiar.
|
||||
build: [
|
||||
{
|
||||
// `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.
|
||||
entry: "src/main.ts",
|
||||
config: "vite.main.config.ts",
|
||||
},
|
||||
],
|
||||
renderer: [],
|
||||
}),
|
||||
],
|
||||
publishers: [
|
||||
new PublisherGithub({
|
||||
repository: {
|
||||
owner: "sailpoint-oss",
|
||||
name: "idn-admin-console",
|
||||
},
|
||||
prerelease: true,
|
||||
draft: true,
|
||||
force: true,
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
2
globals.d.ts
vendored
@@ -1,2 +0,0 @@
|
||||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"$lib": ["src/lib"],
|
||||
"$lib/*": ["src/lib/*"],
|
||||
"/~/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "build", "dist"]
|
||||
}
|
||||
27536
package-lock.json
generated
128
package.json
@@ -1,75 +1,57 @@
|
||||
{
|
||||
"name": "sveltekit-electron",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"description": "Minimal Sveltekit + Electron starter template.",
|
||||
"main": "src/electron.cjs",
|
||||
"type": "module",
|
||||
"author": "Braden Wiggins",
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=dev npm run dev:all",
|
||||
"dev:all": "concurrently -n=svelte,electron -c='#ff3e00',blue \"npm run dev:svelte\" \"npm run dev:electron\"",
|
||||
"dev:svelte": "vite dev",
|
||||
"dev:electron": "electron src/electron.cjs",
|
||||
"build": "cross-env NODE_ENV=production npm run build:svelte && npm run build:electron",
|
||||
"build:svelte": "vite build",
|
||||
"build:electron": "electron-builder -mwl --config build.config.json"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=7",
|
||||
"yarn": "use npm - https://github.com/FractalHQ/sveltekit-electron/issues/12#issuecomment-1068399385"
|
||||
},
|
||||
"browserslist": [
|
||||
"Chrome 89"
|
||||
],
|
||||
"dependencies": {
|
||||
"electron-context-menu": "^3.6.1",
|
||||
"electron-reloader": "^1.2.3",
|
||||
"electron-serve": "^1.1.0",
|
||||
"electron-window-state": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@skeletonlabs/skeleton": "^2.1.0",
|
||||
"@skeletonlabs/tw-plugin": "^0.2.0",
|
||||
"@sveltejs/adapter-static": "2.0.1",
|
||||
"@sveltejs/kit": "1.14.0",
|
||||
"@types/jsonwebtoken": "^9.0.3",
|
||||
"@types/node": "^20.6.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.56.0",
|
||||
"@typescript-eslint/parser": "^5.56.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"alasql": "^4.1.9",
|
||||
"axios": "^1.5.0",
|
||||
"concurrently": "^7.6.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^16.0.3",
|
||||
"electron": "^23.2.0",
|
||||
"electron-builder": "^23.6.0",
|
||||
"electron-connect": "^0.6.3",
|
||||
"electron-packager": "^17.1.1",
|
||||
"electron-updater": "^5.3.0",
|
||||
"eslint": "^8.36.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.4.24",
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"prettier": "^2.8.7",
|
||||
"prettier-plugin-svelte": "^2.10.0",
|
||||
"sailpoint-api-client": "^1.2.2",
|
||||
"sass": "^1.60.0",
|
||||
"svelte": "^3.57.0",
|
||||
"svelte-check": "^3.1.4",
|
||||
"svelte-preprocess": "^5.0.3",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "^4.0.4"
|
||||
},
|
||||
"overrides": {
|
||||
"electron": {
|
||||
"got": "^12.5.1"
|
||||
}
|
||||
}
|
||||
"name": "identitynow-electron-sveltekit-starter",
|
||||
"productName": "Electron SvelteKit Starter",
|
||||
"description": "An example application for building web or desktop apps with IdentityNow",
|
||||
"version": "0.0.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sailpoint-oss/idn-admin-console.git"
|
||||
},
|
||||
"main": ".vite/build/main.js",
|
||||
"scripts": {
|
||||
"build:web": "npm run build --workspace Sveltekit-App",
|
||||
"prebuild": "npm run build --workspace Sveltekit-App",
|
||||
"dev": "npm run dev --workspace Sveltekit-App",
|
||||
"start": "electron-forge start",
|
||||
"package": "electron-forge package",
|
||||
"make": "electron-forge make",
|
||||
"build": "electron-forge make",
|
||||
"publish": "electron-forge publish",
|
||||
"lint": "eslint --ext .ts,.tsx ."
|
||||
},
|
||||
|
||||
"keywords": [],
|
||||
"author": {
|
||||
"name": "Luke Hagar",
|
||||
"email": "luke.hagar@sailpoint.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "^7.2.0",
|
||||
"@electron-forge/maker-deb": "^7.2.0",
|
||||
"@electron-forge/maker-dmg": "^7.2.0",
|
||||
"@electron-forge/maker-rpm": "^7.2.0",
|
||||
"@electron-forge/maker-squirrel": "^7.2.0",
|
||||
"@electron-forge/maker-zip": "^7.2.0",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "^7.2.0",
|
||||
"@electron-forge/plugin-vite": "^7.2.0",
|
||||
"@electron-forge/publisher-github": "^7.2.0",
|
||||
"@electron-toolkit/utils": "^3.0.0",
|
||||
"@types/express": "^4.17.21",
|
||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
||||
"@typescript-eslint/parser": "^6.20.0",
|
||||
"electron": "28.2.1",
|
||||
"electron-log": "^5.1.1",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"eslint": "^8.0.1",
|
||||
"eslint-plugin-import": "^2.25.0",
|
||||
"express": "^4.18.2",
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "~5.3.3",
|
||||
"update-electron-app": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-squirrel-startup": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
const tailwindcss = require('tailwindcss');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
|
||||
const config = {
|
||||
plugins: [
|
||||
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
|
||||
tailwindcss(),
|
||||
//But others, like autoprefixer, need to run after,
|
||||
autoprefixer,
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
BIN
screenshot.png
|
Before Width: | Height: | Size: 52 KiB |
104
src/electron.cjs
@@ -1,104 +0,0 @@
|
||||
const windowStateManager = require('electron-window-state');
|
||||
const { app, BrowserWindow, ipcMain } = require('electron');
|
||||
const contextMenu = require('electron-context-menu');
|
||||
const serve = require('electron-serve');
|
||||
const path = require('path');
|
||||
|
||||
try {
|
||||
require('electron-reloader')(module);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
const serveURL = serve({ directory: '.' });
|
||||
const port = process.env.PORT || 3000;
|
||||
const dev = !app.isPackaged;
|
||||
let mainWindow;
|
||||
|
||||
function createWindow() {
|
||||
let windowState = windowStateManager({
|
||||
defaultWidth: 800,
|
||||
defaultHeight: 600,
|
||||
});
|
||||
|
||||
const mainWindow = new BrowserWindow({
|
||||
backgroundColor: 'whitesmoke',
|
||||
titleBarStyle: 'visible',
|
||||
autoHideMenuBar: false,
|
||||
trafficLightPosition: {
|
||||
x: 17,
|
||||
y: 32,
|
||||
},
|
||||
minHeight: 450,
|
||||
minWidth: 500,
|
||||
webPreferences: {
|
||||
enableRemoteModule: true,
|
||||
contextIsolation: true,
|
||||
nodeIntegration: true,
|
||||
spellcheck: false,
|
||||
devTools: dev,
|
||||
preload: path.join(__dirname, 'preload.cjs'),
|
||||
},
|
||||
x: windowState.x,
|
||||
y: windowState.y,
|
||||
width: windowState.width,
|
||||
height: windowState.height,
|
||||
});
|
||||
|
||||
windowState.manage(mainWindow);
|
||||
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
mainWindow.show();
|
||||
mainWindow.focus();
|
||||
});
|
||||
|
||||
mainWindow.on('close', () => {
|
||||
windowState.saveState(mainWindow);
|
||||
});
|
||||
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
contextMenu({
|
||||
showLookUpSelection: false,
|
||||
showSearchWithGoogle: false,
|
||||
showCopyImage: false,
|
||||
prepend: (defaultActions, params, browserWindow) => [
|
||||
{
|
||||
label: 'Make App 💻',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
function loadVite(port) {
|
||||
mainWindow.loadURL(`http://localhost:${port}`).catch((e) => {
|
||||
console.log('Error loading URL, retrying', e);
|
||||
setTimeout(() => {
|
||||
loadVite(port);
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
|
||||
function createMainWindow() {
|
||||
mainWindow = createWindow();
|
||||
mainWindow.once('close', () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
if (dev) loadVite(port);
|
||||
else serveURL(mainWindow);
|
||||
}
|
||||
|
||||
app.once('ready', createMainWindow);
|
||||
app.on('activate', () => {
|
||||
if (!mainWindow) {
|
||||
createMainWindow();
|
||||
}
|
||||
});
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') app.quit();
|
||||
});
|
||||
|
||||
ipcMain.on('to-main', (event, count) => {
|
||||
return mainWindow.webContents.send('from-main', `next counts is ${count + 1}`);
|
||||
});
|
||||
6
src/global.d.ts
vendored
@@ -1,6 +0,0 @@
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
||||
declare interface Window {
|
||||
electron: any;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { getStore } from '$lib/utils/hmr-stores';
|
||||
|
||||
export let id: string;
|
||||
export let agent: string;
|
||||
|
||||
const count = getStore(id, 0);
|
||||
|
||||
const handleClick = () => {
|
||||
$count += 1;
|
||||
};
|
||||
|
||||
$: if (window.electron) {
|
||||
window.electron.send('to-main', $count);
|
||||
}
|
||||
</script>
|
||||
|
||||
<button {id} on:click={handleClick}>
|
||||
Send Clicks to {agent}: {$count}
|
||||
</button>
|
||||
|
||||
<style>
|
||||
button {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
padding: 1em 2em;
|
||||
color: #ff3e00;
|
||||
background-color: rgba(255, 62, 0, 0.1);
|
||||
border-radius: 2em;
|
||||
border: 2px solid rgba(255, 62, 0, 0);
|
||||
outline: none;
|
||||
width: 200px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
button:focus {
|
||||
border: 2px solid #ff3e00;
|
||||
}
|
||||
button:active {
|
||||
background-color: rgba(255, 62, 0, 0.2);
|
||||
}
|
||||
</style>
|
||||
@@ -1,27 +0,0 @@
|
||||
<script>
|
||||
import { fly } from 'svelte/transition';
|
||||
import { onMount } from 'svelte';
|
||||
let visible = false;
|
||||
onMount(() => (visible = true));
|
||||
</script>
|
||||
|
||||
<center class="py-10">
|
||||
{#if visible}
|
||||
<img
|
||||
src="/sveltekit-electron.svg"
|
||||
alt="Svelte Logo"
|
||||
draggable="false"
|
||||
in:fly={{ y: 100, duration: 1500 }}
|
||||
/>
|
||||
{:else}
|
||||
<div style="height: 16rem" />
|
||||
{/if}
|
||||
</center>
|
||||
|
||||
<style>
|
||||
img {
|
||||
height: 16rem;
|
||||
width: 16rem;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||