Initial commit
This commit is contained in:
commit
9b9ec3472c
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
tags
|
||||||
|
.dev-import
|
27
README.md
Normal file
27
README.md
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# pub.solar Keycloak theme
|
||||||
|
|
||||||
|
|
||||||
|
## Development setup
|
||||||
|
|
||||||
|
To start a Dev Keycloak instance that can show the pub.solar theme, you need to do the following:
|
||||||
|
|
||||||
|
1. Go into Keycloak's [Administration Console](https://auth.pub.solar/admin), and export the `pub.solar` realm config. In Keycloak, open the pub.solar realm, click on the menu item "Realm settings", open the dropdown "Action", and click "Partial export". Move the generated JSON file into `./.dev-imports` in this repo.
|
||||||
|
|
||||||
|
2. Run the following command:
|
||||||
|
```
|
||||||
|
docker run \
|
||||||
|
--name keycloak-theme-dev \
|
||||||
|
-p 8080:8080 \
|
||||||
|
-e KEYCLOAK_ADMIN=admin \
|
||||||
|
-e KEYCLOAK_ADMIN_PASSWORD=admin \
|
||||||
|
-v $(pwd)/keycloak:/opt/keycloak/themes/pub.solar \
|
||||||
|
-v $(pwd)/.dev-import:/opt/keycloak/data/import \
|
||||||
|
quay.io/keycloak/keycloak:23.0.1 \
|
||||||
|
start-dev --import-realm --features="declarative-user-profile"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. After this, you can start and stop the container using `docker start keycloak-theme-dev` and `docker-stop keycloak-theme-dev`.
|
||||||
|
|
||||||
|
4. Connect to the local Keycloak instance at http://localhost:8080 and open the Administration Console. Login with username: `admin`, password: `admin`.
|
||||||
|
|
||||||
|
5. To view the theme in action, open http://localhost:8080/realms/pub.solar/account
|
98
flake.lock
Normal file
98
flake.lock
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"devshell": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1688380630,
|
||||||
|
"narHash": "sha256-8ilApWVb1mAi4439zS3iFeIT0ODlbrifm/fegWwgHjA=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "devshell",
|
||||||
|
"rev": "f9238ec3d75cefbb2b42a44948c4e8fb1ae9a205",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "devshell",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_2"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1689068808,
|
||||||
|
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1689282004,
|
||||||
|
"narHash": "sha256-VNhuyb10c9SV+3hZOlxwJwzEGytZ31gN9w4nPCnNvdI=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "e74e68449c385db82de3170288a28cd0f608544f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"devshell": "devshell",
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
51
flake.nix
Normal file
51
flake.nix
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
description = "Keycloak pub.solar theme";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
|
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
|
||||||
|
devshell.url = "github:numtide/devshell";
|
||||||
|
devshell.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, flake-utils, devshell, nixpkgs }:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
overlays = [
|
||||||
|
devshell.overlay
|
||||||
|
(import ./overlay.nix)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
flake-utils.lib.simpleFlake {
|
||||||
|
inherit self nixpkgs;
|
||||||
|
name = "keycloak-theme-pub-solar";
|
||||||
|
overlay = ./overlay.nix;
|
||||||
|
preOverlays = [ devshell.overlays.default ];
|
||||||
|
shell = { pkgs }:
|
||||||
|
let
|
||||||
|
google-font-downloader = with pkgs; writeShellScriptBin "google-font-downloader" ''
|
||||||
|
echo "Attempting to fetch $1"
|
||||||
|
${nodejs}/bin/npx google-font-downloader -- $1
|
||||||
|
rm ./common/resources/scss/typography.scss
|
||||||
|
echo "/* To regenerate this file, run $ google-font-downloader '$1' */" >> ./common/resources/scss/typography.scss
|
||||||
|
echo "" >> ./common/resources/scss/typography.scss
|
||||||
|
cat ./google-fonts-*.css >> ./common/resources/scss/typography.scss
|
||||||
|
rm ./google-fonts-*.css
|
||||||
|
mv fonts ./common/resources/fonts
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
pkgs.devshell.mkShell {
|
||||||
|
imports = [ ];
|
||||||
|
|
||||||
|
# Add additional packages you'd like to be available in your devshell
|
||||||
|
# PATH here
|
||||||
|
devshell.packages = with pkgs; [
|
||||||
|
google-font-downloader
|
||||||
|
nodejs
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
25
frontend/.eslintrc.cjs
Normal file
25
frontend/.eslintrc.cjs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/* eslint-env node */
|
||||||
|
require('@rushstack/eslint-patch/modern-module-resolution')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
'extends': [
|
||||||
|
'plugin:vue/vue3-essential',
|
||||||
|
'eslint:recommended',
|
||||||
|
'@vue/eslint-config-typescript',
|
||||||
|
'@vue/eslint-config-prettier/skip-formatting'
|
||||||
|
],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
'cypress/e2e/**/*.{cy,spec}.{js,ts,jsx,tsx}'
|
||||||
|
],
|
||||||
|
'extends': [
|
||||||
|
'plugin:cypress/recommended'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest'
|
||||||
|
}
|
||||||
|
}
|
28
frontend/.gitignore
vendored
Normal file
28
frontend/.gitignore
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
8
frontend/.prettierrc.json
Normal file
8
frontend/.prettierrc.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"semi": false,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100,
|
||||||
|
"trailingComma": "none"
|
||||||
|
}
|
68
frontend/README.md
Normal file
68
frontend/README.md
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
# keycloak-theme-pub.solar
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||||
|
|
||||||
|
## Type Support for `.vue` Imports in TS
|
||||||
|
|
||||||
|
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
|
||||||
|
|
||||||
|
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
|
||||||
|
|
||||||
|
1. Disable the built-in TypeScript Extension
|
||||||
|
1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
|
||||||
|
2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
|
||||||
|
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type-Check, Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Unit Tests with [Vitest](https://vitest.dev/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run test:unit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run End-to-End Tests with [Cypress](https://www.cypress.io/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run test:e2e:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
This runs the end-to-end tests against the Vite development server.
|
||||||
|
It is much faster than the production build.
|
||||||
|
|
||||||
|
But it's still recommended to test the production build with `test:e2e` before deploying (e.g. in CI environments):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
npm run test:e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lint with [ESLint](https://eslint.org/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run lint
|
||||||
|
```
|
8
frontend/cypress.config.ts
Normal file
8
frontend/cypress.config.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { defineConfig } from 'cypress'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}',
|
||||||
|
baseUrl: 'http://localhost:4173'
|
||||||
|
}
|
||||||
|
})
|
8
frontend/cypress/e2e/example.cy.ts
Normal file
8
frontend/cypress/e2e/example.cy.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// https://on.cypress.io/api
|
||||||
|
|
||||||
|
describe('My First Test', () => {
|
||||||
|
it('visits the app root url', () => {
|
||||||
|
cy.visit('/')
|
||||||
|
cy.contains('h1', 'You did it!')
|
||||||
|
})
|
||||||
|
})
|
10
frontend/cypress/e2e/tsconfig.json
Normal file
10
frontend/cypress/e2e/tsconfig.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"include": ["./**/*", "../support/**/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"isolatedModules": false,
|
||||||
|
"target": "es5",
|
||||||
|
"lib": ["es5", "dom"],
|
||||||
|
"types": ["cypress"]
|
||||||
|
}
|
||||||
|
}
|
5
frontend/cypress/fixtures/example.json
Normal file
5
frontend/cypress/fixtures/example.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "Using fixtures to represent data",
|
||||||
|
"email": "hello@cypress.io",
|
||||||
|
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||||
|
}
|
39
frontend/cypress/support/commands.ts
Normal file
39
frontend/cypress/support/commands.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/// <reference types="cypress" />
|
||||||
|
// ***********************************************
|
||||||
|
// This example commands.ts shows you how to
|
||||||
|
// create various custom commands and overwrite
|
||||||
|
// existing commands.
|
||||||
|
//
|
||||||
|
// For more comprehensive examples of custom
|
||||||
|
// commands please read more here:
|
||||||
|
// https://on.cypress.io/custom-commands
|
||||||
|
// ***********************************************
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a parent command --
|
||||||
|
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a child command --
|
||||||
|
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a dual command --
|
||||||
|
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This will overwrite an existing command --
|
||||||
|
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||||
|
//
|
||||||
|
// declare global {
|
||||||
|
// namespace Cypress {
|
||||||
|
// interface Chainable {
|
||||||
|
// login(email: string, password: string): Chainable<void>
|
||||||
|
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||||
|
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||||
|
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
export {}
|
20
frontend/cypress/support/e2e.ts
Normal file
20
frontend/cypress/support/e2e.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// ***********************************************************
|
||||||
|
// This example support/index.js is processed and
|
||||||
|
// loaded automatically before your test files.
|
||||||
|
//
|
||||||
|
// This is a great place to put global configuration and
|
||||||
|
// behavior that modifies Cypress.
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off
|
||||||
|
// automatically serving support files with the
|
||||||
|
// 'supportFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/configuration
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// Import commands.js using ES2015 syntax:
|
||||||
|
import './commands'
|
||||||
|
|
||||||
|
// Alternatively you can use CommonJS syntax:
|
||||||
|
// require('./commands')
|
17
frontend/env.d.ts
vendored
Normal file
17
frontend/env.d.ts
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
interface GlobalKeycloakData {
|
||||||
|
developmentMode: boolean;
|
||||||
|
authUrl: string;
|
||||||
|
baseUrl: string;
|
||||||
|
realm: string;
|
||||||
|
resourceUrl: string;
|
||||||
|
isReactLoading: boolean;
|
||||||
|
|
||||||
|
brandImg: string;
|
||||||
|
brandUrl: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Window {
|
||||||
|
globals: InitialData;
|
||||||
|
}
|
13
frontend/index.html
Normal file
13
frontend/index.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
7883
frontend/package-lock.json
generated
Normal file
7883
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
48
frontend/package.json
Normal file
48
frontend/package.json
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
"name": "keycloak-theme-pub.solar",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"test:unit": "vitest",
|
||||||
|
"test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'",
|
||||||
|
"test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'",
|
||||||
|
"build-only": "vite build",
|
||||||
|
"type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
|
||||||
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||||
|
"format": "prettier --write src/"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"keycloak-js": "^23.0.1",
|
||||||
|
"pinia": "^2.1.7",
|
||||||
|
"vue": "^3.3.4",
|
||||||
|
"vue-router": "^4.2.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rushstack/eslint-patch": "^1.3.3",
|
||||||
|
"@tsconfig/node18": "^18.2.2",
|
||||||
|
"@types/jsdom": "^21.1.3",
|
||||||
|
"@types/node": "^18.18.5",
|
||||||
|
"@vitejs/plugin-vue": "^4.4.0",
|
||||||
|
"@vitejs/plugin-vue-jsx": "^3.0.2",
|
||||||
|
"@vue/eslint-config-prettier": "^8.0.0",
|
||||||
|
"@vue/eslint-config-typescript": "^12.0.0",
|
||||||
|
"@vue/test-utils": "^2.4.1",
|
||||||
|
"@vue/tsconfig": "^0.4.0",
|
||||||
|
"cypress": "^13.3.1",
|
||||||
|
"eslint": "^8.49.0",
|
||||||
|
"eslint-plugin-cypress": "^2.15.1",
|
||||||
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
|
"jsdom": "^22.1.0",
|
||||||
|
"npm-run-all2": "^6.1.1",
|
||||||
|
"prettier": "^3.0.3",
|
||||||
|
"sass": "^1.69.5",
|
||||||
|
"start-server-and-test": "^2.0.1",
|
||||||
|
"typescript": "~5.2.0",
|
||||||
|
"vite": "^4.4.11",
|
||||||
|
"vitest": "^0.34.6",
|
||||||
|
"vue-tsc": "^1.8.19"
|
||||||
|
}
|
||||||
|
}
|
BIN
frontend/public/favicon.ico
Normal file
BIN
frontend/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
1
frontend/public/logo.svg
Normal file
1
frontend/public/logo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 6.5 KiB |
12
frontend/src/App.tsx
Normal file
12
frontend/src/App.tsx
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { RouterLink, RouterView } from 'vue-router';
|
||||||
|
import Background from './components/Background';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
return () =>
|
||||||
|
<>
|
||||||
|
<RouterView />
|
||||||
|
<Background />
|
||||||
|
</>
|
||||||
|
},
|
||||||
|
};
|
86
frontend/src/assets/base.css
Normal file
86
frontend/src/assets/base.css
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/* color palette from <https://github.com/vuejs/theme> */
|
||||||
|
:root {
|
||||||
|
--vt-c-white: #ffffff;
|
||||||
|
--vt-c-white-soft: #f8f8f8;
|
||||||
|
--vt-c-white-mute: #f2f2f2;
|
||||||
|
|
||||||
|
--vt-c-black: #181818;
|
||||||
|
--vt-c-black-soft: #222222;
|
||||||
|
--vt-c-black-mute: #282828;
|
||||||
|
|
||||||
|
--vt-c-indigo: #2c3e50;
|
||||||
|
|
||||||
|
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||||
|
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||||
|
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||||
|
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||||
|
|
||||||
|
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||||
|
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||||
|
--vt-c-text-dark-1: var(--vt-c-white);
|
||||||
|
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* semantic color variables for this project */
|
||||||
|
:root {
|
||||||
|
--color-background: var(--vt-c-white);
|
||||||
|
--color-background-soft: var(--vt-c-white-soft);
|
||||||
|
--color-background-mute: var(--vt-c-white-mute);
|
||||||
|
|
||||||
|
--color-border: var(--vt-c-divider-light-2);
|
||||||
|
--color-border-hover: var(--vt-c-divider-light-1);
|
||||||
|
|
||||||
|
--color-heading: var(--vt-c-text-light-1);
|
||||||
|
--color-text: var(--vt-c-text-light-1);
|
||||||
|
|
||||||
|
--section-gap: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-background: var(--vt-c-black);
|
||||||
|
--color-background-soft: var(--vt-c-black-soft);
|
||||||
|
--color-background-mute: var(--vt-c-black-mute);
|
||||||
|
|
||||||
|
--color-border: var(--vt-c-divider-dark-2);
|
||||||
|
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||||
|
|
||||||
|
--color-heading: var(--vt-c-text-dark-1);
|
||||||
|
--color-text: var(--vt-c-text-dark-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
color: var(--color-text);
|
||||||
|
background: var(--color-background);
|
||||||
|
transition:
|
||||||
|
color 0.5s,
|
||||||
|
background-color 0.5s;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-family:
|
||||||
|
Inter,
|
||||||
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
'Segoe UI',
|
||||||
|
Roboto,
|
||||||
|
Oxygen,
|
||||||
|
Ubuntu,
|
||||||
|
Cantarell,
|
||||||
|
'Fira Sans',
|
||||||
|
'Droid Sans',
|
||||||
|
'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
font-size: 15px;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
35
frontend/src/assets/main.css
Normal file
35
frontend/src/assets/main.css
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
@import './base.css';
|
||||||
|
|
||||||
|
#app {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
.green {
|
||||||
|
text-decoration: none;
|
||||||
|
color: hsla(160, 100%, 37%, 1);
|
||||||
|
transition: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
a:hover {
|
||||||
|
background-color: hsla(160, 100%, 37%, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
padding: 0 2rem;
|
||||||
|
}
|
||||||
|
}
|
35
frontend/src/components/Background.scss
Normal file
35
frontend/src/components/Background.scss
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
.ps-background {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
speak: none;
|
||||||
|
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&--logo {
|
||||||
|
width: 100px;
|
||||||
|
margin: 0.1rem;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--1312 {
|
||||||
|
transform: rotate(34deg);
|
||||||
|
transform-origin: center center;
|
||||||
|
color: #eee;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
top: -50vh;
|
||||||
|
left: -50vw;
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 7rem;
|
||||||
|
line-height: 6rem;
|
||||||
|
width: 200vw;
|
||||||
|
height: 200vh;
|
||||||
|
}
|
||||||
|
}
|
45
frontend/src/components/Background.tsx
Normal file
45
frontend/src/components/Background.tsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
onBeforeUnmount,
|
||||||
|
onBeforeMount,
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
|
const x1312 = (new Array(400)).fill("0x1312").join(" ");
|
||||||
|
const getLogoNum = () => (Math.ceil(window.innerWidth / 100) * Math.ceil(window.innerHeight / 100));
|
||||||
|
|
||||||
|
import './Background.scss';
|
||||||
|
import './Logo.scss';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
const count = ref(getLogoNum());
|
||||||
|
const updateCount = () => setTimeout(() => {
|
||||||
|
count.value = getLogoNum();
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
console.log('before mount');
|
||||||
|
window.addEventListener('resize', updateCount);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
console.log('before unmount');
|
||||||
|
window.removeEventListener('resize', updateCount);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => <div class="ps-background">
|
||||||
|
<div
|
||||||
|
id="x1312"
|
||||||
|
class="ps-background--1312"
|
||||||
|
>{x1312}</div>
|
||||||
|
{(new Array(count.value)).fill(null).map(() => (
|
||||||
|
<div class="ps-background--logo ps-logo">
|
||||||
|
<img
|
||||||
|
class="ps-logo--base"
|
||||||
|
src="/public/logo.svg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
};
|
45
frontend/src/components/Logo.scss
Normal file
45
frontend/src/components/Logo.scss
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
.ps-logo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
width: 100px;
|
||||||
|
|
||||||
|
&--base {
|
||||||
|
animation-name: rotate;
|
||||||
|
animation-duration: 0.3s;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-play-state: paused;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
animation-play-state: running;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(2n) &--base { animation-delay: -0.1s; }
|
||||||
|
&:nth-child(3n) &--base { animation-delay: -0.3s; }
|
||||||
|
&:nth-child(5n) &--base { animation-delay: -0.5s; }
|
||||||
|
&:nth-child(7n) &--base { animation-delay: -7s; }
|
||||||
|
&:nth-child(11n) &--base { animation-delay: -0.9s; }
|
||||||
|
&:nth-child(13n) &--base { animation-delay: -1s; }
|
||||||
|
|
||||||
|
&--base::before {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
content: '';
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
11
frontend/src/components/__tests__/HelloWorld.spec.ts
Normal file
11
frontend/src/components/__tests__/HelloWorld.spec.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import HelloWorld from '../HelloWorld.vue'
|
||||||
|
|
||||||
|
describe('HelloWorld', () => {
|
||||||
|
it('renders properly', () => {
|
||||||
|
const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
|
||||||
|
expect(wrapper.text()).toContain('Hello Vitest')
|
||||||
|
})
|
||||||
|
})
|
17
frontend/src/keycloak.ts
Normal file
17
frontend/src/keycloak.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import Keycloak from 'keycloak-js';
|
||||||
|
|
||||||
|
const keycloak = new Keycloak({
|
||||||
|
url: `http://keycloak-server${kc_base_path}`,
|
||||||
|
realm,
|
||||||
|
clientId,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const authenticated = await keycloak.init({
|
||||||
|
onLoad: 'check-sso',
|
||||||
|
silentCheckSsoRedirectUri: `${location.origin}/silent-check-sso.html`
|
||||||
|
});
|
||||||
|
console.log(`User is ${authenticated ? 'authenticated' : 'not authenticated'}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize adapter:', error);
|
||||||
|
}
|
14
frontend/src/main.ts
Normal file
14
frontend/src/main.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import './assets/main.css'
|
||||||
|
|
||||||
|
import { createApp } from 'vue';
|
||||||
|
import { createPinia } from 'pinia';
|
||||||
|
|
||||||
|
import App from './App';
|
||||||
|
import router from './router';
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
|
||||||
|
app.use(createPinia());
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
|
app.mount('#app');
|
22
frontend/src/router/index.ts
Normal file
22
frontend/src/router/index.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
component: () => import('../views/Home.tsx'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/about',
|
||||||
|
name: 'about',
|
||||||
|
// route level code-splitting
|
||||||
|
// this generates a separate chunk (About.[hash].js) for this route
|
||||||
|
// which is lazy-loaded when the route is visited.
|
||||||
|
component: () => import('../views/AboutView.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
12
frontend/src/stores/counter.ts
Normal file
12
frontend/src/stores/counter.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useCounterStore = defineStore('counter', () => {
|
||||||
|
const count = ref(0)
|
||||||
|
const doubleCount = computed(() => count.value * 2)
|
||||||
|
function increment() {
|
||||||
|
count.value++
|
||||||
|
}
|
||||||
|
|
||||||
|
return { count, doubleCount, increment }
|
||||||
|
})
|
11
frontend/src/views/Home.tsx
Normal file
11
frontend/src/views/Home.tsx
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import TheWelcome from '../components/TheWelcome.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
return () => (
|
||||||
|
<main>
|
||||||
|
<TheWelcome />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
9
frontend/src/views/HomeView.vue
Normal file
9
frontend/src/views/HomeView.vue
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import TheWelcome from '../components/TheWelcome.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<main>
|
||||||
|
<TheWelcome />
|
||||||
|
</main>
|
||||||
|
</template>
|
12
frontend/tsconfig.app.json
Normal file
12
frontend/tsconfig.app.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
|
"exclude": ["src/**/__tests__/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
frontend/tsconfig.json
Normal file
14
frontend/tsconfig.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.vitest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
16
frontend/tsconfig.node.json
Normal file
16
frontend/tsconfig.node.json
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"extends": "@tsconfig/node18/tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"vite.config.*",
|
||||||
|
"vitest.config.*",
|
||||||
|
"cypress.config.*",
|
||||||
|
"nightwatch.conf.*",
|
||||||
|
"playwright.config.*"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"types": ["node"]
|
||||||
|
}
|
||||||
|
}
|
9
frontend/tsconfig.vitest.json
Normal file
9
frontend/tsconfig.vitest.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.app.json",
|
||||||
|
"exclude": [],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"lib": [],
|
||||||
|
"types": ["node", "jsdom"]
|
||||||
|
}
|
||||||
|
}
|
25
frontend/vite.config.ts
Normal file
25
frontend/vite.config.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { fileURLToPath, URL } from 'node:url';
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
vueJsx(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/realms': 'http://localhost:8080',
|
||||||
|
'/silent-check-sso.html': 'http://localhost:8080',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
14
frontend/vitest.config.ts
Normal file
14
frontend/vitest.config.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
|
||||||
|
import viteConfig from './vite.config'
|
||||||
|
|
||||||
|
export default mergeConfig(
|
||||||
|
viteConfig,
|
||||||
|
defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
exclude: [...configDefaults.exclude, 'e2e/*'],
|
||||||
|
root: fileURLToPath(new URL('./', import.meta.url))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
76
keycloak/account/index.ftl
Normal file
76
keycloak/account/index.ftl
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>${msg("accountManagementTitle")}</title>
|
||||||
|
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const globals = {
|
||||||
|
<#if properties.developmentMode?has_content && properties.developmentMode == "true">
|
||||||
|
developmentMode: true,
|
||||||
|
<#else>
|
||||||
|
developmentMode: false,
|
||||||
|
</#if>
|
||||||
|
authUrl: '${authUrl}',
|
||||||
|
baseUrl: '${baseUrl}',
|
||||||
|
realm: '${realm.name}',
|
||||||
|
resourceUrl: '${resourceUrl}',
|
||||||
|
isReactLoading: false,
|
||||||
|
|
||||||
|
<#if properties.logo?has_content>
|
||||||
|
brandImg: resourceUrl + '${properties.logo}',
|
||||||
|
<#else>
|
||||||
|
brandImg: resourceUrl + '/public/logo.svg',
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if properties.logoUrl?has_content>
|
||||||
|
brandUrl: '${properties.logoUrl}',
|
||||||
|
<#else>
|
||||||
|
brandUrl: baseUrl,
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if referrer??>
|
||||||
|
referrer: '${referrer}',
|
||||||
|
referrerName: '${referrerName}',
|
||||||
|
referrerUri: '${referrer_uri}'.replace('&', '&'),
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if msg??>
|
||||||
|
locale: '${locale}',
|
||||||
|
<#outputformat "JavaScript",
|
||||||
|
l18nMsg: JSON.parse('${msgJSON?js_string}'),
|
||||||
|
</#outputformat>
|
||||||
|
<#else>
|
||||||
|
locale: 'en',
|
||||||
|
l18Msg: {},
|
||||||
|
</#if>
|
||||||
|
};
|
||||||
|
|
||||||
|
const features = {
|
||||||
|
isRegistrationEmailAsUsername : ${realm.registrationEmailAsUsername?c},
|
||||||
|
isEditUserNameAllowed : ${realm.editUsernameAllowed?c},
|
||||||
|
isInternationalizationEnabled : ${realm.isInternationalizationEnabled()?c},
|
||||||
|
isLinkedAccountsEnabled : ${realm.identityFederationEnabled?c},
|
||||||
|
isMyResourcesEnabled : ${(realm.userManagedAccessAllowed && isAuthorizationEnabled)?c},
|
||||||
|
deleteAccountAllowed : ${deleteAccountAllowed?c},
|
||||||
|
updateEmailFeatureEnabled: ${updateEmailFeatureEnabled?c},
|
||||||
|
updateEmailActionEnabled: ${updateEmailActionEnabled?c},
|
||||||
|
isViewGroupsEnabled : ${isViewGroupsEnabled?c}
|
||||||
|
};
|
||||||
|
|
||||||
|
const availableLocales = [];
|
||||||
|
<#list supportedLocales as locale, label>
|
||||||
|
availableLocales.push({locale : '${locale}', label : '${label}'});
|
||||||
|
</#list>
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
19
keycloak/account/theme.properties
Normal file
19
keycloak/account/theme.properties
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
parent=base
|
||||||
|
deprecatedMode=false
|
||||||
|
|
||||||
|
scripts=
|
||||||
|
|
||||||
|
developmentMode=true
|
||||||
|
|
||||||
|
# This is the logo in upper lefthand corner.
|
||||||
|
# It must be a relative path.
|
||||||
|
logo=/public/logo.svg
|
||||||
|
|
||||||
|
# This is the link followed when clicking on the logo.
|
||||||
|
# It can be any valid URL, including an external site.
|
||||||
|
logoUrl=./
|
||||||
|
|
||||||
|
# This is the icon for the account console.
|
||||||
|
# It must be a relative path.
|
||||||
|
favIcon=/public/favicon.ico
|
||||||
|
|
19
keycloak/login/theme.properties
Normal file
19
keycloak/login/theme.properties
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
parent=base
|
||||||
|
deprecatedMode=false
|
||||||
|
|
||||||
|
scripts=
|
||||||
|
|
||||||
|
developmentMode=true
|
||||||
|
|
||||||
|
# This is the logo in upper lefthand corner.
|
||||||
|
# It must be a relative path.
|
||||||
|
logo=/public/logo.svg
|
||||||
|
|
||||||
|
# This is the link followed when clicking on the logo.
|
||||||
|
# It can be any valid URL, including an external site.
|
||||||
|
logoUrl=./
|
||||||
|
|
||||||
|
# This is the icon for the account console.
|
||||||
|
# It must be a relative path.
|
||||||
|
favIcon=/public/favicon.ico
|
||||||
|
|
10
overlay.nix
Normal file
10
overlay.nix
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
final: prev:
|
||||||
|
let
|
||||||
|
pkgs = final;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# this key should be the same as the simpleFlake name attribute.
|
||||||
|
keycloak-theme-pub-solar = {
|
||||||
|
keycloak-theme-pub-solar = import ./pkgs/keycloak-theme-pub-solar.nix { inherit pkgs; };
|
||||||
|
};
|
||||||
|
}
|
18
pkgs/keycloak-theme-pub-solar.nix
Normal file
18
pkgs/keycloak-theme-pub-solar.nix
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{ pkgs, ... }:
|
||||||
|
pkgs.stdenv.mkDerivation {
|
||||||
|
pname = "keycloak-theme-pub-solar";
|
||||||
|
version = "0.0.0";
|
||||||
|
src = pkgs.lib.cleanSource ../src;
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
${pkgs.sassc}/bin/sassc "./common/resources/scss/index.scss" > "./common/resources/css/index.css"
|
||||||
|
|
||||||
|
mkdir $out
|
||||||
|
cp -r account $out/
|
||||||
|
cp -r login $out/
|
||||||
|
cp -r admin $out/
|
||||||
|
cp -r common $out/
|
||||||
|
cp -r welcome $out/
|
||||||
|
cp -r email $out/
|
||||||
|
'';
|
||||||
|
}
|
Loading…
Reference in a new issue