├── .dockerignore ├── .github └── workflows │ ├── build_release.yml │ ├── create_release.yml │ ├── main.yaml │ └── pull_request.yaml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── Dockerfile ├── Jenkinsfile ├── LICENCE ├── LICENCE_INTERNAL_USE ├── README.md ├── core ├── Cargo.toml └── src │ ├── dispatcher │ ├── dispatcher.rs │ ├── mod.rs │ ├── usecase │ │ ├── add_node_to_cluster.rs │ │ ├── change_resources.rs │ │ ├── common.rs │ │ ├── create_cluster.rs │ │ ├── delete_cluster.rs │ │ ├── delete_node_from_cluster.rs │ │ └── mod.rs │ └── utils.rs │ ├── error.rs │ ├── event.rs │ ├── generator.rs │ ├── lib.rs │ ├── model.rs │ ├── operator.rs │ ├── repository.rs │ ├── repository_json.rs │ └── supported.rs ├── doc └── screenshots │ ├── 1_login.png │ ├── 2_1_create_cluster_settings.png │ ├── 2_2_create_cluster_nodes.png │ ├── 3_list_of_clusters.png │ ├── 4_1_cluster_details.png │ ├── 4_2_helm_apps.png │ ├── 4_3_add_chart.png │ ├── 4_4_workload.png │ └── 4_5_logs.png ├── docker-compose.yaml ├── helm-client ├── Cargo.toml └── src │ ├── command_builder.rs │ └── lib.rs ├── proxmox-client ├── Cargo.toml └── src │ ├── client.rs │ ├── client_operations.rs │ ├── error.rs │ ├── http.rs │ ├── lib.rs │ └── model.rs ├── ssh-client ├── Cargo.toml └── src │ ├── lib.rs │ └── ssh.rs ├── tasks.lua └── web ├── Cargo.toml ├── Makefile ├── src-web ├── .gitignore ├── index.html ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public │ └── favicon.ico ├── src │ ├── App.css │ ├── App.tsx │ ├── Router.tsx │ ├── api │ │ ├── api.ts │ │ ├── apps.ts │ │ ├── auth.ts │ │ ├── cluster_resources.ts │ │ ├── clusters.ts │ │ ├── model.ts │ │ ├── networks.ts │ │ ├── nodes.ts │ │ ├── settings.ts │ │ └── storage.ts │ ├── assets │ │ ├── fonts │ │ │ ├── Poppins │ │ │ │ ├── Poppins-Black.ttf │ │ │ │ ├── Poppins-BlackItalic.ttf │ │ │ │ ├── Poppins-Bold.ttf │ │ │ │ ├── Poppins-BoldItalic.ttf │ │ │ │ ├── Poppins-ExtraBold.ttf │ │ │ │ ├── Poppins-ExtraBoldItalic.ttf │ │ │ │ ├── Poppins-ExtraLight.ttf │ │ │ │ ├── Poppins-ExtraLightItalic.ttf │ │ │ │ ├── Poppins-Italic.ttf │ │ │ │ ├── Poppins-Light.ttf │ │ │ │ ├── Poppins-LightItalic.ttf │ │ │ │ ├── Poppins-Medium.ttf │ │ │ │ ├── Poppins-MediumItalic.ttf │ │ │ │ ├── Poppins-Regular.ttf │ │ │ │ ├── Poppins-SemiBold.ttf │ │ │ │ ├── Poppins-SemiBoldItalic.ttf │ │ │ │ ├── Poppins-Thin.ttf │ │ │ │ └── Poppins-ThinItalic.ttf │ │ │ └── fonts.css │ │ └── images │ │ │ ├── makonn_logo.svg │ │ │ └── makonn_logo_wh.svg │ ├── components │ │ ├── Content.tsx │ │ ├── ErrorPanel │ │ │ ├── ErrorPanel.css │ │ │ └── ErrorPanel.tsx │ │ ├── FormError.tsx │ │ ├── Header.tsx │ │ ├── HiddenPassword.tsx │ │ ├── LogoContainer.tsx │ │ ├── MainContainer.tsx │ │ ├── MainMenu.tsx │ │ ├── Panel.tsx │ │ ├── Section.tsx │ │ └── StorageDropdownOption.tsx │ ├── constants.ts │ ├── main.tsx │ ├── store │ │ ├── application-store.ts │ │ ├── cluster-creator-store.ts │ │ ├── cluster-management-store.ts │ │ ├── clusters-list-store.ts │ │ └── processing-indicator-store.ts │ ├── style.css │ ├── theme │ │ ├── theme-base │ │ │ ├── _colors.scss │ │ │ ├── _common.scss │ │ │ ├── _components.scss │ │ │ ├── _mixins.scss │ │ │ └── components │ │ │ │ ├── button │ │ │ │ ├── _button.scss │ │ │ │ ├── _speeddial.scss │ │ │ │ └── _splitbutton.scss │ │ │ │ ├── data │ │ │ │ ├── _carousel.scss │ │ │ │ ├── _datascroller.scss │ │ │ │ ├── _datatable.scss │ │ │ │ ├── _dataview.scss │ │ │ │ ├── _filter.scss │ │ │ │ ├── _fullcalendar.scss │ │ │ │ ├── _orderlist.scss │ │ │ │ ├── _organizationchart.scss │ │ │ │ ├── _paginator.scss │ │ │ │ ├── _picklist.scss │ │ │ │ ├── _timeline.scss │ │ │ │ ├── _tree.scss │ │ │ │ └── _treetable.scss │ │ │ │ ├── file │ │ │ │ └── _fileupload.scss │ │ │ │ ├── input │ │ │ │ ├── _autocomplete.scss │ │ │ │ ├── _calendar.scss │ │ │ │ ├── _cascadeselect.scss │ │ │ │ ├── _checkbox.scss │ │ │ │ ├── _chips.scss │ │ │ │ ├── _colorpicker.scss │ │ │ │ ├── _dropdown.scss │ │ │ │ ├── _editor.scss │ │ │ │ ├── _inputgroup.scss │ │ │ │ ├── _inputnumber.scss │ │ │ │ ├── _inputswitch.scss │ │ │ │ ├── _inputtext.scss │ │ │ │ ├── _listbox.scss │ │ │ │ ├── _mention.scss │ │ │ │ ├── _multiselect.scss │ │ │ │ ├── _password.scss │ │ │ │ ├── _radiobutton.scss │ │ │ │ ├── _rating.scss │ │ │ │ ├── _selectbutton.scss │ │ │ │ ├── _slider.scss │ │ │ │ ├── _togglebutton.scss │ │ │ │ └── _treeselect.scss │ │ │ │ ├── menu │ │ │ │ ├── _breadcrumb.scss │ │ │ │ ├── _contextmenu.scss │ │ │ │ ├── _dock.scss │ │ │ │ ├── _megamenu.scss │ │ │ │ ├── _menu.scss │ │ │ │ ├── _menubar.scss │ │ │ │ ├── _panelmenu.scss │ │ │ │ ├── _slidemenu.scss │ │ │ │ ├── _steps.scss │ │ │ │ ├── _tabmenu.scss │ │ │ │ └── _tieredmenu.scss │ │ │ │ ├── messages │ │ │ │ ├── _inlinemessage.scss │ │ │ │ ├── _message.scss │ │ │ │ └── _toast.scss │ │ │ │ ├── misc │ │ │ │ ├── _avatar.scss │ │ │ │ ├── _badge.scss │ │ │ │ ├── _blockui.scss │ │ │ │ ├── _chip.scss │ │ │ │ ├── _inplace.scss │ │ │ │ ├── _progressbar.scss │ │ │ │ ├── _scrolltop.scss │ │ │ │ ├── _skeleton.scss │ │ │ │ ├── _tag.scss │ │ │ │ └── _terminal.scss │ │ │ │ ├── multimedia │ │ │ │ ├── _galleria.scss │ │ │ │ └── _image.scss │ │ │ │ ├── overlay │ │ │ │ ├── _confirmpopup.scss │ │ │ │ ├── _dialog.scss │ │ │ │ ├── _overlaypanel.scss │ │ │ │ ├── _sidebar.scss │ │ │ │ └── _tooltip.scss │ │ │ │ └── panel │ │ │ │ ├── _accordion.scss │ │ │ │ ├── _card.scss │ │ │ │ ├── _divider.scss │ │ │ │ ├── _fieldset.scss │ │ │ │ ├── _panel.scss │ │ │ │ ├── _scrollpanel.scss │ │ │ │ ├── _splitter.scss │ │ │ │ ├── _tabview.scss │ │ │ │ └── _toolbar.scss │ │ └── themes │ │ │ └── fluent │ │ │ └── fluent-light │ │ │ ├── _extensions.scss │ │ │ ├── _fonts.scss │ │ │ ├── _variables.scss │ │ │ └── theme.scss │ ├── utils │ │ ├── api.ts │ │ ├── hooks.ts │ │ ├── nodes.ts │ │ ├── patterns.ts │ │ └── size.ts │ ├── views │ │ ├── cluster-creator │ │ │ ├── CreatorNavigator.tsx │ │ │ ├── context.ts │ │ │ ├── index.tsx │ │ │ └── steps │ │ │ │ ├── apps │ │ │ │ ├── CreatorHelmAppDialog.tsx │ │ │ │ ├── HelmAppsSection.tsx │ │ │ │ └── index.tsx │ │ │ │ ├── cluster │ │ │ │ └── index.tsx │ │ │ │ ├── nodes │ │ │ │ ├── CreatorNodeDialog.tsx │ │ │ │ ├── NodesSection.tsx │ │ │ │ ├── TableNodes.tsx │ │ │ │ └── index.tsx │ │ │ │ ├── settings │ │ │ │ └── index.tsx │ │ │ │ └── workloads │ │ │ │ ├── CreatorWorkloadsDialog.tsx │ │ │ │ ├── WorkloadsSection.tsx │ │ │ │ └── index.tsx │ │ ├── cluster-list │ │ │ ├── components │ │ │ │ ├── ClusterListPanel.tsx │ │ │ │ ├── ClusterStatus.tsx │ │ │ │ └── UsedResourcesPanel.tsx │ │ │ └── index.tsx │ │ ├── clusters-management │ │ │ ├── components │ │ │ │ ├── apps │ │ │ │ │ ├── HelmAppDialog.tsx │ │ │ │ │ ├── HelmAppStatus.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── cluster-logs │ │ │ │ │ ├── LogLevel.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── nodes │ │ │ │ │ ├── AddNodeDialog.tsx │ │ │ │ │ ├── EditNodeDialog.tsx │ │ │ │ │ ├── KubeStatusInfo.tsx │ │ │ │ │ ├── NodesTable.tsx │ │ │ │ │ ├── VmStatusInfo.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── workloads │ │ │ │ │ ├── WorkloadsDialog.tsx │ │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── login │ │ │ └── index.tsx │ │ └── settings │ │ │ └── index.tsx │ └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── src ├── handlers │ ├── actix.rs │ ├── apps.rs │ ├── auth.rs │ ├── cluster.rs │ ├── cluster_resources.rs │ ├── error.rs │ ├── export.rs │ ├── mod.rs │ ├── model.rs │ ├── network.rs │ ├── nodes.rs │ ├── settings.rs │ └── storage.rs ├── lib.rs └── main.rs ├── tests ├── common │ ├── makoon_client.rs │ └── mod.rs └── e2e_create_cluster.rs └── typeshare.toml /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | target/ 3 | .idea/ -------------------------------------------------------------------------------- /.github/workflows/build_release.yml: -------------------------------------------------------------------------------- 1 | name: Build release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | with: 16 | token: ${{ secrets.ACCESS_TOKEN }} 17 | - name: Login to Docker Hub 18 | uses: docker/login-action@v2 19 | with: 20 | username: ${{ secrets.DOCKERHUB_USERNAME }} 21 | password: ${{ secrets.DOCKERHUB_TOKEN }} 22 | - name: Set up Docker Buildx 23 | uses: docker/setup-buildx-action@v2 24 | 25 | - name: Set env variables 26 | id: variables 27 | run: | 28 | echo "SOURCE_NAME=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT 29 | echo "SOURCE_BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT 30 | echo "SOURCE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT 31 | 32 | - name: Build and push 33 | uses: docker/build-push-action@v4 34 | with: 35 | context: . 36 | file: ./Dockerfile 37 | push: true 38 | tags: ${{ secrets.DOCKERHUB_USERNAME }}/makoon:${{ steps.variables.outputs.SOURCE_TAG }},${{ secrets.DOCKERHUB_USERNAME }}/makoon:latest -------------------------------------------------------------------------------- /.github/workflows/create_release.yml: -------------------------------------------------------------------------------- 1 | name: Create release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | release-version-and-build-changelog: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | with: 13 | token: ${{ secrets.ACCESS_TOKEN }} 14 | 15 | - name: Conventional Changelog Action 16 | id: changelog 17 | uses: TriPSs/conventional-changelog-action@v3.7.1 18 | with: 19 | github-token: ${{ secrets.ACCESS_TOKEN }} 20 | git-message: 'chore(release): {version}' 21 | version-file: './web/src-web/package.json' 22 | git-user-email: damian.sieradzki@hotmail.com 23 | 24 | - name: Create release 25 | uses: softprops/action-gh-release@v1 26 | if: ${{ steps.changelog.outputs.skipped == 'false' }} 27 | with: 28 | token: ${{ secrets.ACCESS_TOKEN }} 29 | name: ${{ steps.changelog.outputs.tag }} 30 | tag_name: ${{ steps.changelog.outputs.tag }} 31 | body: ${{ steps.changelog.outputs.clean_changelog }} -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | with: 16 | token: ${{ secrets.ACCESS_TOKEN }} 17 | 18 | - name: Login to Docker Hub 19 | uses: docker/login-action@v2 20 | with: 21 | username: ${{ secrets.DOCKERHUB_USERNAME }} 22 | password: ${{ secrets.DOCKERHUB_TOKEN }} 23 | - name: Set up Docker Buildx 24 | uses: docker/setup-buildx-action@v2 25 | - name: Build and push 26 | uses: docker/build-push-action@v4 27 | with: 28 | context: . 29 | file: ./Dockerfile 30 | push: true 31 | tags: ${{ secrets.DOCKERHUB_USERNAME }}/makoon:main -------------------------------------------------------------------------------- /.github/workflows/pull_request.yaml: -------------------------------------------------------------------------------- 1 | name: Pull request 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | with: 13 | token: ${{ secrets.ACCESS_TOKEN }} 14 | 15 | - name: Login to Docker Hub 16 | uses: docker/login-action@v2 17 | with: 18 | username: ${{ secrets.DOCKERHUB_USERNAME }} 19 | password: ${{ secrets.DOCKERHUB_TOKEN }} 20 | - name: Set up Docker Buildx 21 | uses: docker/setup-buildx-action@v2 22 | - name: Build 23 | uses: docker/build-push-action@v4 24 | with: 25 | context: . 26 | file: ./Dockerfile 27 | push: false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | Cargo.lock 4 | **/*.rs.bk 5 | **/*.iml 6 | .vscode 7 | makoon.json 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.17.4](https://github.com/dsieradzki/makoon/compare/v0.17.3...v0.17.4) (2024-05-08) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * Support Proxmox 8.2 ([#78](https://github.com/dsieradzki/makoon/issues/78)) ([22dce6f](https://github.com/dsieradzki/makoon/commit/22dce6f0c7a28d0ff865c1f76031fc7ac578e558)) 7 | 8 | 9 | 10 | ## [0.17.3](https://github.com/dsieradzki/makoon/compare/v0.17.2...v0.17.3) (2024-02-14) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * Remove repository requirement to support oci helm charts ([8cdca24](https://github.com/dsieradzki/makoon/commit/8cdca2438a9c73f9cbf4466b487200de99315b12)) 16 | 17 | 18 | 19 | ## [0.17.2](https://github.com/dsieradzki/makoon/compare/v0.17.1...v0.17.2) (2024-02-14) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * Remove repository requirement to support oci helm charts ([350a485](https://github.com/dsieradzki/makoon/commit/350a4855acc0349fad9c98230b8f2f3615205c3f)) 25 | 26 | 27 | 28 | ## [0.17.1](https://github.com/dsieradzki/makoon/compare/v0.17.0...v0.17.1) (2024-02-14) 29 | 30 | 31 | ### Bug Fixes 32 | 33 | * Remove repository requirement to support oci helm charts ([4d8d6c0](https://github.com/dsieradzki/makoon/commit/4d8d6c041a50ba78bc796ecce31e9770a749cdc0)) 34 | 35 | 36 | 37 | # [0.17.0](https://github.com/dsieradzki/makoon/compare/v0.16.1...v0.17.0) (2023-10-16) 38 | 39 | 40 | ### Bug Fixes 41 | 42 | * Better UI layout ([d72067d](https://github.com/dsieradzki/makoon/commit/d72067d689b0e1dde8db819f0c1506f5d12126ed)) 43 | * Validation messages ([7c95915](https://github.com/dsieradzki/makoon/commit/7c959152513b78c733a2c017eba9810d2b6f8c73)) 44 | 45 | 46 | ### Features 47 | 48 | * Remember IP during login ([25e397a](https://github.com/dsieradzki/makoon/commit/25e397a5e807cb6daf8a875170c1aa7c44aec7ba)) 49 | * Remember username during login ([fea8e1e](https://github.com/dsieradzki/makoon/commit/fea8e1eb290503adcbcacc139ac83d977b6e4111)) 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | members = [ 5 | "core", 6 | "web", 7 | "proxmox-client", 8 | "helm-client", 9 | "ssh-client" 10 | ] -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/rust:1.73.0 as build 2 | # Install and configure NODE using NVM 3 | RUN mkdir /usr/local/nvm 4 | ENV NVM_DIR /usr/local/nvm 5 | ENV NODE_VERSION 18.18.0 6 | RUN curl https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash \ 7 | && . $NVM_DIR/nvm.sh \ 8 | && nvm install $NODE_VERSION \ 9 | && nvm alias default $NODE_VERSION \ 10 | && nvm use default 11 | 12 | ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules 13 | ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH 14 | 15 | RUN npm install -g pnpm 16 | RUN mkdir /build 17 | WORKDIR /build 18 | COPY . . 19 | RUN cd web/src-web; \ 20 | pnpm install; \ 21 | pnpm build; 22 | 23 | RUN cargo test 24 | RUN cargo build --release 25 | 26 | FROM debian:bookworm-slim 27 | RUN apt-get update && \ 28 | apt-get upgrade -y && \ 29 | apt-get install libssl3 -y && \ 30 | apt-get clean 31 | 32 | RUN mkdir /app 33 | WORKDIR /app 34 | COPY --from=build /build/target/release/makoon . 35 | ENV RUST_LOG="info" 36 | ENV MAKOON_DB_PATH="/app/data/makoon.db" 37 | ENV MAKOON_SERVER_PORT=8080 38 | VOLUME /app/data 39 | CMD ["/app/makoon"] 40 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | docker { image 'rust:1.73.0-bullseye' } 4 | } 5 | stages { 6 | stage('Checkout') { 7 | steps { 8 | sh 'rm -rf makoon' 9 | sh 'git clone --depth 1 https://github.com/dsieradzki/makoon' 10 | } 11 | } 12 | stage('Test') { 13 | steps { 14 | dir('makoon') { 15 | withCredentials([usernamePassword(credentialsId: 'proxmox', usernameVariable: 'PROXMOX_USER', passwordVariable: 'PROXMOX_PASSWORD')]) { 16 | sh 'mkdir web/src-web/dist/ -p && touch web/src-web/dist/dummy' 17 | sh 'cargo test --features e2e -- --nocapture' 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "core" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | proxmox-client = { path = "../proxmox-client" } 10 | helm-client = { path = "../helm-client" } 11 | ssh-client = { path = "../ssh-client" } 12 | openssl = "0.10" 13 | pem = "3.0" 14 | ssh-keys = "0.1" 15 | serde = { version = "1.0", features = ["derive"] } 16 | serde_json = "1.0" 17 | typeshare = "1.0" 18 | chrono = { version = "0.4", features = ["serde"] } 19 | log = "0.4" 20 | env_logger = "0.10" 21 | uuid = { version = "1.4", features = ["v4", "fast-rng"] } 22 | -------------------------------------------------------------------------------- /core/src/dispatcher/mod.rs: -------------------------------------------------------------------------------- 1 | pub use dispatcher::*; 2 | 3 | mod dispatcher; 4 | mod usecase; 5 | mod utils; 6 | pub use usecase::install_cluster_resource; 7 | pub use usecase::install_helm_app; 8 | pub use usecase::HELM_CMD; 9 | -------------------------------------------------------------------------------- /core/src/dispatcher/usecase/change_resources.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use log::info; 3 | 4 | use proxmox_client::{ 5 | model::{AccessData, VmConfig}, 6 | Client, 7 | }; 8 | use crate::dispatcher::usecase::common; 9 | use crate::model::LogEntry; 10 | use crate::Repository; 11 | 12 | 13 | pub(crate) fn execute( 14 | proxmox_client: Arc, 15 | repo: Arc, 16 | access: AccessData, 17 | cluster_name: String, 18 | node_name: String, 19 | cores: u16, 20 | memory: u32, 21 | ) -> Result<(), String> { 22 | info!("Cluster creation request has been received"); 23 | let proxmox_client = proxmox_client.operations(access); 24 | 25 | repo.save_log(LogEntry::info( 26 | &cluster_name, 27 | "Start creating cluster".to_string(), 28 | ))?; 29 | 30 | let cluster = repo 31 | .get_cluster(&cluster_name)? 32 | .ok_or("Cannot find cluster")?; 33 | 34 | let node_to_change = cluster 35 | .nodes 36 | .iter() 37 | .find(|i| i.name == node_name) 38 | .ok_or("Cannot find node to create")?; 39 | proxmox_client.update_config(VmConfig { 40 | vm_id: node_to_change.vm_id, 41 | node: cluster.node.clone(), 42 | cores, 43 | memory: u64::from(memory), 44 | })?; 45 | 46 | common::vm::stop_vm(&proxmox_client, &cluster.node, node_to_change.vm_id)?; 47 | proxmox_client.start_vm(&cluster.node, node_to_change.vm_id)?; 48 | 49 | let mut cluster = repo 50 | .get_cluster(&cluster_name)? 51 | .ok_or("Cannot find cluster")?; 52 | for node in cluster.nodes.iter_mut() { 53 | if node.name == node_name { 54 | node.cores = cores; 55 | node.memory = memory; 56 | } 57 | } 58 | repo.save_cluster(cluster)?; 59 | 60 | Ok(()) 61 | } 62 | -------------------------------------------------------------------------------- /core/src/dispatcher/usecase/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod add_node_to_cluster; 2 | pub mod change_resources; 3 | mod common; 4 | pub mod create_cluster; 5 | pub mod delete_cluster; 6 | pub mod delete_node_from_cluster; 7 | pub use common::apps::install_cluster_resource; 8 | pub use common::apps::install_helm_app; 9 | pub use common::apps::HELM_CMD; 10 | -------------------------------------------------------------------------------- /core/src/dispatcher/utils.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | use log::info; 3 | 4 | pub fn retry(f: F) -> Result 5 | where 6 | E: ToString, 7 | F: Fn() -> Result, 8 | { 9 | retry_with_opts::(f, 10, 30) 10 | } 11 | 12 | pub fn retry_with_opts(f: F, delay: u64, attempts: u64) -> Result 13 | where 14 | E: ToString, 15 | F: Fn() -> Result, 16 | { 17 | let mut attempts_left = attempts; 18 | loop { 19 | match f() { 20 | Ok(v) => return Ok(v), 21 | Err(e) => { 22 | if attempts_left > 0 { 23 | attempts_left -= 1; 24 | info!( 25 | "Operation probe: [{}/{}], wait [{}] seconds: {}", 26 | (attempts - attempts_left), 27 | attempts, 28 | delay, 29 | e.to_string() 30 | ); 31 | std::thread::sleep(Duration::from_secs(delay)) 32 | } else { 33 | return Err(e); 34 | } 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /core/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | use std::sync::mpsc::SendError; 3 | use crate::repository; 4 | 5 | 6 | #[derive(Debug)] 7 | pub enum Error { 8 | ResourceNotFound, 9 | ResourceAlreadyExists, 10 | Generic(String), 11 | } 12 | 13 | impl From for Error { 14 | fn from(value: repository::Error) -> Self { 15 | match value { 16 | repository::Error::IO(e) => Error::Generic(e), 17 | repository::Error::DB(e) => Error::Generic(e), 18 | } 19 | } 20 | } 21 | 22 | impl From for Error { 23 | fn from(value: proxmox_client::Error) -> Self { 24 | match value { 25 | proxmox_client::Error::Generic(e) => Error::Generic(e), 26 | proxmox_client::Error::CannotConnectToProxmox(e) => Error::Generic(e), 27 | proxmox_client::Error::CredentialsInvalid => Error::Generic(String::new()), 28 | proxmox_client::Error::HttpError { 29 | status, 30 | response, 31 | reason, 32 | } => Error::Generic(format!( 33 | "Http status: [{}], reason: [{}], response: [{}]", 34 | status, reason, response 35 | )), 36 | proxmox_client::Error::BodyMalformed(e) => Error::Generic(e), 37 | } 38 | } 39 | } 40 | 41 | impl From> for Error { 42 | fn from(value: SendError) -> Self { 43 | Error::Generic(value.to_string()) 44 | } 45 | } 46 | 47 | impl From for Error { 48 | fn from(value: serde_json::Error) -> Self { 49 | Error::Generic(value.to_string()) 50 | } 51 | } 52 | 53 | impl From for Error { 54 | fn from(value: String) -> Self { 55 | Error::Generic(value) 56 | } 57 | } 58 | 59 | impl Display for Error { 60 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 61 | match self { 62 | Error::ResourceAlreadyExists => write!(f, "Cluster already exists"), 63 | Error::Generic(e) => write!(f, "Unknown error: {}", e), 64 | Error::ResourceNotFound => write!(f, "Cluster not found"), 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /core/src/event.rs: -------------------------------------------------------------------------------- 1 | use proxmox_client::model::AccessData; 2 | 3 | #[derive(Debug)] 4 | pub enum Event { 5 | CreateCluster { 6 | access: AccessData, 7 | cluster_name: String, 8 | }, 9 | AddNodeToCluster { 10 | access: AccessData, 11 | cluster_name: String, 12 | node_name: String, 13 | }, 14 | DeleteNodeFromCluster { 15 | access: AccessData, 16 | cluster_name: String, 17 | node_name: String, 18 | }, 19 | DeleteCluster { 20 | access: AccessData, 21 | cluster_name: String, 22 | }, 23 | ChangeNodeResources { 24 | access: AccessData, 25 | cluster_name: String, 26 | node_name: String, 27 | cores: u16, 28 | #[doc = "Unit: MiB"] 29 | memory: u32, 30 | }, 31 | } 32 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod dispatcher; 2 | mod error; 3 | mod event; 4 | mod generator; 5 | mod operator; 6 | mod repository; 7 | mod repository_json; 8 | pub mod model; 9 | pub mod supported; 10 | 11 | pub use dispatcher::Dispatcher; 12 | pub use error::Error; 13 | pub use operator::{Config, Operator}; 14 | pub use repository::Repository; 15 | 16 | pub type Result = std::result::Result; 17 | 18 | pub use generator::DefaultClusterConfigurationGenerator; 19 | -------------------------------------------------------------------------------- /core/src/repository.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Display, Formatter}; 2 | use crate::model::{Cluster, LogEntry}; 3 | use crate::repository_json::JsonRepository; 4 | 5 | 6 | #[derive(Clone, Debug)] 7 | pub enum Error { 8 | DB(String), 9 | IO(String), 10 | } 11 | 12 | impl Display for Error { 13 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 14 | match self { 15 | Error::DB(e) => write!(f, "{}", e), 16 | Error::IO(e) => write!(f, "{}", e), 17 | } 18 | } 19 | } 20 | 21 | impl From for String { 22 | fn from(value: Error) -> Self { 23 | value.to_string() 24 | } 25 | } 26 | 27 | pub type Result = std::result::Result; 28 | 29 | pub struct Repository { 30 | inner: JsonRepository, 31 | } 32 | 33 | impl Repository { 34 | pub fn new(path: &str) -> Result { 35 | Ok(Repository { 36 | inner: JsonRepository::new(path)?, 37 | }) 38 | } 39 | 40 | pub fn get_clusters(&self) -> Result> { 41 | self.inner.get_clusters() 42 | } 43 | 44 | pub fn get_cluster(&self, name: &str) -> Result> { 45 | self.inner.get_cluster(name) 46 | } 47 | 48 | pub fn delete_cluster(&self, name: &str) -> Result<()> { 49 | self.inner.delete_cluster(name) 50 | } 51 | 52 | pub fn save_cluster(&self, cluster_to_save: Cluster) -> Result<()> { 53 | self.inner.save_cluster(cluster_to_save) 54 | } 55 | 56 | pub fn logs(&self, cluster_name: &str) -> Result> { 57 | self.inner.logs(cluster_name) 58 | } 59 | 60 | pub fn save_log(&self, entry: LogEntry) -> Result<()> { 61 | self.inner.save_log(entry) 62 | } 63 | pub fn delete_logs(&self, cluster_name: &str) -> Result<()> { 64 | self.inner.delete_logs(cluster_name)?; 65 | Ok(()) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /core/src/supported.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | pub fn kube_versions() -> Vec { 4 | vec![ 5 | "1.28/stable".to_string(), 6 | "1.27/stable".to_string(), 7 | "1.26/stable".to_string(), 8 | "1.25/stable".to_string(), 9 | "1.24/stable".to_string(), 10 | ] 11 | } 12 | 13 | pub fn os_images() -> HashMap { 14 | HashMap::from([ 15 | ( 16 | "Ubuntu Server 22.04 LTS - jammy-server-cloudimg-amd64.img".to_string(), 17 | "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img".to_string() 18 | ) 19 | ]) 20 | } -------------------------------------------------------------------------------- /doc/screenshots/1_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/doc/screenshots/1_login.png -------------------------------------------------------------------------------- /doc/screenshots/2_1_create_cluster_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/doc/screenshots/2_1_create_cluster_settings.png -------------------------------------------------------------------------------- /doc/screenshots/2_2_create_cluster_nodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/doc/screenshots/2_2_create_cluster_nodes.png -------------------------------------------------------------------------------- /doc/screenshots/3_list_of_clusters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/doc/screenshots/3_list_of_clusters.png -------------------------------------------------------------------------------- /doc/screenshots/4_1_cluster_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/doc/screenshots/4_1_cluster_details.png -------------------------------------------------------------------------------- /doc/screenshots/4_2_helm_apps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/doc/screenshots/4_2_helm_apps.png -------------------------------------------------------------------------------- /doc/screenshots/4_3_add_chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/doc/screenshots/4_3_add_chart.png -------------------------------------------------------------------------------- /doc/screenshots/4_4_workload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/doc/screenshots/4_4_workload.png -------------------------------------------------------------------------------- /doc/screenshots/4_5_logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/doc/screenshots/4_5_logs.png -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | makoon: 3 | image: sieradzki/makoon:latest 4 | ports: 5 | - 80:8080 6 | volumes: 7 | - makoon_db:/app/data 8 | volumes: 9 | makoon_db: 10 | -------------------------------------------------------------------------------- /helm-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "helm-client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /helm-client/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod command_builder; 2 | 3 | pub use command_builder::*; 4 | 5 | pub fn new(binary: &str) -> CommandBuilder { 6 | CommandBuilder::new(binary) 7 | } 8 | -------------------------------------------------------------------------------- /proxmox-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proxmox-client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | reqwest = { version = "0.11.18", features = ["blocking", "json"] } 10 | serde = { version = "1.0", features = ["derive"] } 11 | serde_json = "1.0" 12 | regex = "1.9.1" 13 | log = "0.4.19" 14 | env_logger = "0.10.0" 15 | urlencoding = "2.1.3" 16 | -------------------------------------------------------------------------------- /proxmox-client/src/client.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | use reqwest::header::CONTENT_TYPE; 3 | use crate::{Result, Error, to_url_encoded}; 4 | use crate::client_operations::ClientOperations; 5 | use crate::http::{HttpClient}; 6 | use crate::model::*; 7 | 8 | pub struct Client {} 9 | 10 | 11 | impl Default for Client { 12 | fn default() -> Self { 13 | Client::new() 14 | } 15 | } 16 | 17 | 18 | 19 | impl Client { 20 | pub fn new() -> Self { 21 | Client {} 22 | } 23 | 24 | pub fn login(&self, options: LoginRequest) -> Result { 25 | let http = HttpClient::new( 26 | options.host.clone(), 27 | options.port, 28 | options.base_path.clone()); 29 | 30 | let response = http.client() 31 | .post(format!("https://{}:{}{}/access/ticket?password={}", options.host, options.port, options.base_path, to_url_encoded(&options.password))) 32 | .header(CONTENT_TYPE, "application/x-www-form-urlencoded") 33 | .body(format!("username={}@pam", options.username)) 34 | .timeout(Duration::from_secs(10)) 35 | .send()?; 36 | 37 | match response.status().as_u16() { 38 | 200..=299 => { 39 | let token = response.json::>()?.data; 40 | Ok(AccessData { 41 | token, 42 | host: options.host, 43 | port: options.port, 44 | base_path: options.base_path, 45 | }) 46 | } 47 | 401 => Err(Error::CredentialsInvalid), 48 | c => Err(Error::Generic(format!("Login to proxmox returned [{}] code", c))) 49 | } 50 | } 51 | pub fn operations(&self, access: AccessData) -> ClientOperations { 52 | let http = HttpClient::new( 53 | access.host, 54 | access.port, 55 | access.base_path); 56 | 57 | ClientOperations::new(http, access.token) 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /proxmox-client/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | 3 | #[derive(Debug)] 4 | pub enum Error { 5 | Generic(String), 6 | CannotConnectToProxmox(String), 7 | CredentialsInvalid, 8 | HttpError { 9 | status: u16, 10 | reason: String, 11 | response: String, 12 | }, 13 | BodyMalformed(String), 14 | } 15 | 16 | impl From for Error { 17 | fn from(value: reqwest::Error) -> Self { 18 | match value.is_connect() || value.is_timeout() { 19 | true => Error::CannotConnectToProxmox(value.to_string()), 20 | false => Error::Generic(value.to_string()) 21 | } 22 | } 23 | } 24 | 25 | impl From for String { 26 | fn from(value: Error) -> Self { 27 | value.to_string() 28 | } 29 | } 30 | 31 | impl Display for Error { 32 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 33 | match self { 34 | Error::CannotConnectToProxmox(e) => write!(f, "Cannot connect to Proxmox: [{}]", e), 35 | Error::CredentialsInvalid => write!(f, "Credentials invalid"), 36 | Error::Generic(e) => write!(f, "Generic error: [{}]", e), 37 | Error::HttpError { status, reason, response } => write!(f, "Http status: [{}], reason: [{}], response: [{}]", status, reason, response), 38 | Error::BodyMalformed(e) => write!(f, "Body malformed: [{}]", e), 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /proxmox-client/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod http; 2 | mod client; 3 | mod client_operations; 4 | 5 | mod error; 6 | pub mod model; 7 | 8 | pub use error::Error; 9 | 10 | pub type Result = std::result::Result; 11 | 12 | pub use client::*; 13 | pub use client_operations::*; 14 | 15 | //TODO: cleanup using this outside 16 | pub fn to_url_encoded(val: &str) -> String { 17 | let result = urlencoding::encode(val); 18 | result.to_string() 19 | } 20 | 21 | 22 | #[cfg(test)] 23 | mod test { 24 | use crate::to_url_encoded; 25 | 26 | #[test] 27 | fn to_url_encoded_ssh_key() { 28 | let input = r#"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCoLCbdAlkatCns0eVPPTWFD0SbSm72I3jLEET6BDNRr2Nu0FXmM04zpBaWySmRuS+Pq1Kh6OVt7K9ngQJIagFTGnuW/NBDBG4e89KbKh9+txfIVrKiBQHwVHBUXXN07uyTKrVUHOoX0E8gwQR1lBChsfb9aNYUwgMdBQpgA3qYQb2AfUoTDmzhBYG8PNjAK1fICt0AaHAyYrfKi7n/XvjxapwR5i3y7YwS0vz6tWAwg2KDReP4bdnyPxYVDZhnsSqgiXLvLbCB7qGbnP+xNe2B23hLSsKrtv8FXqn2PwFXu+blmPiRnSifhBh1ChhliMXulQKUo5duRC/xuNhoNi87Sosc2WDPit6/QtV7kFGfZvwsV+7XdH0ltsn3Ly6nLTTas48IZhgk4Ew6xVJ8gub4JHgZblOrRB5ENv/MVoYwzSDUD3SCEmF3DCbl02H/ljMabVsTrNeHy4vZXCwzuTVgyWWY/WSGGrnK1OrcwUrYYhhwwtFCSfknsCcc360+u2wwCIKnpBURiGdRMupRRMRoLdxekobGKz90MHQsGlVGPtk/3qeVQ7aoX6tNaucnUdaxkfO4Dk5bXJtWSZLmqOdKAbfrRIxuvSnvusYG8ODTEkg1BGp5kbmdkMCRWOzNJvn9+V7A4U6m4AXRXgk2u8kmtzSO/SWgSdwWlO6BE7Pmvw=="#; 29 | let output = r#"ssh-rsa%20AAAAB3NzaC1yc2EAAAADAQABAAACAQCoLCbdAlkatCns0eVPPTWFD0SbSm72I3jLEET6BDNRr2Nu0FXmM04zpBaWySmRuS%2BPq1Kh6OVt7K9ngQJIagFTGnuW%2FNBDBG4e89KbKh9%2BtxfIVrKiBQHwVHBUXXN07uyTKrVUHOoX0E8gwQR1lBChsfb9aNYUwgMdBQpgA3qYQb2AfUoTDmzhBYG8PNjAK1fICt0AaHAyYrfKi7n%2FXvjxapwR5i3y7YwS0vz6tWAwg2KDReP4bdnyPxYVDZhnsSqgiXLvLbCB7qGbnP%2BxNe2B23hLSsKrtv8FXqn2PwFXu%2BblmPiRnSifhBh1ChhliMXulQKUo5duRC%2FxuNhoNi87Sosc2WDPit6%2FQtV7kFGfZvwsV%2B7XdH0ltsn3Ly6nLTTas48IZhgk4Ew6xVJ8gub4JHgZblOrRB5ENv%2FMVoYwzSDUD3SCEmF3DCbl02H%2FljMabVsTrNeHy4vZXCwzuTVgyWWY%2FWSGGrnK1OrcwUrYYhhwwtFCSfknsCcc360%2Bu2wwCIKnpBURiGdRMupRRMRoLdxekobGKz90MHQsGlVGPtk%2F3qeVQ7aoX6tNaucnUdaxkfO4Dk5bXJtWSZLmqOdKAbfrRIxuvSnvusYG8ODTEkg1BGp5kbmdkMCRWOzNJvn9%2BV7A4U6m4AXRXgk2u8kmtzSO%2FSWgSdwWlO6BE7Pmvw%3D%3D"#; 30 | 31 | let result = to_url_encoded(input); 32 | assert_eq!(output, result); 33 | } 34 | } -------------------------------------------------------------------------------- /ssh-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ssh-client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | ssh2 = "0.9" 10 | serde = { version = "1.0", features = ["derive"] } 11 | serde_json = "1.0" 12 | log = "0.4" 13 | -------------------------------------------------------------------------------- /ssh-client/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod ssh; 2 | pub use ssh::*; 3 | 4 | #[macro_use] 5 | extern crate log; 6 | -------------------------------------------------------------------------------- /tasks.lua: -------------------------------------------------------------------------------- 1 | return { 2 | tasks = { 3 | { 4 | name = "backend", 5 | exec = "cargo watch -x run", 6 | }, 7 | { 8 | name = "frontend", 9 | exec = "pnpm run dev", 10 | working_dir = "web/src-web", 11 | }, 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /web/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "web" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [[bin]] 7 | name = "makoon" 8 | path = "src/main.rs" 9 | 10 | [features] 11 | e2e = [] 12 | 13 | [dependencies] 14 | core = { path = "../core" } 15 | proxmox-client = { path = "../proxmox-client" } 16 | tokio = { version = "1.36", features = ["macros", "rt-multi-thread"] } 17 | actix-web = "4.5" 18 | actix-session = { version = "0.8", features = ["cookie-session"] } 19 | serde = { version = "1.0", features = ["derive"] } 20 | serde_json = "1.0" 21 | rust-embed = "8.2" 22 | mime_guess = "2.0" 23 | typeshare = "1.0" 24 | chrono = { version = "0.4", features = ["serde"] } 25 | log = "0.4" 26 | env_logger = "0.10" 27 | rayon = "1.8" 28 | uuid = { version = "1.7", features = ["v4", "fast-rng"] } 29 | 30 | [dev-dependencies] 31 | reqwest = { version = "0.11", features = ["blocking", "json", "cookies"] } 32 | -------------------------------------------------------------------------------- /web/Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | .PHONY: 4 | .ONESHELL: 5 | clean: 6 | rm src-web/src/api -rfv 7 | rm src-web/dist -rfv 8 | rm src-web/node_modules -rfv 9 | 10 | .PHONY: 11 | .ONESHELL: 12 | build: web.install web.generate-models web.build app.build.release 13 | 14 | .PHONY: 15 | .ONESHELL: 16 | web.generate-models: 17 | typeshare ../ --lang=typescript -c ./typeshare.toml --output-file=src-web/src/api/model.ts 18 | 19 | .PHONY: 20 | .ONESHELL: 21 | web.install: 22 | . ${HOME}/.nvm/nvm.sh && nvm use --lts 23 | cd src-web 24 | pnpm install 25 | 26 | .PHONY: 27 | .ONESHELL: 28 | web.build: 29 | . ${HOME}/.nvm/nvm.sh && nvm use --lts 30 | cd src-web 31 | pnpm build 32 | 33 | .PHONY: 34 | .ONESHELL: 35 | app.build.release: 36 | cargo build --release 37 | 38 | .PHONY: 39 | .ONESHELL: 40 | app.build.debug: 41 | cargo build 42 | -------------------------------------------------------------------------------- /web/src-web/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | package.json.md5 30 | -------------------------------------------------------------------------------- /web/src-web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Makoon 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /web/src-web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.17.4", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "preview": "vite preview", 9 | "update:dependencies": "ncu -u" 10 | }, 11 | "dependencies": { 12 | "@heroicons/react": "^2.0.18", 13 | "axios": "^1.4.0", 14 | "formik": "^2.4.3", 15 | "mobx": "^6.10.0", 16 | "mobx-react-lite": "^4.0.3", 17 | "primeicons": "^6.0.1", 18 | "primereact": "^9.6.2", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "react-router-dom": "^6.15.0", 22 | "yup": "^1.2.0" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "^20.5.4", 26 | "@types/react": "^18.2.21", 27 | "@types/react-dom": "^18.2.7", 28 | "@vitejs/plugin-react": "^4.0.4", 29 | "autoprefixer": "^10.4.15", 30 | "npm-check-updates": "^16.13.1", 31 | "postcss": "^8.4.28", 32 | "sass": "^1.66.1", 33 | "tailwindcss": "^3.3.3", 34 | "typescript": "^5.1.6", 35 | "vite": "^4.4.9" 36 | } 37 | } -------------------------------------------------------------------------------- /web/src-web/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /web/src-web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/public/favicon.ico -------------------------------------------------------------------------------- /web/src-web/src/App.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, body { 6 | padding: 0; 7 | margin: 0; 8 | width: 100%; 9 | height: 100%; 10 | background-color: var(--bg); 11 | } 12 | 13 | #root { 14 | height: 100%; 15 | width: 100%; 16 | } 17 | 18 | .p-component { 19 | font-family: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 20 | font-size: 1rem; 21 | font-weight: normal; 22 | } 23 | 24 | input::placeholder { 25 | font-style: italic; 26 | opacity: 0.4; 27 | } 28 | 29 | .required::after { 30 | content: '*'; 31 | color: var(--danger); 32 | } 33 | 34 | .font-monospace { 35 | font-family: 'Courier New', monospace !important; 36 | } -------------------------------------------------------------------------------- /web/src-web/src/App.tsx: -------------------------------------------------------------------------------- 1 | import "primereact/resources/primereact.min.css"; 2 | import "primeicons/primeicons.css"; 3 | import './App.css'; 4 | import "./assets/fonts/fonts.css"; 5 | import Router from "@/Router"; 6 | import "./theme/themes/fluent/fluent-light/theme.scss" 7 | import ErrorPanel from "@/components/ErrorPanel/ErrorPanel"; 8 | 9 | function App() { 10 | return <> 11 | 12 | 13 | 14 | } 15 | 16 | export default App 17 | -------------------------------------------------------------------------------- /web/src-web/src/Router.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Navigate, Route, Routes} from "react-router-dom"; 3 | import ClusterList from "@/views/cluster-list"; 4 | import ClusterManagement from "@/views/clusters-management"; 5 | import Nodes from "@/views/clusters-management/components/nodes"; 6 | import Apps from "@/views/clusters-management/components/apps"; 7 | import Workloads from "@/views/clusters-management/components/workloads"; 8 | import Login from "@/views/login"; 9 | import Logs from "@/views/clusters-management/components/cluster-logs"; 10 | 11 | type GuardProps = { 12 | children: React.ReactNode 13 | } 14 | 15 | const Guard = (props: GuardProps) => { 16 | const logged = document.cookie.indexOf('id=') !== -1; 17 | return logged 18 | ? <>{props.children} 19 | : 20 | } 21 | 22 | const Router = () => { 23 | return 24 | }/> 25 | }/> 26 | 28 | 29 | 30 | }/> 31 | 32 | 34 | 35 | 36 | }> 37 | }/> 38 | }/> 39 | }/> 40 | }/> 41 | }/> 42 | 43 | ; 44 | }; 45 | 46 | export default Router; -------------------------------------------------------------------------------- /web/src-web/src/api/api.ts: -------------------------------------------------------------------------------- 1 | import { networks } from "@/api/networks"; 2 | import { nodes } from "@/api/nodes"; 3 | import { auth } from "@/api/auth"; 4 | import { clusters } from "@/api/clusters"; 5 | import { storage } from "@/api/storage"; 6 | import { apps } from "@/api/apps"; 7 | import { cluster_resources } from "@/api/cluster_resources"; 8 | import {settings} from "@/api/settings"; 9 | 10 | export default { 11 | networks, 12 | nodes, 13 | auth, 14 | clusters, 15 | storage, 16 | apps, 17 | cluster_resources, 18 | settings 19 | } 20 | -------------------------------------------------------------------------------- /web/src-web/src/api/apps.ts: -------------------------------------------------------------------------------- 1 | import { AppStatus, HelmApp } from "@/api/model"; 2 | import axios from "axios"; 3 | 4 | export namespace apps { 5 | 6 | export function appsStatus(clusterName: string): Promise { 7 | return axios.get(`/api/v1/clusters/${clusterName}/apps/status`).then(e => e.data); 8 | } 9 | 10 | export function saveHelmApp(clusterName: string, app: HelmApp): Promise { 11 | return axios.post(`/api/v1/clusters/${clusterName}/apps`, app).then(e => e.data); 12 | } 13 | 14 | export function updateHelmApp(clusterName: string, app: HelmApp): Promise { 15 | return axios.put(`/api/v1/clusters/${clusterName}/apps`, app); 16 | } 17 | 18 | export function deleteHelmApp(clusterName: string, appId: string): Promise { 19 | return axios.delete(`/api/v1/clusters/${clusterName}/apps/${appId}`); 20 | } 21 | 22 | export function installHelmApp(clusterName: string, appId: string): Promise { 23 | return axios.post(`/api/v1/clusters/${clusterName}/apps/${appId}/install`); 24 | } 25 | 26 | 27 | export function uninstallHelmApp(clusterName: string, appId: string): Promise { 28 | return axios.delete(`/api/v1/clusters/${clusterName}/apps/${appId}/uninstall`); 29 | } 30 | } -------------------------------------------------------------------------------- /web/src-web/src/api/auth.ts: -------------------------------------------------------------------------------- 1 | import { LoginRequest } from "@/api/model"; 2 | import axios, { AxiosError } from 'axios'; 3 | 4 | export namespace auth { 5 | export function getLoggedInHostIp(): Promise { 6 | return axios.get("/api/v1/host-ip").then(e => e.data) 7 | } 8 | 9 | export function login(req: LoginRequest): Promise { 10 | return axios.post("/api/v1/login", req).then(e => e.status).catch((e: AxiosError) => e.response?.status ?? 400); 11 | } 12 | 13 | export function logout(): Promise { 14 | return axios.post("/api/v1/logout") 15 | } 16 | } -------------------------------------------------------------------------------- /web/src-web/src/api/cluster_resources.ts: -------------------------------------------------------------------------------- 1 | import { ClusterResource } from "@/api/model"; 2 | import axios from "axios"; 3 | 4 | export namespace cluster_resources { 5 | export function saveClusterResource(clusterName: string, app: ClusterResource): Promise { 6 | return axios.post(`/api/v1/clusters/${clusterName}/cluster-resources`, app).then(e => e.data); 7 | } 8 | 9 | export function updateClusterResource(clusterName: string, app: ClusterResource): Promise { 10 | return axios.put(`/api/v1/clusters/${clusterName}/cluster-resources`, app); 11 | } 12 | 13 | export function deleteClusterResource(clusterName: string, resId: string): Promise { 14 | return axios.delete(`/api/v1/clusters/${clusterName}/cluster-resources/${resId}`); 15 | } 16 | 17 | export function installClusterResource(clusterName: string, resId: string): Promise { 18 | return axios.post(`/api/v1/clusters/${clusterName}/cluster-resources/${resId}/install`); 19 | } 20 | 21 | export function uninstallClusterResource(clusterName: string, resId: string): Promise { 22 | return axios.delete(`/api/v1/clusters/${clusterName}/cluster-resources/${resId}/uninstall`); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /web/src-web/src/api/clusters.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChangeNodeResourcesRequest, 3 | Cluster, 4 | ClusterHeader, 5 | ClusterNode, 6 | ClusterNodeStatus, 7 | ClusterNodeVmStatus, 8 | ClusterRequest, LogEntry 9 | } from "@/api/model"; 10 | import axios from "axios"; 11 | 12 | export namespace clusters { 13 | export function getClusters(): Promise { 14 | return axios.get("/api/v1/clusters").then(e => e.data); 15 | } 16 | 17 | export function getCluster(name: string): Promise { 18 | return axios.get(`/api/v1/clusters/${name}`).then(e => e.data); 19 | } 20 | 21 | export function getClusterNodes(name: string): Promise { 22 | return axios.get(`/api/v1/clusters/${name}/nodes`).then(e => e.data); 23 | } 24 | 25 | export function deleteCluster(name: string): Promise { 26 | return axios.delete(`/api/v1/clusters/${name}`).then(e => e.data); 27 | } 28 | 29 | export function deleteNodeFromCluster(clusterName: string, nodeName: string): Promise { 30 | return axios.delete(`/api/v1/clusters/${clusterName}/nodes/${nodeName}`).then(e => e.data); 31 | } 32 | 33 | export function changeNodeResources(clusterName: string, nodeName: string, cores: number, memory: number): Promise { 34 | return axios.put(`/api/v1/clusters/${clusterName}/nodes/${nodeName}/resources`, { 35 | cores, 36 | memory 37 | } as ChangeNodeResourcesRequest).then(e => e.data); 38 | } 39 | 40 | export function createCluster(request: ClusterRequest): Promise { 41 | return axios.post("/api/v1/clusters", request); 42 | } 43 | 44 | export function addNodeToCluster(clusterName: string, request: ClusterNode): Promise { 45 | return axios.post(`/api/v1/clusters/${clusterName}/nodes`, request).then(e => e.data); 46 | } 47 | 48 | export function generateDefaultClusterConfiguration(): Promise { 49 | return axios.get("/api/v1/clusters/generate").then(e => e.data); 50 | } 51 | 52 | export function logsForCluster(name: string): Promise { 53 | return axios.get(`/api/v1/clusters/${name}/logs`).then(e => e.data); 54 | } 55 | export function clearLogsForCluster(name: string): Promise { 56 | return axios.delete(`/api/v1/clusters/${name}/logs`).then(e => e.data); 57 | } 58 | 59 | export function clusterNodeVmsStatus(clusterName: string): Promise { 60 | return axios.get(`/api/v1/clusters/${clusterName}/status/vms`).then(e => e.data); 61 | } 62 | 63 | export function clusterKubeStatus(clusterName: string): Promise { 64 | return axios.get(`/api/v1/clusters/${clusterName}/status/kube`).then(e => e.data); 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /web/src-web/src/api/networks.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { AvailableNetwork } from "@/api/model"; 3 | 4 | export namespace networks { 5 | export function bridges(node: string): Promise { 6 | return axios.get(`/api/v1/nodes/${node}/networks/bridges`).then(e => e.data) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /web/src-web/src/api/nodes.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export namespace nodes { 4 | export function nodes(): Promise { 5 | return axios.get("/api/v1/nodes").then(r => r.data) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /web/src-web/src/api/settings.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import {AvailableKubeVersion, AvailableOsImage} from "@/api/model"; 3 | 4 | export namespace settings { 5 | export function os_images() { 6 | return axios.get("/api/v1/os-images").then(r => r.data); 7 | } 8 | 9 | export function kube_versions() { 10 | return axios.get("/api/v1/kube-versions").then(r => r.data); 11 | } 12 | } -------------------------------------------------------------------------------- /web/src-web/src/api/storage.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { AvailableStorage } from "@/api/model"; 3 | 4 | export namespace storage { 5 | export enum StorageContentType { 6 | Iso = "iso", 7 | Images = "images", 8 | Rootdir = "rootdir", 9 | Vztmpl = "vztmpl", 10 | Backup = "backup", 11 | Snippets = "snippets" 12 | } 13 | 14 | export function storage(node: string, storageContentType: StorageContentType): Promise { 15 | return axios.get(`/api/v1/nodes/${node}/storage/${storageContentType}`).then(r => r.data); 16 | } 17 | } -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/Poppins/Poppins-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/assets/fonts/Poppins/Poppins-Black.ttf -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/Poppins/Poppins-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/assets/fonts/Poppins/Poppins-BlackItalic.ttf -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/Poppins/Poppins-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/assets/fonts/Poppins/Poppins-Bold.ttf -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/Poppins/Poppins-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/assets/fonts/Poppins/Poppins-BoldItalic.ttf -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/Poppins/Poppins-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/assets/fonts/Poppins/Poppins-ExtraBold.ttf -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/Poppins/Poppins-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/assets/fonts/Poppins/Poppins-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/Poppins/Poppins-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/assets/fonts/Poppins/Poppins-ExtraLight.ttf -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/Poppins/Poppins-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/assets/fonts/Poppins/Poppins-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/Poppins/Poppins-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/assets/fonts/Poppins/Poppins-Italic.ttf -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/Poppins/Poppins-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/assets/fonts/Poppins/Poppins-Light.ttf -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/Poppins/Poppins-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/assets/fonts/Poppins/Poppins-LightItalic.ttf -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/Poppins/Poppins-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/assets/fonts/Poppins/Poppins-Medium.ttf -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/Poppins/Poppins-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/assets/fonts/Poppins/Poppins-MediumItalic.ttf -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/Poppins/Poppins-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/assets/fonts/Poppins/Poppins-Regular.ttf -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/Poppins/Poppins-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/assets/fonts/Poppins/Poppins-SemiBold.ttf -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/Poppins/Poppins-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/assets/fonts/Poppins/Poppins-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/Poppins/Poppins-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/assets/fonts/Poppins/Poppins-Thin.ttf -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/Poppins/Poppins-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/assets/fonts/Poppins/Poppins-ThinItalic.ttf -------------------------------------------------------------------------------- /web/src-web/src/assets/fonts/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Poppins'; 3 | font-weight: 100; 4 | src: local('Poppins'), url(Poppins/Poppins-Thin.ttf) format('truetype'); 5 | } 6 | 7 | @font-face { 8 | font-family: 'Poppins'; 9 | font-weight: 200; 10 | src: local('Poppins'), url(Poppins/Poppins-ExtraLight.ttf) format('truetype'); 11 | } 12 | 13 | @font-face { 14 | font-family: 'Poppins'; 15 | font-weight: 300; 16 | src: local('Poppins'), url(Poppins/Poppins-Light.ttf) format('truetype'); 17 | } 18 | 19 | @font-face { 20 | font-family: 'Poppins'; 21 | font-weight: 400; 22 | src: local('Poppins'), url(Poppins/Poppins-Regular.ttf) format('truetype'); 23 | } 24 | 25 | @font-face { 26 | font-family: 'Poppins'; 27 | font-weight: 500; 28 | src: local('Poppins'), url(Poppins/Poppins-Medium.ttf) format('truetype'); 29 | } 30 | 31 | @font-face { 32 | font-family: 'Poppins'; 33 | font-weight: 600; 34 | src: local('Poppins'), url(Poppins/Poppins-SemiBold.ttf) format('truetype'); 35 | } 36 | 37 | @font-face { 38 | font-family: 'Poppins'; 39 | font-weight: 700; 40 | src: local('Poppins'), url(Poppins/Poppins-Bold.ttf) format('truetype'); 41 | } 42 | 43 | @font-face { 44 | font-family: 'Poppins'; 45 | font-weight: 800; 46 | src: local('Poppins'), url(Poppins/Poppins-ExtraBold.ttf) format('truetype'); 47 | } 48 | 49 | @font-face { 50 | font-family: 'Poppins'; 51 | font-weight: 900; 52 | src: local('Poppins'), url(Poppins/Poppins-Black.ttf) format('truetype'); 53 | } 54 | -------------------------------------------------------------------------------- /web/src-web/src/components/Content.tsx: -------------------------------------------------------------------------------- 1 | import {Header, Props as HeaderProps} from "@/components/Header"; 2 | import React, {PropsWithChildren} from "react"; 3 | 4 | export type Props = { 5 | header?: HeaderProps 6 | className?: string 7 | } & PropsWithChildren 8 | const Content = (props: Props) => { 9 | return
10 | { 11 | props.header 12 | ?
13 | : null 14 | } 15 |
16 | {props.children} 17 |
18 |
19 | } 20 | export default Content; -------------------------------------------------------------------------------- /web/src-web/src/components/ErrorPanel/ErrorPanel.css: -------------------------------------------------------------------------------- 1 | .error-panel { 2 | background: #FFCDD2; 3 | border: 1px solid #e60017; 4 | border-radius: 5px; 5 | color: #73000c; 6 | margin: 10px; 7 | } -------------------------------------------------------------------------------- /web/src-web/src/components/ErrorPanel/ErrorPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { observer } from "mobx-react-lite"; 3 | import "./ErrorPanel.css" 4 | import { ISSUE_TRACKER_URL } from "@/constants"; 5 | import { Button } from "primereact/button"; 6 | import applicationStore from "@/store/application-store"; 7 | 8 | const ErrorPanel = () => { 9 | const renderError = () => { 10 | return applicationStore.error 11 | } 12 | 13 | const openIssueTracker = () => { 14 | window.open(ISSUE_TRACKER_URL, '_blank', 'noreferrer'); 15 | } 16 | 17 | if (applicationStore.isError()) { 18 | return ( 19 |
20 |
21 |
Application error:
22 |
{renderError()}
23 |
24 |
Please create issue on application GitHub project. 26 |
27 |
28 |
29 |
30 |
35 |
36 | ); 37 | } else { 38 | return null 39 | } 40 | }; 41 | export default observer(ErrorPanel); -------------------------------------------------------------------------------- /web/src-web/src/components/FormError.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type Props = { 4 | error: string | undefined, 5 | touched: boolean | undefined 6 | } 7 | 8 | const FormError = (props: Props) => { 9 | return ( 10 |
11 | {props.error && props.touched &&
{props.error}
} 12 |
13 | ); 14 | }; 15 | 16 | export default FormError; -------------------------------------------------------------------------------- /web/src-web/src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import {Button} from "primereact/button"; 2 | import React, {useState} from "react"; 3 | import {apiCall} from "@/utils/api"; 4 | import api from "@/api/api"; 5 | import {useNavigate} from "react-router-dom"; 6 | import {useOnFirstMount} from "@/utils/hooks"; 7 | 8 | export type Props = { 9 | title: string; 10 | titlePrefix?: string 11 | content?: React.ReactNode 12 | } 13 | const header = (props: Props) => { 14 | 15 | const navigate = useNavigate(); 16 | const [hostIp, setHostIp] = useState(""); 17 | 18 | // TODO: move this to global place 19 | useOnFirstMount(async () => { 20 | setHostIp(await api.auth.getLoggedInHostIp()); 21 | }); 22 | const onLogoutHandler = async () => { 23 | await apiCall(() => api.auth.logout()); 24 | navigate("/login") 25 | } 26 | return
27 |
28 |
29 | { 30 | props.titlePrefix 31 | ?
{props.titlePrefix}
32 | : null 33 | } 34 |
35 | {props.title} 36 |
37 |
38 | {props.content 39 | ?
40 | {props.content} 41 |
42 | : null 43 | } 44 |
45 |
46 |

47 |

{hostIp}
48 |
52 |
; 53 | } 54 | 55 | export default header; 56 | export const Header = header; -------------------------------------------------------------------------------- /web/src-web/src/components/HiddenPassword.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import {Button} from "primereact/button"; 3 | 4 | type Props = { 5 | password: string 6 | } 7 | const HiddenPassword = (props: Props) => { 8 | const [show, setShow] = useState(false) 9 | 10 | if (show) { 11 | return {props.password} 12 | } else { 13 | 14 | return { 17 | setShow(true) 18 | }}> 19 | show 20 | 21 | } 22 | }; 23 | 24 | export default HiddenPassword; -------------------------------------------------------------------------------- /web/src-web/src/components/LogoContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import logo from "../assets/images/makonn_logo_wh.svg"; 3 | 4 | const LogoContainer = (props: React.PropsWithChildren) => { 5 | return ( 6 |
7 |
8 |
9 |
10 |
11 |
12 | Makoon 13 |
v{APP_VERSION}
14 |
15 |
16 |
Kubernetes Cluster Management for Proxmox VE
17 |
18 |
19 |
20 | 21 | Makoon logo 22 |
23 |
24 | {props.children} 25 |
26 |
27 |
28 | ); 29 | }; 30 | 31 | export default LogoContainer; -------------------------------------------------------------------------------- /web/src-web/src/components/MainContainer.tsx: -------------------------------------------------------------------------------- 1 | import MainMenu from "@/components/MainMenu"; 2 | import React, {PropsWithChildren} from "react"; 3 | import Content from "@/components/Content"; 4 | import {Props as HeaderProps }from "@/components/Header"; 5 | 6 | export type Props = { 7 | header?: HeaderProps 8 | } &PropsWithChildren; 9 | const MainContainer = (props: Props)=>{ 10 | return
11 | 12 | 13 | {props.children} 14 | 15 |
16 | } 17 | 18 | export default MainContainer; 19 | -------------------------------------------------------------------------------- /web/src-web/src/components/MainMenu.tsx: -------------------------------------------------------------------------------- 1 | import logo from "../assets/images/makonn_logo_wh.svg"; 2 | import {Link, NavLink} from "react-router-dom"; 3 | import {useState} from "react"; 4 | import {useOnFirstMount} from "@/utils/hooks"; 5 | import api from "@/api/api"; 6 | 7 | 8 | type MenuItemProps = { 9 | active?: boolean 10 | name: string 11 | icon: string 12 | to: string 13 | } 14 | const MenuItem = (props: MenuItemProps) => { 15 | return props.active 16 | ? 17 | 18 |
{props.name}
19 |
20 | 21 | : 22 | 23 |
{props.name}
24 |
25 | } 26 | 27 | const MainMenu = () => { 28 | const [hostIp, setHostIp] = useState(""); 29 | 30 | // TODO: move this to global place 31 | useOnFirstMount(async () => { 32 | setHostIp(await api.auth.getLoggedInHostIp()); 33 | }); 34 | 35 | return
36 |
37 | 38 |
Makoon
39 |
40 |
41 | 42 | {/**/} 43 | {/**/} 44 | {/**/} 45 | 46 | 54 |
55 |
56 | v{APP_VERSION} 57 |
58 |
59 | } 60 | 61 | export default MainMenu; 62 | -------------------------------------------------------------------------------- /web/src-web/src/components/Panel.tsx: -------------------------------------------------------------------------------- 1 | import React, {PropsWithChildren} from "react"; 2 | 3 | export type Props = { 4 | className?: string; 5 | title?: string | React.ReactNode; 6 | icon?: string; 7 | } & PropsWithChildren; 8 | const Panel = (props: Props) => { 9 | return
10 | { 11 | props.title && 12 |
13 | {props.icon 14 | ? 15 | :
16 | } 17 |
{props.title}
18 |
19 | } 20 |
{props.children}
21 |
; 22 | } 23 | 24 | const TitleContainer = (props: { value: string } & PropsWithChildren) => { 25 | return
26 | {props.value} 27 | {props.children} 28 |
; 29 | } 30 | 31 | Panel.Title = TitleContainer; 32 | export default Panel; -------------------------------------------------------------------------------- /web/src-web/src/components/Section.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | type Props = { 4 | title?: string | React.ReactNode 5 | titleContainerClass?: string 6 | } & React.PropsWithChildren 7 | 8 | const Section = (props: Props) => { 9 | return
10 |
11 | {props.title} 12 |
13 |
14 | {props.children} 15 |
16 |
17 | } 18 | 19 | export default Section -------------------------------------------------------------------------------- /web/src-web/src/components/StorageDropdownOption.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { AvailableStorage } from "@/api/model"; 3 | import { toHumanReadableSize } from "@/utils/size"; 4 | 5 | const StorageDropdownOption = (option: AvailableStorage) => { 6 | return
7 |
8 | {option.storage} 9 |
10 |
11 |
12 |
13 | Available: 14 |
15 |
16 | {toHumanReadableSize(option.avail)} 17 |
18 |
19 |
20 |
21 | Used: 22 |
23 |
24 | {toHumanReadableSize(option.used)} 25 |
26 |
27 |
28 |
29 | Total: 30 |
31 |
32 | {toHumanReadableSize(option.total)} 33 |
34 |
35 |
36 |
37 | }; 38 | 39 | export default StorageDropdownOption; -------------------------------------------------------------------------------- /web/src-web/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const ISSUE_TRACKER_URL = "https://github.com/dsieradzki/makoon/issues" 2 | -------------------------------------------------------------------------------- /web/src-web/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {createRoot} from 'react-dom/client' 3 | import './style.css' 4 | import App from './App' 5 | import {createHashRouter, RouterProvider} from 'react-router-dom' 6 | import axios from "axios"; 7 | 8 | const container = document.getElementById('root') 9 | 10 | const root = createRoot(container!) 11 | 12 | export const router = createHashRouter( 13 | [ 14 | // match everything with "*" 15 | {path: "*", element: } 16 | ], 17 | { 18 | basename: "/" 19 | }) 20 | axios.interceptors.response.use(response => response, error => { 21 | if (error?.response?.status === 401) { 22 | console.warn("Unauthenticated request. Redirect to login page"); 23 | router.navigate("/login") 24 | .then(() => { 25 | console.warn("Redirected after unauthenticated request") 26 | }) 27 | } 28 | return Promise.reject(error); 29 | }); 30 | 31 | 32 | root.render( 33 | 34 | 35 | 36 | ) 37 | -------------------------------------------------------------------------------- /web/src-web/src/store/application-store.ts: -------------------------------------------------------------------------------- 1 | import { makeAutoObservable } from "mobx"; 2 | 3 | 4 | export class ApplicationStore { 5 | error: string = ""; 6 | 7 | constructor() { 8 | makeAutoObservable(this) 9 | } 10 | 11 | throwError(message: string) { 12 | this.error = message 13 | } 14 | 15 | clearError() { 16 | this.error = "" 17 | } 18 | 19 | isError(): boolean { 20 | return this.error.length > 0 21 | } 22 | } 23 | 24 | const applicationStore = new ApplicationStore() 25 | export default applicationStore -------------------------------------------------------------------------------- /web/src-web/src/store/clusters-list-store.ts: -------------------------------------------------------------------------------- 1 | import { makeAutoObservable, runInAction } from "mobx"; 2 | import { apiCall } from "@/utils/api"; 3 | import { ClusterHeader } from "@/api/model"; 4 | import api from "@/api/api"; 5 | 6 | export const LOADING_INDICATOR_LOAD_CLUSTERS = "LOADING_INDICATOR_LOAD_CLUSTERS" 7 | 8 | class ClustersListStore { 9 | clusters: ClusterHeader[] = [] 10 | 11 | constructor() { 12 | makeAutoObservable(this) 13 | } 14 | 15 | async loadClusters() { 16 | const clusters = await apiCall(() => api.clusters.getClusters(), LOADING_INDICATOR_LOAD_CLUSTERS) 17 | runInAction(() => { 18 | this.clusters = clusters 19 | }) 20 | } 21 | 22 | get cpuSum() { 23 | return this.clusters.reduce((acc, i) => acc + i.coresSum, 0) 24 | } 25 | 26 | get ramSum() { 27 | return this.clusters.reduce((acc, i) => acc + i.memorySum, 0) 28 | } 29 | 30 | get disksSizeSum() { 31 | return this.clusters.reduce((acc, i) => acc + i.diskSizeSum, 0) 32 | } 33 | } 34 | 35 | 36 | const clustersListStore = new ClustersListStore() 37 | 38 | export default clustersListStore -------------------------------------------------------------------------------- /web/src-web/src/store/processing-indicator-store.ts: -------------------------------------------------------------------------------- 1 | import { makeAutoObservable } from "mobx"; 2 | 3 | export class ProcessingIndicatorStore { 4 | loaders: { [key: string]: boolean } = {} 5 | 6 | constructor() { 7 | makeAutoObservable(this) 8 | } 9 | 10 | startProcessing(key: string) { 11 | this.loaders[key] = true 12 | } 13 | 14 | stopProcessing(key: string) { 15 | delete this.loaders[key] 16 | } 17 | 18 | status(key: string): boolean { 19 | return this.loaders[key] 20 | } 21 | } 22 | 23 | 24 | const processingIndicatorStore = new ProcessingIndicatorStore() 25 | 26 | export const wrapWithProcessingIndicator = async (key: string, fn: () => Promise): Promise => { 27 | console.debug("API CALL [%s] START", key); 28 | processingIndicatorStore.startProcessing(key) 29 | try { 30 | return await fn() 31 | } finally { 32 | processingIndicatorStore.stopProcessing(key) 33 | console.debug("API CALL [%s] STOP", key); 34 | } 35 | } 36 | 37 | export default processingIndicatorStore -------------------------------------------------------------------------------- /web/src-web/src/style.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | 6 | 7 | .editor-border { 8 | border: 1px solid #c8c6c4; 9 | } 10 | 11 | .editor-border:has(.focused) { 12 | border: 1px solid #64B5F6; 13 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/_colors.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | @if variable-exists(colors) { 3 | @each $name, $color in $colors { 4 | @for $i from 0 through 5 { 5 | @if ($i == 0) { 6 | --#{$name}-50:#{tint($color, (5 - $i) * 19%)}; 7 | } 8 | @else { 9 | --#{$name}-#{$i * 100}:#{tint($color, (5 - $i) * 19%)}; 10 | } 11 | } 12 | 13 | @for $i from 1 through 4 { 14 | --#{$name}-#{($i + 5) * 100}:#{shade($color, $i * 15%)}; 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/_common.scss: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | .p-component { 6 | font-family: $fontFamily; 7 | font-size: $fontSize; 8 | font-weight: $fontWeight; 9 | } 10 | 11 | .p-component-overlay { 12 | background-color: $maskBg; 13 | transition-duration: $transitionDuration; 14 | } 15 | 16 | .p-disabled, .p-component:disabled { 17 | opacity: $disabledOpacity; 18 | } 19 | 20 | .p-error { 21 | color: $errorColor; 22 | } 23 | 24 | .p-text-secondary { 25 | color: $textSecondaryColor; 26 | } 27 | 28 | .pi { 29 | font-size: $primeIconFontSize; 30 | } 31 | 32 | .p-icon { 33 | width: $primeIconFontSize; 34 | height: $primeIconFontSize; 35 | } 36 | 37 | .p-link { 38 | font-size: $fontSize; 39 | font-family: $fontFamily; 40 | border-radius: $borderRadius; 41 | 42 | &:focus { 43 | @include focused(); 44 | } 45 | } 46 | 47 | .p-component-overlay-enter { 48 | animation: p-component-overlay-enter-animation 150ms forwards; 49 | } 50 | 51 | .p-component-overlay-leave { 52 | animation: p-component-overlay-leave-animation 150ms forwards; 53 | } 54 | 55 | .p-component-overlay { 56 | @keyframes p-component-overlay-enter-animation { 57 | from { 58 | background-color: transparent; 59 | } 60 | to { 61 | background-color: var(--maskbg); 62 | } 63 | } 64 | 65 | @keyframes p-component-overlay-leave-animation { 66 | from { 67 | background-color: var(--maskbg); 68 | } 69 | to { 70 | background-color: transparent; 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/button/_speeddial.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:math'; 2 | 3 | .p-speeddial-button { 4 | &.p-button.p-button-icon-only { 5 | width: $speedDialButtonWidth; 6 | height: $speedDialButtonHeight; 7 | 8 | .p-button-icon { 9 | font-size: $speedDialButtonIconFontSize; 10 | 11 | &.p-icon { 12 | width: $speedDialButtonIconFontSize; 13 | height: $speedDialButtonIconFontSize; 14 | } 15 | } 16 | } 17 | } 18 | 19 | .p-speeddial-action { 20 | width: $speedDialActionWidth; 21 | height: $speedDialActionHeight; 22 | background: $speedDialActionBg; 23 | color: $speedDialActionTextColor; 24 | 25 | &:hover { 26 | background: $speedDialActionHoverBg; 27 | color: $speedDialActionTextHoverColor; 28 | } 29 | } 30 | 31 | .p-speeddial-direction-up { 32 | .p-speeddial-item { 33 | margin: math.div($inlineSpacing, 2); 34 | 35 | &:first-child { 36 | margin-bottom: $inlineSpacing; 37 | } 38 | } 39 | } 40 | 41 | .p-speeddial-direction-down { 42 | .p-speeddial-item { 43 | margin: math.div($inlineSpacing, 2); 44 | 45 | &:first-child { 46 | margin-top: $inlineSpacing; 47 | } 48 | } 49 | } 50 | 51 | .p-speeddial-direction-left { 52 | .p-speeddial-item { 53 | margin: 0 math.div($inlineSpacing, 2); 54 | 55 | &:first-child { 56 | margin-right: $inlineSpacing; 57 | } 58 | } 59 | } 60 | 61 | .p-speeddial-direction-right { 62 | .p-speeddial-item { 63 | margin: 0 math.div($inlineSpacing, 2); 64 | 65 | &:first-child { 66 | margin-left: $inlineSpacing; 67 | } 68 | } 69 | } 70 | 71 | .p-speeddial-circle, 72 | .p-speeddial-semi-circle, 73 | .p-speeddial-quarter-circle { 74 | .p-speeddial-item { 75 | margin: 0; 76 | 77 | &:first-child, 78 | &:last-child { 79 | margin: 0; 80 | } 81 | } 82 | } 83 | 84 | .p-speeddial-mask { 85 | background-color: $maskBg; 86 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/data/_carousel.scss: -------------------------------------------------------------------------------- 1 | .p-carousel { 2 | .p-carousel-content { 3 | .p-carousel-prev, 4 | .p-carousel-next { 5 | @include action-icon(); 6 | margin: $inlineSpacing; 7 | } 8 | } 9 | 10 | .p-carousel-indicators { 11 | padding: $carouselIndicatorsPadding; 12 | 13 | .p-carousel-indicator { 14 | margin-right: $inlineSpacing; 15 | margin-bottom: $inlineSpacing; 16 | 17 | button { 18 | background-color: $carouselIndicatorBg; 19 | width: $carouselIndicatorWidth; 20 | height: $carouselIndicatorHeight; 21 | transition: $actionIconTransition; 22 | border-radius: $carouselIndicatorBorderRadius; 23 | 24 | &:hover { 25 | background: $carouselIndicatorHoverBg; 26 | } 27 | } 28 | 29 | &.p-highlight { 30 | button { 31 | background: $highlightBg; 32 | color: $highlightTextColor; 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/data/_datascroller.scss: -------------------------------------------------------------------------------- 1 | .p-datascroller { 2 | .p-paginator-top { 3 | border-width: $tableTopPaginatorBorderWidth; 4 | border-radius: 0; 5 | } 6 | 7 | .p-paginator-bottom { 8 | border-width: $tableBottomPaginatorBorderWidth; 9 | border-radius: 0; 10 | } 11 | 12 | .p-datascroller-header { 13 | background: $tableHeaderBg; 14 | color: $tableHeaderTextColor; 15 | border: $tableHeaderBorder; 16 | border-width: $tableHeaderBorderWidth; 17 | padding: $tableHeaderPadding; 18 | font-weight: $tableHeaderFontWeight; 19 | } 20 | 21 | .p-datascroller-content { 22 | background: $tableBodyRowBg; 23 | color: $tableBodyRowTextColor; 24 | border: $dataViewContentBorder; 25 | padding: $dataViewContentPadding; 26 | } 27 | 28 | &.p-datascroller-inline { 29 | .p-datascroller-list { 30 | > li { 31 | border: $dataViewListItemBorder; 32 | border-width: $dataViewListItemBorderWidth; 33 | } 34 | } 35 | } 36 | 37 | .p-datascroller-footer { 38 | background: $tableFooterBg; 39 | color: $tableFooterTextColor; 40 | border: $tableFooterBorder; 41 | border-width: $tableFooterBorderWidth; 42 | padding: $tableFooterPadding; 43 | font-weight: $tableFooterFontWeight; 44 | border-bottom-left-radius: $borderRadius; 45 | border-bottom-right-radius: $borderRadius; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/data/_dataview.scss: -------------------------------------------------------------------------------- 1 | .p-dataview { 2 | .p-paginator-top { 3 | border-width: $tableTopPaginatorBorderWidth; 4 | border-radius: 0; 5 | } 6 | 7 | .p-paginator-bottom { 8 | border-width: $tableBottomPaginatorBorderWidth; 9 | border-radius: 0; 10 | } 11 | 12 | .p-dataview-header { 13 | background: $tableHeaderBg; 14 | color: $tableHeaderTextColor; 15 | border: $tableHeaderBorder; 16 | border-width: $tableHeaderBorderWidth; 17 | padding: $tableHeaderPadding; 18 | font-weight: $tableHeaderFontWeight; 19 | } 20 | 21 | .p-dataview-content { 22 | background: $tableBodyRowBg; 23 | color: $tableBodyRowTextColor; 24 | border: $dataViewContentBorder; 25 | padding: $dataViewContentPadding; 26 | } 27 | 28 | &.p-dataview-list { 29 | .p-dataview-content { 30 | > .p-grid > div { 31 | border: $dataViewListItemBorder; 32 | border-width: $dataViewListItemBorderWidth; 33 | } 34 | } 35 | } 36 | 37 | .p-dataview-footer { 38 | background: $tableFooterBg; 39 | color: $tableFooterTextColor; 40 | border: $tableFooterBorder; 41 | border-width: $tableFooterBorderWidth; 42 | padding: $tableFooterPadding; 43 | font-weight: $tableFooterFontWeight; 44 | border-bottom-left-radius: $borderRadius; 45 | border-bottom-right-radius: $borderRadius; 46 | } 47 | 48 | .p-dataview-loading-icon { 49 | font-size: $loadingIconFontSize; 50 | 51 | &.p-icon { 52 | width: $loadingIconFontSize; 53 | height: $loadingIconFontSize; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/data/_orderlist.scss: -------------------------------------------------------------------------------- 1 | .p-orderlist { 2 | .p-orderlist-controls { 3 | padding: $panelContentPadding; 4 | 5 | .p-button { 6 | margin-bottom: $inlineSpacing; 7 | } 8 | } 9 | 10 | .p-orderlist-header { 11 | background: $panelHeaderBg; 12 | color: $panelHeaderTextColor; 13 | border: $panelHeaderBorder; 14 | padding: $panelHeaderPadding; 15 | font-weight: $panelHeaderFontWeight; 16 | border-bottom: 0 none; 17 | border-top-right-radius: $borderRadius; 18 | border-top-left-radius: $borderRadius; 19 | } 20 | 21 | .p-orderlist-filter-container { 22 | padding: $panelHeaderPadding; 23 | background: $panelContentBg; 24 | border: $panelHeaderBorder; 25 | border-bottom: 0 none; 26 | 27 | .p-orderlist-filter-input { 28 | padding-right: nth($inputPadding, 2) + $primeIconFontSize; 29 | } 30 | 31 | .p-orderlist-filter-icon { 32 | right: nth($inputPadding, 2); 33 | color: $inputIconColor; 34 | } 35 | } 36 | 37 | .p-orderlist-list { 38 | border: $panelContentBorder; 39 | background: $panelContentBg; 40 | color: $panelContentTextColor; 41 | padding: $inputListPadding; 42 | border-bottom-right-radius: $borderRadius; 43 | border-bottom-left-radius: $borderRadius; 44 | 45 | .p-orderlist-item { 46 | padding: $inputListItemPadding; 47 | margin: $inputListItemMargin; 48 | border: $inputListItemBorder; 49 | color: $inputListItemTextColor; 50 | background: $inputListItemBg; 51 | transition: transform $transitionDuration, $listItemTransition; 52 | 53 | &:not(.p-highlight):hover { 54 | background: $inputListItemHoverBg; 55 | color: $inputListItemTextHoverColor; 56 | } 57 | 58 | &:focus { 59 | @include focused-listitem(); 60 | } 61 | 62 | &.p-highlight { 63 | color: $highlightTextColor; 64 | background: $highlightBg; 65 | } 66 | } 67 | } 68 | 69 | &.p-orderlist-striped { 70 | .p-orderlist-list { 71 | .p-orderlist-item:nth-child(even) { 72 | background: $panelContentEvenRowBg; 73 | 74 | &:hover { 75 | background: $inputListItemHoverBg; 76 | } 77 | } 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/data/_organizationchart.scss: -------------------------------------------------------------------------------- 1 | .p-organizationchart { 2 | .p-organizationchart-node-content { 3 | &.p-organizationchart-selectable-node:not(.p-highlight):hover { 4 | background: $inputListItemHoverBg; 5 | color: $inputListItemTextHoverColor; 6 | } 7 | 8 | &.p-highlight { 9 | background: $highlightBg; 10 | color: $highlightTextColor; 11 | 12 | .p-node-toggler { 13 | i { 14 | color: darken($highlightBg, 25%); 15 | } 16 | } 17 | } 18 | } 19 | 20 | .p-organizationchart-line-down { 21 | background: $organizationChartConnectorColor; 22 | } 23 | 24 | .p-organizationchart-line-left { 25 | border-right: $panelContentBorder; 26 | border-color: $organizationChartConnectorColor; 27 | } 28 | 29 | .p-organizationchart-line-top { 30 | border-top: $panelContentBorder; 31 | border-color: $organizationChartConnectorColor; 32 | } 33 | 34 | .p-organizationchart-node-content { 35 | border: $panelContentBorder; 36 | background: $panelContentBg; 37 | color: $panelContentTextColor; 38 | padding: $panelContentPadding; 39 | } 40 | 41 | .p-organizationchart-node-content .p-node-toggler { 42 | background: inherit; 43 | color: inherit; 44 | border-radius: 50%; 45 | 46 | &:focus { 47 | @include focused(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/data/_picklist.scss: -------------------------------------------------------------------------------- 1 | .p-picklist { 2 | .p-picklist-buttons { 3 | padding: $panelContentPadding; 4 | 5 | .p-button { 6 | margin-bottom: $inlineSpacing; 7 | } 8 | } 9 | 10 | .p-picklist-header { 11 | background: $panelHeaderBg; 12 | color: $panelHeaderTextColor; 13 | border: $panelHeaderBorder; 14 | padding: $panelHeaderPadding; 15 | font-weight: $panelHeaderFontWeight; 16 | border-bottom: 0 none; 17 | border-top-right-radius: $borderRadius; 18 | border-top-left-radius: $borderRadius; 19 | } 20 | 21 | .p-picklist-filter-container { 22 | padding: $panelHeaderPadding; 23 | background: $panelContentBg; 24 | border: $panelHeaderBorder; 25 | border-bottom: 0 none; 26 | 27 | .p-picklist-filter-input { 28 | padding-right: nth($inputPadding, 2) + $primeIconFontSize; 29 | } 30 | 31 | .p-picklist-filter-icon { 32 | right: nth($inputPadding, 2); 33 | color: $inputIconColor; 34 | } 35 | } 36 | 37 | .p-picklist-list { 38 | border: $panelContentBorder; 39 | background: $panelContentBg; 40 | color: $panelContentTextColor; 41 | padding: $inputListPadding; 42 | border-bottom-right-radius: $borderRadius; 43 | border-bottom-left-radius: $borderRadius; 44 | 45 | .p-picklist-item { 46 | padding: $inputListItemPadding; 47 | margin: $inputListItemMargin; 48 | border: $inputListItemBorder; 49 | color: $inputListItemTextColor; 50 | background: $inputListItemBg; 51 | transition: transform $transitionDuration, $listItemTransition; 52 | 53 | &:not(.p-highlight):hover { 54 | background: $inputListItemHoverBg; 55 | color: $inputListItemTextHoverColor; 56 | } 57 | 58 | &:focus { 59 | @include focused-listitem(); 60 | } 61 | 62 | &.p-highlight { 63 | color: $highlightTextColor; 64 | background: $highlightBg; 65 | } 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/data/_timeline.scss: -------------------------------------------------------------------------------- 1 | .p-timeline { 2 | .p-timeline-event-marker { 3 | border: $timelineEventMarkerBorder; 4 | border-radius: $timelineEventMarkerBorderRadius; 5 | width: $timelineEventMarkerWidth; 6 | height: $timelineEventMarkerHeight; 7 | background-color: $timelineEventMarkerBackground; 8 | } 9 | 10 | .p-timeline-event-connector { 11 | background-color: $timelineEventColor; 12 | } 13 | 14 | &.p-timeline-vertical { 15 | .p-timeline-event-opposite, 16 | .p-timeline-event-content { 17 | padding: $timelineVerticalEventContentPadding; 18 | } 19 | 20 | .p-timeline-event-connector { 21 | width: $timelineEventConnectorSize; 22 | } 23 | } 24 | 25 | &.p-timeline-horizontal { 26 | .p-timeline-event-opposite, 27 | .p-timeline-event-content { 28 | padding: $timelineHorizontalEventContentPadding; 29 | } 30 | 31 | .p-timeline-event-connector { 32 | height: $timelineEventConnectorSize; 33 | } 34 | } 35 | } 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/file/_fileupload.scss: -------------------------------------------------------------------------------- 1 | .p-fileupload { 2 | .p-fileupload-buttonbar { 3 | background: $panelHeaderBg; 4 | padding: $panelHeaderPadding; 5 | border: $panelHeaderBorder; 6 | color: $panelHeaderTextColor; 7 | border-bottom: 0 none; 8 | border-top-right-radius: $borderRadius; 9 | border-top-left-radius: $borderRadius; 10 | gap: $inlineSpacing; 11 | 12 | .p-button { 13 | margin-right: $inlineSpacing; 14 | } 15 | } 16 | 17 | .p-fileupload-content { 18 | background: $panelContentBg; 19 | padding: $fileUploadContentPadding; 20 | border: $panelContentBorder; 21 | color: $panelContentTextColor; 22 | border-bottom-right-radius: $borderRadius; 23 | border-bottom-left-radius: $borderRadius; 24 | } 25 | 26 | .p-progressbar { 27 | height: $fileUploadProgressBarHeight; 28 | } 29 | 30 | .p-fileupload-row { 31 | > div { 32 | padding: $tableBodyCellPadding; 33 | } 34 | } 35 | 36 | &.p-fileupload-advanced { 37 | .p-message { 38 | margin-top: 0; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/input/_checkbox.scss: -------------------------------------------------------------------------------- 1 | .p-checkbox { 2 | width: $checkboxWidth; 3 | height: $checkboxHeight; 4 | 5 | .p-checkbox-box { 6 | border: $checkboxBorder; 7 | background: $inputBg; 8 | width: $checkboxWidth; 9 | height: $checkboxHeight; 10 | color: $textColor; 11 | border-radius: $borderRadius; 12 | transition: $formElementTransition; 13 | 14 | .p-checkbox-icon { 15 | transition-duration: $transitionDuration; 16 | color: $checkboxIconActiveColor; 17 | font-size: $checkboxIconFontSize; 18 | 19 | &.p-icon { 20 | width: $checkboxIconFontSize; 21 | height: $checkboxIconFontSize; 22 | } 23 | } 24 | 25 | &.p-highlight { 26 | border-color: $checkboxActiveBorderColor; 27 | background: $checkboxActiveBg; 28 | 29 | &:not(.p-disabled):hover { 30 | border-color: $checkboxActiveHoverBorderColor; 31 | background: $checkboxActiveHoverBg; 32 | color: $checkboxIconActiveHoverColor; 33 | } 34 | } 35 | } 36 | 37 | &:not(.p-checkbox-disabled) { 38 | .p-checkbox-box { 39 | &:hover { 40 | border-color: $inputHoverBorderColor; 41 | } 42 | 43 | &.p-focus { 44 | @include focused-input(); 45 | } 46 | 47 | &.p-highlight:hover { 48 | border-color: $checkboxActiveHoverBorderColor; 49 | background: $checkboxActiveHoverBg; 50 | color: $checkboxIconActiveHoverColor; 51 | } 52 | } 53 | } 54 | 55 | &.p-invalid > .p-checkbox-box { 56 | @include invalid-input(); 57 | } 58 | } 59 | 60 | .p-input-filled { 61 | .p-checkbox { 62 | .p-checkbox-box { 63 | background-color: $inputFilledBg; 64 | 65 | &.p-highlight { 66 | background: $checkboxActiveBg; 67 | } 68 | } 69 | 70 | &:not(.p-checkbox-disabled) { 71 | .p-checkbox-box { 72 | &:hover { 73 | background-color: $inputFilledHoverBg; 74 | } 75 | 76 | &.p-highlight:hover { 77 | background: $checkboxActiveHoverBg; 78 | } 79 | } 80 | } 81 | } 82 | } 83 | 84 | @if ($highlightBg == $checkboxActiveBg) { 85 | .p-highlight { 86 | .p-checkbox { 87 | .p-checkbox-box { 88 | border-color: $checkboxIconActiveColor; 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/input/_chips.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:math'; 2 | 3 | .p-chips { 4 | .p-chips-multiple-container { 5 | padding: math.div(nth($inputPadding, 1), 2) nth($inputPadding, 2); 6 | 7 | &:not(.p-disabled):hover { 8 | border-color: $inputHoverBorderColor; 9 | } 10 | 11 | &:not(.p-disabled).p-focus { 12 | @include focused-input(); 13 | } 14 | 15 | .p-chips-token { 16 | padding: math.div(nth($inputPadding, 1), 2) nth($inputPadding, 2); 17 | margin-right: $inlineSpacing; 18 | background: $chipBg; 19 | color: $chipTextColor; 20 | border-radius: $chipBorderRadius; 21 | 22 | .p-chips-token-icon { 23 | margin-left: $inlineSpacing; 24 | } 25 | } 26 | 27 | .p-chips-input-token { 28 | padding: math.div(nth($inputPadding, 1), 2) 0; 29 | 30 | input { 31 | font-family: $fontFamily; 32 | font-size: $fontSize; 33 | color: $textColor; 34 | padding: 0; 35 | margin: 0; 36 | } 37 | } 38 | } 39 | 40 | &.p-invalid.p-component > .p-inputtext { 41 | @include invalid-input(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/input/_colorpicker.scss: -------------------------------------------------------------------------------- 1 | .p-colorpicker-preview { 2 | width: $colorPickerPreviewWidth; 3 | height: $colorPickerPreviewHeight; 4 | } 5 | 6 | .p-colorpicker-panel { 7 | background: $colorPickerBg; 8 | border: $colorPickerBorder; 9 | 10 | .p-colorpicker-color-handle, 11 | .p-colorpicker-hue-handle { 12 | border-color: $colorPickerHandleColor; 13 | } 14 | } 15 | 16 | .p-colorpicker-overlay-panel { 17 | box-shadow: $inputOverlayShadow; 18 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/input/_inputgroup.scss: -------------------------------------------------------------------------------- 1 | .p-inputgroup-addon { 2 | background: $inputGroupBg; 3 | color: $inputGroupTextColor; 4 | border-top: $inputBorder; 5 | border-left: $inputBorder; 6 | border-bottom: $inputBorder; 7 | padding: $inputPadding; 8 | min-width: $inputGroupAddOnMinWidth; 9 | 10 | &:last-child { 11 | border-right: $inputBorder; 12 | } 13 | } 14 | 15 | .p-inputgroup { 16 | > .p-component, 17 | > .p-inputwrapper > .p-inputtext, 18 | > .p-float-label > .p-component { 19 | border-radius: 0; 20 | margin: 0; 21 | 22 | + .p-inputgroup-addon { 23 | border-left: 0 none; 24 | } 25 | 26 | &:focus { 27 | z-index: 1; 28 | 29 | ~ label { 30 | z-index: 1; 31 | } 32 | } 33 | } 34 | } 35 | 36 | .p-inputgroup-addon:first-child, 37 | .p-inputgroup button:first-child, 38 | .p-inputgroup input:first-child, 39 | .p-inputgroup > .p-inputwrapper:first-child, 40 | .p-inputgroup > .p-inputwrapper:first-child > .p-inputtext { 41 | border-top-left-radius: $borderRadius; 42 | border-bottom-left-radius: $borderRadius; 43 | } 44 | 45 | .p-inputgroup .p-float-label:first-child input { 46 | border-top-left-radius: $borderRadius; 47 | border-bottom-left-radius: $borderRadius; 48 | } 49 | 50 | .p-inputgroup-addon:last-child, 51 | .p-inputgroup button:last-child, 52 | .p-inputgroup input:last-child, 53 | .p-inputgroup > .p-inputwrapper:last-child, 54 | .p-inputgroup > .p-inputwrapper:last-child > .p-inputtext { 55 | border-top-right-radius: $borderRadius; 56 | border-bottom-right-radius: $borderRadius; 57 | } 58 | 59 | .p-inputgroup .p-float-label:last-child input { 60 | border-top-right-radius: $borderRadius; 61 | border-bottom-right-radius: $borderRadius; 62 | } 63 | 64 | .p-fluid { 65 | .p-inputgroup { 66 | .p-button { 67 | width: auto; 68 | 69 | &.p-button-icon-only { 70 | width: $buttonIconOnlyWidth; 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/input/_inputnumber.scss: -------------------------------------------------------------------------------- 1 | .p-inputnumber { 2 | &.p-invalid.p-component > .p-inputtext { 3 | @include invalid-input(); 4 | } 5 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/input/_inputswitch.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:math'; 2 | 3 | .p-inputswitch { 4 | width: $inputSwitchWidth; 5 | height: $inputSwitchHeight; 6 | 7 | .p-inputswitch-slider { 8 | background: $inputSwitchSliderOffBg; 9 | transition: $formElementTransition; 10 | border-radius: $inputSwitchBorderRadius; 11 | 12 | &:before { 13 | background: $inputSwitchHandleOffBg; 14 | width: $inputSwitchHandleWidth; 15 | height: $inputSwitchHandleHeight; 16 | left: $inputSwitchSliderPadding; 17 | margin-top: math.div(-1 * $inputSwitchHandleHeight, 2); 18 | border-radius: $inputSwitchHandleBorderRadius; 19 | transition-duration: $transitionDuration; 20 | } 21 | } 22 | 23 | &.p-inputswitch-checked { 24 | .p-inputswitch-slider:before { 25 | transform: translateX($inputSwitchHandleWidth); 26 | } 27 | } 28 | 29 | &.p-focus { 30 | .p-inputswitch-slider { 31 | @include focused(); 32 | } 33 | } 34 | 35 | &:not(.p-disabled):hover { 36 | .p-inputswitch-slider { 37 | background: $inputSwitchSliderOffHoverBg; 38 | } 39 | } 40 | 41 | &.p-inputswitch-checked { 42 | .p-inputswitch-slider { 43 | background: $inputSwitchSliderOnBg; 44 | 45 | &:before { 46 | background: $inputSwitchHandleOnBg; 47 | } 48 | } 49 | 50 | &:not(.p-disabled):hover { 51 | .p-inputswitch-slider { 52 | background: $inputSwitchSliderOnHoverBg; 53 | } 54 | } 55 | } 56 | 57 | &.p-invalid { 58 | .p-inputswitch-slider { 59 | @include invalid-input(); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/input/_inputtext.scss: -------------------------------------------------------------------------------- 1 | .p-inputtext { 2 | font-family: $fontFamily; 3 | font-size: $inputTextFontSize; 4 | color: $inputTextColor; 5 | background: $inputBg; 6 | padding: $inputPadding; 7 | border: $inputBorder; 8 | transition: $formElementTransition; 9 | appearance: none; 10 | border-radius: $borderRadius; 11 | 12 | &:enabled:hover { 13 | border-color: $inputHoverBorderColor; 14 | } 15 | 16 | &:enabled:focus { 17 | @include focused-input(); 18 | } 19 | 20 | &.p-invalid.p-component { 21 | @include invalid-input(); 22 | } 23 | 24 | &.p-inputtext-sm { 25 | @include scaledFontSize($inputTextFontSize, $scaleSM); 26 | @include scaledPadding($inputPadding, $scaleSM); 27 | } 28 | 29 | &.p-inputtext-lg { 30 | @include scaledFontSize($inputTextFontSize, $scaleLG); 31 | @include scaledPadding($inputPadding, $scaleLG); 32 | } 33 | } 34 | 35 | .p-float-label > label { 36 | left: nth($inputPadding, 2); 37 | color: $inputPlaceholderTextColor; 38 | transition-duration: $transitionDuration; 39 | } 40 | 41 | .p-float-label > label.p-error { 42 | color: $errorColor; 43 | } 44 | 45 | .p-input-icon-left > i:first-of-type, 46 | .p-input-icon-left > svg:first-of-type, 47 | .p-input-icon-left > .p-input-prefix { 48 | left: nth($inputPadding, 2); 49 | color: $inputIconColor; 50 | } 51 | 52 | .p-input-icon-left > .p-inputtext { 53 | padding-left: nth($inputPadding, 2) * 2 + $primeIconFontSize; 54 | } 55 | 56 | .p-input-icon-left.p-float-label > label { 57 | left: nth($inputPadding, 2) * 2 + $primeIconFontSize; 58 | } 59 | 60 | .p-input-icon-right > i:last-of-type, 61 | .p-input-icon-right > svg:last-of-type, 62 | .p-input-icon-right > .p-input-suffix { 63 | right: nth($inputPadding, 2); 64 | color: $inputIconColor; 65 | } 66 | 67 | .p-input-icon-right > .p-inputtext { 68 | padding-right: nth($inputPadding, 2) * 2 + $primeIconFontSize; 69 | } 70 | 71 | @include placeholder { 72 | color: $inputPlaceholderTextColor 73 | }; 74 | 75 | .p-input-filled { 76 | .p-inputtext { 77 | background-color: $inputFilledBg; 78 | 79 | &:enabled:hover { 80 | background-color: $inputFilledHoverBg; 81 | } 82 | 83 | &:enabled:focus { 84 | background-color: $inputFilledFocusBg; 85 | } 86 | } 87 | } 88 | 89 | .p-inputtext-sm { 90 | .p-inputtext { 91 | @include scaledFontSize($inputTextFontSize, $scaleSM); 92 | @include scaledPadding($inputPadding, $scaleSM); 93 | } 94 | } 95 | 96 | .p-inputtext-lg { 97 | .p-inputtext { 98 | @include scaledFontSize($inputTextFontSize, $scaleLG); 99 | @include scaledPadding($inputPadding, $scaleLG); 100 | } 101 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/input/_listbox.scss: -------------------------------------------------------------------------------- 1 | .p-listbox { 2 | background: $inputListBg; 3 | color: $inputListTextColor; 4 | border: $inputListBorder; 5 | border-radius: $borderRadius; 6 | 7 | .p-listbox-header { 8 | padding: $inputListHeaderPadding; 9 | border-bottom: $inputListHeaderBorder; 10 | color: $inputListHeaderTextColor; 11 | background: $inputListHeaderBg; 12 | margin: $inputListHeaderMargin; 13 | border-top-right-radius: $borderRadius; 14 | border-top-left-radius: $borderRadius; 15 | 16 | .p-listbox-filter { 17 | padding-right: nth($inputPadding, 2) + $primeIconFontSize; 18 | } 19 | 20 | .p-listbox-filter-icon { 21 | right: nth($inputPadding, 2); 22 | color: $inputIconColor; 23 | } 24 | } 25 | 26 | .p-listbox-list { 27 | padding: $inputListPadding; 28 | 29 | .p-listbox-item { 30 | margin: $inputListItemMargin; 31 | padding: $inputListItemPadding; 32 | border: $inputListItemBorder; 33 | color: $inputListItemTextColor; 34 | transition: $listItemTransition; 35 | border-radius: $inputListItemBorderRadius; 36 | 37 | &.p-highlight { 38 | color: $highlightTextColor; 39 | background: $highlightBg; 40 | } 41 | 42 | &:focus { 43 | @include focused-listitem(); 44 | } 45 | } 46 | 47 | .p-listbox-item-group { 48 | margin: $submenuHeaderMargin; 49 | padding: $submenuHeaderPadding; 50 | color: $submenuHeaderTextColor; 51 | background: $submenuHeaderBg; 52 | font-weight: $submenuHeaderFontWeight; 53 | } 54 | 55 | .p-listbox-empty-message { 56 | padding: $inputListItemPadding; 57 | color: $inputListItemTextColor; 58 | background: $inputListItemBg; 59 | } 60 | } 61 | 62 | &:not(.p-disabled) { 63 | .p-listbox-item { 64 | &:not(.p-highlight):not(.p-disabled):hover { 65 | color: $inputListItemTextHoverColor; 66 | background: $inputListItemHoverBg; 67 | } 68 | } 69 | } 70 | 71 | &.p-invalid { 72 | @include invalid-input(); 73 | } 74 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/input/_mention.scss: -------------------------------------------------------------------------------- 1 | .p-mention-panel { 2 | background: $inputOverlayBg; 3 | color: $inputListTextColor; 4 | border: $inputOverlayBorder; 5 | border-radius: $borderRadius; 6 | box-shadow: $inputOverlayShadow; 7 | 8 | .p-mention-items { 9 | padding: $inputListPadding; 10 | 11 | .p-mention-item { 12 | margin: $inputListItemMargin; 13 | padding: $inputListItemPadding; 14 | border: $inputListItemBorder; 15 | color: $inputListItemTextColor; 16 | background: $inputListItemBg; 17 | transition: $listItemTransition; 18 | border-radius: $inputListItemBorderRadius; 19 | 20 | &:hover { 21 | color: $inputListItemTextHoverColor; 22 | background: $inputListItemHoverBg; 23 | } 24 | 25 | &.p-highlight { 26 | color: $highlightTextColor; 27 | background: $highlightBg; 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/input/_password.scss: -------------------------------------------------------------------------------- 1 | .p-password { 2 | &.p-invalid.p-component > .p-inputtext { 3 | @include invalid-input(); 4 | } 5 | } 6 | 7 | .p-password-panel { 8 | padding: $panelContentPadding; 9 | background: $panelContentBg; 10 | color: $panelContentTextColor; 11 | border: $overlayContentBorder; 12 | box-shadow: $inputOverlayShadow; 13 | border-radius: $borderRadius; 14 | 15 | .p-password-meter { 16 | margin-bottom: $inlineSpacing; 17 | background: $passwordMeterBg; 18 | 19 | .p-password-strength { 20 | &.weak { 21 | background: $passwordWeakBg; 22 | } 23 | 24 | &.medium { 25 | background: $passwordMediumBg; 26 | } 27 | 28 | &.strong { 29 | background: $passwordStrongBg; 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/input/_radiobutton.scss: -------------------------------------------------------------------------------- 1 | .p-radiobutton { 2 | width: $radiobuttonWidth; 3 | height: $radiobuttonHeight; 4 | 5 | .p-radiobutton-box { 6 | border: $radiobuttonBorder; 7 | background: $inputBg; 8 | width: $radiobuttonWidth; 9 | height: $radiobuttonHeight; 10 | color: $textColor; 11 | border-radius: 50%; 12 | transition: $formElementTransition; 13 | 14 | &:not(.p-disabled):not(.p-highlight):hover { 15 | border-color: $inputHoverBorderColor; 16 | } 17 | 18 | &:not(.p-disabled).p-focus { 19 | @include focused-input(); 20 | } 21 | 22 | .p-radiobutton-icon { 23 | width: $radiobuttonIconSize; 24 | height: $radiobuttonIconSize; 25 | transition-duration: $transitionDuration; 26 | background-color: $radiobuttonIconActiveColor; 27 | } 28 | 29 | &.p-highlight { 30 | border-color: $radiobuttonActiveBorderColor; 31 | background: $radiobuttonActiveBg; 32 | 33 | &:not(.p-disabled):hover { 34 | border-color: $radiobuttonActiveHoverBorderColor; 35 | background: $radiobuttonActiveHoverBg; 36 | color: $radiobuttonIconActiveHoverColor; 37 | } 38 | } 39 | } 40 | 41 | &.p-invalid > .p-radiobutton-box { 42 | @include invalid-input(); 43 | } 44 | 45 | &:focus { 46 | outline: 0 none; 47 | } 48 | } 49 | 50 | .p-input-filled { 51 | .p-radiobutton { 52 | .p-radiobutton-box { 53 | background-color: $inputFilledBg; 54 | 55 | &:not(.p-disabled):hover { 56 | background-color: $inputFilledHoverBg; 57 | } 58 | 59 | &.p-highlight { 60 | background: $radiobuttonActiveBg; 61 | 62 | &:not(.p-disabled):hover { 63 | background: $radiobuttonActiveHoverBg; 64 | } 65 | } 66 | } 67 | } 68 | } 69 | 70 | @if ($highlightBg == $radiobuttonActiveBg) { 71 | .p-highlight { 72 | .p-radiobutton { 73 | .p-radiobutton-box { 74 | border-color: $radiobuttonIconActiveColor; 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/input/_rating.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:math'; 2 | 3 | .p-rating { 4 | gap: $inlineSpacing; 5 | 6 | .p-rating-item { 7 | .p-rating-icon { 8 | color: $ratingStarIconOffColor; 9 | transition: $formElementTransition; 10 | font-size: $ratingIconFontSize; 11 | 12 | &.p-icon { 13 | width: $ratingIconFontSize; 14 | height: $ratingIconFontSize; 15 | } 16 | 17 | &.p-rating-cancel { 18 | color: $ratingCancelIconColor; 19 | } 20 | } 21 | 22 | &:focus { 23 | @include focused(); 24 | } 25 | 26 | &.p-rating-item-active { 27 | .p-rating-icon { 28 | color: $ratingStarIconOnColor; 29 | } 30 | } 31 | } 32 | 33 | &:not(.p-disabled):not(.p-readonly) { 34 | .p-rating-item { 35 | &:hover { 36 | .p-rating-icon { 37 | color: $ratingStarIconHoverColor; 38 | 39 | &.p-rating-cancel { 40 | color: $ratingCancelIconHoverColor; 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | 48 | @if ($highlightBg == $ratingStarIconOnColor) { 49 | .p-highlight { 50 | .p-rating { 51 | .p-rating-item { 52 | &.p-rating-item-active { 53 | .p-rating-icon { 54 | color: $highlightTextColor; 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/input/_selectbutton.scss: -------------------------------------------------------------------------------- 1 | .p-selectbutton { 2 | .p-button { 3 | background: $toggleButtonBg; 4 | border: $toggleButtonBorder; 5 | color: $toggleButtonTextColor; 6 | transition: $formElementTransition; 7 | 8 | .p-button-icon-left, 9 | .p-button-icon-right { 10 | color: $toggleButtonIconColor; 11 | } 12 | 13 | &:not(.p-disabled):not(.p-highlight):hover { 14 | background: $toggleButtonHoverBg; 15 | border-color: $toggleButtonHoverBorderColor; 16 | color: $toggleButtonTextHoverColor; 17 | 18 | .p-button-icon-left, 19 | .p-button-icon-right { 20 | color: $toggleButtonIconHoverColor; 21 | } 22 | } 23 | 24 | &.p-highlight { 25 | background: $toggleButtonActiveBg; 26 | border-color: $toggleButtonActiveBorderColor; 27 | color: $toggleButtonTextActiveColor; 28 | 29 | .p-button-icon-left, 30 | .p-button-icon-right { 31 | color: $toggleButtonIconActiveColor; 32 | } 33 | 34 | &:hover { 35 | background: $toggleButtonActiveHoverBg; 36 | border-color: $toggleButtonActiveHoverBorderColor; 37 | color: $toggleButtonTextActiveHoverColor; 38 | 39 | .p-button-icon-left, 40 | .p-button-icon-right { 41 | color: $toggleButtonIconActiveHoverColor; 42 | } 43 | } 44 | } 45 | } 46 | 47 | &.p-invalid > .p-button { 48 | @include invalid-input(); 49 | } 50 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/input/_slider.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:math'; 2 | 3 | .p-slider { 4 | background: $sliderBg; 5 | border: $sliderBorder; 6 | border-radius: $borderRadius; 7 | 8 | &.p-slider-horizontal { 9 | height: $sliderHorizontalHeight; 10 | 11 | .p-slider-handle { 12 | margin-top: math.div(-1 * $sliderHandleHeight, 2); 13 | margin-left: math.div(-1 * $sliderHandleWidth, 2); 14 | } 15 | } 16 | 17 | &.p-slider-vertical { 18 | width: $sliderVerticalWidth; 19 | 20 | .p-slider-handle { 21 | margin-left: math.div(-1 * $sliderHandleWidth, 2); 22 | margin-bottom: math.div(-1 * $sliderHandleHeight, 2); 23 | } 24 | } 25 | 26 | .p-slider-handle { 27 | height: $sliderHandleHeight; 28 | width: $sliderHandleWidth; 29 | background: $sliderHandleBg; 30 | border: $sliderHandleBorder; 31 | border-radius: $sliderHandleBorderRadius; 32 | transition: $formElementTransition; 33 | 34 | &:focus { 35 | @include focused(); 36 | } 37 | } 38 | 39 | .p-slider-range { 40 | background: $sliderRangeBg; 41 | } 42 | 43 | &:not(.p-disabled) { 44 | .p-slider-handle:hover { 45 | background: $sliderHandleHoverBg; 46 | border-color: $sliderHandleHoverBorderColor; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/input/_togglebutton.scss: -------------------------------------------------------------------------------- 1 | .p-togglebutton.p-button { 2 | background: $toggleButtonBg; 3 | border: $toggleButtonBorder; 4 | color: $toggleButtonTextColor; 5 | transition: $formElementTransition; 6 | 7 | .p-button-icon-left, 8 | .p-button-icon-right { 9 | color: $toggleButtonIconColor; 10 | } 11 | 12 | &:not(.p-disabled):not(.p-highlight):hover { 13 | background: $toggleButtonHoverBg; 14 | border-color: $toggleButtonHoverBorderColor; 15 | color: $toggleButtonTextHoverColor; 16 | 17 | .p-button-icon-left, 18 | .p-button-icon-right { 19 | color: $toggleButtonIconHoverColor; 20 | } 21 | } 22 | 23 | &.p-highlight { 24 | background: $toggleButtonActiveBg; 25 | border-color: $toggleButtonActiveBorderColor; 26 | color: $toggleButtonTextActiveColor; 27 | 28 | .p-button-icon-left, 29 | .p-button-icon-right { 30 | color: $toggleButtonIconActiveColor; 31 | } 32 | 33 | &:hover { 34 | background: $toggleButtonActiveHoverBg; 35 | border-color: $toggleButtonActiveHoverBorderColor; 36 | color: $toggleButtonTextActiveHoverColor; 37 | 38 | .p-button-icon-left, 39 | .p-button-icon-right { 40 | color: $toggleButtonIconActiveHoverColor; 41 | } 42 | } 43 | } 44 | 45 | &.p-invalid > .p-button { 46 | @include invalid-input(); 47 | } 48 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/menu/_breadcrumb.scss: -------------------------------------------------------------------------------- 1 | .p-breadcrumb { 2 | background: $breadcrumbBg; 3 | border: $breadcrumbBorder; 4 | border-radius: $borderRadius; 5 | padding: $breadcrumbPadding; 6 | 7 | .p-breadcrumb-list { 8 | li { 9 | .p-menuitem-link { 10 | transition: $listItemTransition; 11 | border-radius: $borderRadius; 12 | 13 | &:focus { 14 | @include focused(); 15 | } 16 | 17 | .p-menuitem-text { 18 | color: $breadcrumbItemTextColor; 19 | } 20 | 21 | .p-menuitem-icon { 22 | color: $breadcrumbItemIconColor; 23 | } 24 | } 25 | 26 | &.p-menuitem-separator { 27 | margin: 0 $inlineSpacing 0 $inlineSpacing; 28 | color: $breadcrumbSeparatorColor; 29 | } 30 | 31 | &:last-child { 32 | .p-menuitem-text { 33 | color: $breadcrumbLastItemTextColor; 34 | } 35 | 36 | .p-menuitem-icon { 37 | color: $breadcrumbLastItemIconColor; 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/menu/_contextmenu.scss: -------------------------------------------------------------------------------- 1 | .p-contextmenu { 2 | padding: $verticalMenuPadding; 3 | background: $overlayMenuBg; 4 | color: $menuTextColor; 5 | border: $overlayMenuBorder; 6 | box-shadow: $overlayMenuShadow; 7 | border-radius: $borderRadius; 8 | width: $menuWidth; 9 | 10 | .p-menuitem-link { 11 | @include menuitem-link(); 12 | } 13 | 14 | .p-submenu-list { 15 | padding: $verticalMenuPadding; 16 | background: $overlayMenuBg; 17 | border: $overlayMenuBorder; 18 | box-shadow: $overlayMenuShadow; 19 | border-radius: $borderRadius; 20 | } 21 | 22 | .p-menuitem { 23 | &.p-menuitem-active { 24 | > .p-menuitem-link { 25 | background: $menuitemActiveBg; 26 | 27 | .p-menuitem-text { 28 | color: $menuitemTextActiveColor; 29 | } 30 | 31 | .p-menuitem-icon, .p-submenu-icon { 32 | color: $menuitemIconActiveColor; 33 | } 34 | } 35 | } 36 | } 37 | 38 | .p-menu-separator { 39 | border-top: $divider; 40 | margin: $menuSeparatorMargin; 41 | } 42 | 43 | .p-submenu-icon { 44 | font-size: $menuitemSubmenuIconFontSize; 45 | transition: transform $transitionDuration; 46 | 47 | &.p-icon { 48 | width: $menuitemSubmenuIconFontSize; 49 | height: $menuitemSubmenuIconFontSize; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/menu/_dock.scss: -------------------------------------------------------------------------------- 1 | .p-dock { 2 | .p-dock-list { 3 | background: $dockBg; 4 | border: $dockBorder; 5 | padding: $dockPadding; 6 | border-radius: $dockBorderRadius; 7 | } 8 | 9 | .p-dock-item { 10 | padding: $dockItemPadding; 11 | } 12 | 13 | .p-dock-action { 14 | width: $dockActionWidth; 15 | height: $dockActionHeight; 16 | } 17 | 18 | &.p-dock-magnification { 19 | &.p-dock-top, 20 | &.p-dock-bottom { 21 | .p-dock-item-second-prev, 22 | .p-dock-item-second-next { 23 | margin: 0 $dockSecondItemsMargin; 24 | } 25 | 26 | .p-dock-item-prev, 27 | .p-dock-item-next { 28 | margin: 0 $dockFirstItemsMargin; 29 | } 30 | 31 | .p-dock-item-current { 32 | margin: 0 $dockCurrentItemMargin; 33 | } 34 | } 35 | 36 | &.p-dock-left, 37 | &.p-dock-right { 38 | .p-dock-item-second-prev, 39 | .p-dock-item-second-next { 40 | margin: $dockSecondItemsMargin 0; 41 | } 42 | 43 | .p-dock-item-prev, 44 | .p-dock-item-next { 45 | margin: $dockFirstItemsMargin 0; 46 | } 47 | 48 | .p-dock-item-current { 49 | margin: $dockCurrentItemMargin 0; 50 | } 51 | } 52 | } 53 | } 54 | 55 | @media screen and (max-width: 960px) { 56 | .p-dock { 57 | &.p-dock-top, 58 | &.p-dock-bottom { 59 | .p-dock-container { 60 | overflow-x: auto; 61 | width: 100%; 62 | 63 | .p-dock-list { 64 | margin: 0 auto; 65 | } 66 | } 67 | } 68 | 69 | &.p-dock-left, 70 | &.p-dock-right { 71 | .p-dock-container { 72 | overflow-y: auto; 73 | height: 100%; 74 | 75 | .p-dock-list { 76 | margin: auto 0; 77 | } 78 | } 79 | } 80 | 81 | &.p-dock-magnification { 82 | &.p-dock-top, 83 | &.p-dock-bottom, 84 | &.p-dock-left, 85 | &.p-dock-right { 86 | .p-dock-item-second-prev, 87 | .p-dock-item-second-next, 88 | .p-dock-item-prev, 89 | .p-dock-item-next, 90 | .p-dock-item-current { 91 | transform: none; 92 | margin: 0; 93 | } 94 | } 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/menu/_menu.scss: -------------------------------------------------------------------------------- 1 | .p-menu { 2 | padding: $verticalMenuPadding; 3 | background: $menuBg; 4 | color: $menuTextColor; 5 | border: $menuBorder; 6 | border-radius: $borderRadius; 7 | width: $menuWidth; 8 | 9 | .p-menuitem-link { 10 | @include menuitem-link(); 11 | } 12 | 13 | &.p-menu-overlay { 14 | background: $overlayMenuBg; 15 | border: $overlayMenuBorder; 16 | box-shadow: $overlayMenuShadow; 17 | } 18 | 19 | .p-submenu-header { 20 | margin: $submenuHeaderMargin; 21 | padding: $submenuHeaderPadding; 22 | color: $submenuHeaderTextColor; 23 | background: $submenuHeaderBg; 24 | font-weight: $submenuHeaderFontWeight; 25 | border-top-right-radius: $submenuHeaderBorderRadius; 26 | border-top-left-radius: $submenuHeaderBorderRadius; 27 | } 28 | 29 | .p-menu-separator { 30 | border-top: $divider; 31 | margin: $menuSeparatorMargin; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/menu/_slidemenu.scss: -------------------------------------------------------------------------------- 1 | .p-slidemenu { 2 | padding: $verticalMenuPadding; 3 | background: $menuBg; 4 | color: $menuTextColor; 5 | border: $menuBorder; 6 | border-radius: $borderRadius; 7 | width: $menuWidth; 8 | 9 | .p-menuitem-link { 10 | @include menuitem-link(); 11 | } 12 | 13 | &.p-slidemenu-overlay { 14 | background: $overlayMenuBg; 15 | border: $overlayMenuBorder; 16 | box-shadow: $overlayMenuShadow; 17 | } 18 | 19 | .p-slidemenu-list { 20 | padding: $verticalMenuPadding; 21 | background: $overlayMenuBg; 22 | border: $overlayMenuBorder; 23 | box-shadow: $overlayMenuShadow; 24 | } 25 | 26 | .p-slidemenu { 27 | &.p-slidemenu-active { 28 | > .p-slidemenu-link { 29 | background: $menuitemActiveBg; 30 | 31 | .p-slidemenu-text { 32 | color: $menuitemTextActiveColor; 33 | } 34 | 35 | .p-slidemenu-icon, .p-slidemenu-icon { 36 | color: $menuitemIconActiveColor; 37 | } 38 | } 39 | } 40 | } 41 | 42 | .p-slidemenu-separator { 43 | border-top: $divider; 44 | margin: $menuSeparatorMargin; 45 | } 46 | 47 | .p-slidemenu-icon { 48 | font-size: $menuitemSubmenuIconFontSize; 49 | 50 | &.p-icon { 51 | width: $menuitemSubmenuIconFontSize; 52 | height: $menuitemSubmenuIconFontSize; 53 | } 54 | } 55 | 56 | .p-slidemenu-backward { 57 | padding: $menuitemPadding; 58 | color: $menuitemTextColor; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/menu/_steps.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:math'; 2 | 3 | .p-steps { 4 | 5 | .p-steps-item { 6 | .p-menuitem-link { 7 | background: transparent; 8 | transition: $listItemTransition; 9 | border-radius: $borderRadius; 10 | background: $stepsItemBg; 11 | 12 | .p-steps-number { 13 | color: $stepsItemNumberColor; 14 | border: $stepsItemBorder; 15 | background: $stepsItemBg; 16 | min-width: $stepsItemNumberWidth; 17 | height: $stepsItemNumberHeight; 18 | line-height: $stepsItemNumberHeight; 19 | font-size: $stepsItemNumberFontSize; 20 | z-index: 1; 21 | border-radius: $stepsItemNumberBorderRadius; 22 | } 23 | 24 | .p-steps-title { 25 | margin-top: $inlineSpacing; 26 | color: $stepsItemTextColor; 27 | } 28 | 29 | &:not(.p-disabled):focus { 30 | @include focused(); 31 | } 32 | } 33 | 34 | &.p-highlight { 35 | .p-steps-number { 36 | background: $highlightBg; 37 | color: $highlightTextColor; 38 | } 39 | 40 | .p-steps-title { 41 | font-weight: $stepsItemActiveFontWeight; 42 | color: $textColor; 43 | } 44 | } 45 | 46 | &:before { 47 | content:' '; 48 | border-top: $divider; 49 | width: 100%; 50 | top: 50%; 51 | left: 0; 52 | display: block; 53 | position: absolute; 54 | margin-top: math.div(-1 * $stepsItemNumberHeight, 2); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/menu/_tabmenu.scss: -------------------------------------------------------------------------------- 1 | .p-tabmenu { 2 | .p-tabmenu-nav { 3 | background: $tabviewNavBg; 4 | border: $tabviewNavBorder; 5 | border-width: $tabviewNavBorderWidth; 6 | 7 | .p-tabmenuitem { 8 | margin-right: $tabviewHeaderSpacing; 9 | 10 | .p-menuitem-link { 11 | border: $tabviewHeaderBorder; 12 | border-width: $tabviewHeaderBorderWidth; 13 | border-color: $tabviewHeaderBorderColor; 14 | background: $tabviewHeaderBg; 15 | color: $tabviewHeaderTextColor; 16 | padding: $tabviewHeaderPadding; 17 | font-weight: $tabviewHeaderFontWeight; 18 | border-top-right-radius: $borderRadius; 19 | border-top-left-radius: $borderRadius; 20 | transition: $listItemTransition; 21 | margin: $tabviewHeaderMargin; 22 | height: calc(100% + #{-1 * nth($tabviewHeaderMargin, 3)}); 23 | 24 | .p-menuitem-icon { 25 | margin-right: $inlineSpacing; 26 | } 27 | 28 | &:not(.p-disabled):focus { 29 | @include focused-inset(); 30 | } 31 | } 32 | 33 | &:not(.p-highlight):not(.p-disabled):hover { 34 | .p-menuitem-link { 35 | background: $tabviewHeaderHoverBg; 36 | border-color: $tabviewHeaderHoverBorderColor; 37 | color: $tabviewHeaderTextHoverColor; 38 | } 39 | } 40 | 41 | &.p-highlight { 42 | .p-menuitem-link { 43 | background: $tabviewHeaderActiveBg; 44 | border-color: $tabviewHeaderActiveBorderColor; 45 | color: $tabviewHeaderTextActiveColor; 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/menu/_tieredmenu.scss: -------------------------------------------------------------------------------- 1 | .p-tieredmenu { 2 | padding: $verticalMenuPadding; 3 | background: $menuBg; 4 | color: $menuTextColor; 5 | border: $menuBorder; 6 | border-radius: $borderRadius; 7 | width: $menuWidth; 8 | 9 | .p-menuitem-link { 10 | @include menuitem-link(); 11 | } 12 | 13 | &.p-tieredmenu-overlay { 14 | background: $overlayMenuBg; 15 | border: $overlayMenuBorder; 16 | box-shadow: $overlayMenuShadow; 17 | } 18 | 19 | .p-submenu-list { 20 | padding: $verticalMenuPadding; 21 | background: $overlayMenuBg; 22 | border: $overlayMenuBorder; 23 | box-shadow: $overlayMenuShadow; 24 | } 25 | 26 | .p-menuitem { 27 | &.p-menuitem-active { 28 | > .p-menuitem-link { 29 | background: $menuitemActiveBg; 30 | 31 | .p-menuitem-text { 32 | color: $menuitemTextActiveColor; 33 | } 34 | 35 | .p-menuitem-icon, .p-submenu-icon { 36 | color: $menuitemIconActiveColor; 37 | } 38 | } 39 | } 40 | } 41 | 42 | .p-menu-separator { 43 | border-top: $divider; 44 | margin: $menuSeparatorMargin; 45 | } 46 | 47 | .p-submenu-icon { 48 | font-size: $menuitemSubmenuIconFontSize; 49 | transition: transform $transitionDuration; 50 | 51 | &.p-icon { 52 | width: $menuitemSubmenuIconFontSize; 53 | height: $menuitemSubmenuIconFontSize; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/messages/_inlinemessage.scss: -------------------------------------------------------------------------------- 1 | .p-inline-message { 2 | padding: $inlineMessagePadding; 3 | margin: $inlineMessageMargin; 4 | border-radius: $borderRadius; 5 | 6 | &.p-inline-message-info { 7 | background: $infoMessageBg; 8 | border: $infoMessageBorder; 9 | border-width: $inlineMessageBorderWidth; 10 | color: $infoMessageTextColor; 11 | 12 | .p-inline-message-icon { 13 | color: $infoMessageIconColor; 14 | } 15 | } 16 | 17 | &.p-inline-message-success { 18 | background: $successMessageBg; 19 | border: $successMessageBorder; 20 | border-width: $inlineMessageBorderWidth; 21 | color: $successMessageTextColor; 22 | 23 | .p-inline-message-icon { 24 | color: $successMessageIconColor; 25 | } 26 | } 27 | 28 | &.p-inline-message-warn { 29 | background: $warningMessageBg; 30 | border: $warningMessageBorder; 31 | border-width: $inlineMessageBorderWidth; 32 | color: $warningMessageTextColor; 33 | 34 | .p-inline-message-icon { 35 | color: $warningMessageIconColor; 36 | } 37 | } 38 | 39 | &.p-inline-message-error { 40 | background: $errorMessageBg; 41 | border: $errorMessageBorder; 42 | border-width: $inlineMessageBorderWidth; 43 | color: $errorMessageTextColor; 44 | 45 | .p-inline-message-icon { 46 | color: $errorMessageIconColor; 47 | } 48 | } 49 | 50 | .p-inline-message-icon { 51 | font-size: $inlineMessageIconFontSize; 52 | margin-right: $inlineSpacing; 53 | 54 | &.p-icon { 55 | width: $inlineMessageIconFontSize; 56 | height: $inlineMessageIconFontSize; 57 | } 58 | } 59 | 60 | .p-inline-message-text { 61 | font-size: $inlineMessageTextFontSize; 62 | } 63 | 64 | &.p-inline-message-icon-only { 65 | .p-inline-message-icon { 66 | margin-right: 0; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/misc/_avatar.scss: -------------------------------------------------------------------------------- 1 | .p-avatar { 2 | background-color: $avatarBg; 3 | border-radius: $borderRadius; 4 | 5 | &.p-avatar-lg { 6 | width: 3rem; 7 | height: 3rem; 8 | font-size: 1.5rem; 9 | 10 | .p-avatar-icon { 11 | font-size: 1.5rem; 12 | } 13 | } 14 | 15 | &.p-avatar-xl { 16 | width: 4rem; 17 | height: 4rem; 18 | font-size: 2rem; 19 | 20 | .p-avatar-icon { 21 | font-size: 2rem; 22 | } 23 | } 24 | } 25 | 26 | .p-avatar-group { 27 | .p-avatar { 28 | border: 2px solid $panelContentBg; 29 | } 30 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/misc/_badge.scss: -------------------------------------------------------------------------------- 1 | .p-badge { 2 | background: $badgeBg; 3 | color: $badgeTextColor; 4 | font-size: $badgeFontSize; 5 | font-weight: $badgeFontWeight; 6 | min-width: $badgeMinWidth; 7 | height: $badgeHeight; 8 | line-height: $badgeHeight; 9 | 10 | &.p-badge-secondary { 11 | background-color: $secondaryButtonBg; 12 | color: $secondaryButtonTextColor; 13 | } 14 | 15 | &.p-badge-success { 16 | background-color: $successButtonBg; 17 | color: $successButtonTextColor; 18 | } 19 | 20 | &.p-badge-info { 21 | background-color: $infoButtonBg; 22 | color: $infoButtonTextColor; 23 | } 24 | 25 | &.p-badge-warning { 26 | background-color: $warningButtonBg; 27 | color: $warningButtonTextColor; 28 | } 29 | 30 | &.p-badge-danger { 31 | background-color: $dangerButtonBg; 32 | color: $dangerButtonTextColor; 33 | } 34 | 35 | &.p-badge-lg { 36 | font-size: 1.5 * $badgeFontSize; 37 | min-width: 1.5 * $badgeMinWidth; 38 | height: 1.5 * $badgeHeight; 39 | line-height: 1.5 * $badgeHeight; 40 | } 41 | 42 | &.p-badge-xl { 43 | font-size: 2 * $badgeFontSize; 44 | min-width: 2 * $badgeMinWidth; 45 | height: 2 * $badgeHeight; 46 | line-height: 2 * $badgeHeight; 47 | } 48 | } 49 | 50 | .p-tag { 51 | background: $badgeBg; 52 | color: $badgeTextColor; 53 | font-size: $badgeFontSize; 54 | font-weight: $badgeFontWeight; 55 | padding: $tagPadding; 56 | border-radius: $borderRadius; 57 | 58 | &.p-tag-success { 59 | background-color: $successButtonBg; 60 | color: $successButtonTextColor; 61 | } 62 | 63 | &.p-tag-info { 64 | background-color: $infoButtonBg; 65 | color: $infoButtonTextColor; 66 | } 67 | 68 | &.p-tag-warning { 69 | background-color: $warningButtonBg; 70 | color: $warningButtonTextColor; 71 | } 72 | 73 | &.p-tag-danger { 74 | background-color: $dangerButtonBg; 75 | color: $dangerButtonTextColor; 76 | } 77 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/misc/_blockui.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/theme/theme-base/components/misc/_blockui.scss -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/misc/_chip.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:math'; 2 | 3 | .p-chip { 4 | background-color: $chipBg; 5 | color: $chipTextColor; 6 | border-radius: $chipBorderRadius; 7 | padding: 0 nth($inputPadding, 2); 8 | 9 | .p-chip-text { 10 | line-height: 1.5; 11 | margin-top: math.div(nth($inputPadding, 1), 2); 12 | margin-bottom: math.div(nth($inputPadding, 1), 2); 13 | } 14 | 15 | .p-chip-icon { 16 | margin-right: $inlineSpacing; 17 | } 18 | 19 | img { 20 | width: 1.5 + nth($inputPadding, 1); 21 | height: 1.5 + nth($inputPadding, 1); 22 | margin-left: -1 * nth($inputPadding, 2); 23 | margin-right: $inlineSpacing; 24 | } 25 | 26 | .p-chip-remove-icon { 27 | border-radius: $borderRadius; 28 | transition: $actionIconTransition; 29 | margin-left: $inlineSpacing; 30 | 31 | &:focus { 32 | @include focused(); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/misc/_inplace.scss: -------------------------------------------------------------------------------- 1 | .p-inplace { 2 | .p-inplace-display { 3 | padding: $inplacePadding; 4 | border-radius: $borderRadius; 5 | transition: $formElementTransition; 6 | 7 | &:not(.p-disabled):hover { 8 | background: $inplaceHoverBg; 9 | color: $inplaceTextHoverColor; 10 | } 11 | 12 | &:focus { 13 | @include focused(); 14 | } 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/misc/_progressbar.scss: -------------------------------------------------------------------------------- 1 | .p-progressbar { 2 | border: $progressBarBorder; 3 | height: $progressBarHeight; 4 | background: $progressBarBg; 5 | border-radius: $borderRadius; 6 | 7 | .p-progressbar-value { 8 | border: 0 none; 9 | margin: 0; 10 | background: $progressBarValueBg; 11 | } 12 | 13 | .p-progressbar-label { 14 | color: $progressBarValueTextColor; 15 | line-height: $progressBarHeight; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/misc/_scrolltop.scss: -------------------------------------------------------------------------------- 1 | .p-scrolltop { 2 | width: $scrollTopWidth; 3 | height: $scrollTopHeight; 4 | border-radius: $scrollTopBorderRadius; 5 | box-shadow: $inputOverlayShadow; 6 | transition: $actionIconTransition; 7 | 8 | &.p-link { 9 | background: $scrollTopBg; 10 | 11 | &:hover { 12 | background: $scrollTopHoverBg; 13 | } 14 | } 15 | 16 | .p-scrolltop-icon { 17 | font-size: $scrollTopFontSize; 18 | color: $scrollTopTextColor; 19 | 20 | &.p-icon { 21 | width: $scrollTopFontSize; 22 | height: $scrollTopFontSize; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/misc/_skeleton.scss: -------------------------------------------------------------------------------- 1 | .p-skeleton { 2 | background-color: $skeletonBg; 3 | border-radius: $borderRadius; 4 | 5 | &:after { 6 | background: linear-gradient(90deg, rgba(255, 255, 255, 0), $skeletonAnimationBg, rgba(255, 255, 255, 0)); 7 | } 8 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/misc/_tag.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:math'; 2 | 3 | .p-tag { 4 | background: $badgeBg; 5 | color: $badgeTextColor; 6 | font-size: $badgeFontSize; 7 | font-weight: $badgeFontWeight; 8 | padding: $tagPadding; 9 | border-radius: $borderRadius; 10 | 11 | &.p-tag-success { 12 | background-color: $successButtonBg; 13 | color: $successButtonTextColor; 14 | } 15 | 16 | &.p-tag-info { 17 | background-color: $infoButtonBg; 18 | color: $infoButtonTextColor; 19 | } 20 | 21 | &.p-tag-warning { 22 | background-color: $warningButtonBg; 23 | color: $warningButtonTextColor; 24 | } 25 | 26 | &.p-tag-danger { 27 | background-color: $dangerButtonBg; 28 | color: $dangerButtonTextColor; 29 | } 30 | 31 | .p-tag-icon { 32 | margin-right: math.div($inlineSpacing, 2); 33 | font-size: $badgeFontSize; 34 | 35 | &.p-icon { 36 | width: $badgeFontSize; 37 | height: $badgeFontSize; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/misc/_terminal.scss: -------------------------------------------------------------------------------- 1 | .p-terminal { 2 | background: $panelContentBg; 3 | color: $panelContentTextColor; 4 | border: $panelContentBorder; 5 | padding: $panelContentPadding; 6 | 7 | .p-terminal-input { 8 | font-size: $fontSize; 9 | font-family: $fontFamily; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/multimedia/_image.scss: -------------------------------------------------------------------------------- 1 | .p-image-mask { 2 | --maskbg: #{$imageMaskBg}; 3 | } 4 | 5 | .p-image-preview-indicator { 6 | background-color: transparent; 7 | color: $imagePreviewIndicatorColor; 8 | transition: $actionIconTransition; 9 | 10 | .p-icon { 11 | width: $imagePreviewActionIconFontSize; 12 | height: $imagePreviewActionIconFontSize; 13 | } 14 | } 15 | 16 | .p-image-preview-container { 17 | &:hover { 18 | > .p-image-preview-indicator { 19 | background-color: $imagePreviewIndicatorBg; 20 | } 21 | } 22 | } 23 | 24 | .p-image-toolbar { 25 | padding: $imagePreviewToolbarPadding; 26 | } 27 | 28 | .p-image-action.p-link { 29 | color: $imagePreviewActionIconColor; 30 | background-color: $imagePreviewActionIconBg; 31 | width: $imagePreviewActionIconWidth; 32 | height: $imagePreviewActionIconHeight; 33 | border-radius: $imagePreviewActionIconBorderRadius; 34 | transition: $actionIconTransition; 35 | margin-right: $inlineSpacing; 36 | 37 | &:last-child { 38 | margin-right: 0; 39 | } 40 | 41 | &:hover { 42 | color: $imagePreviewActionIconHoverColor; 43 | background-color: $imagePreviewActionIconHoverBg; 44 | } 45 | 46 | span { 47 | font-size: $imagePreviewActionIconFontSize; 48 | } 49 | 50 | .p-icon { 51 | width: $imagePreviewActionIconFontSize; 52 | height: $imagePreviewActionIconFontSize; 53 | } 54 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/overlay/_confirmpopup.scss: -------------------------------------------------------------------------------- 1 | .p-confirm-popup { 2 | background: $overlayContentBg; 3 | color: $panelContentTextColor; 4 | border: $overlayContentBorder; 5 | border-radius: $borderRadius; 6 | box-shadow: $overlayContainerShadow; 7 | 8 | .p-confirm-popup-content { 9 | padding: $confirmPopupContentPadding; 10 | } 11 | 12 | .p-confirm-popup-footer { 13 | text-align: right; 14 | padding: $confirmPopupFooterPadding; 15 | 16 | button { 17 | margin: 0 $inlineSpacing 0 0; 18 | width: auto; 19 | 20 | &:last-child { 21 | margin: 0; 22 | } 23 | } 24 | } 25 | 26 | &:after { 27 | border: solid transparent; 28 | border-color: rgba($overlayContentBg, 0); 29 | border-bottom-color: $overlayContentBg; 30 | } 31 | 32 | &:before { 33 | border: solid transparent; 34 | 35 | @if (nth($overlayContentBorder, 2) == 'none') { 36 | border-color: rgba($overlayContentBg, 0); 37 | border-bottom-color: $overlayContentBg; 38 | } 39 | @else { 40 | border-color: rgba(nth($overlayContentBorder, 3), 0); 41 | border-bottom-color: nth($overlayContentBorder, 3); 42 | } 43 | } 44 | 45 | &.p-confirm-popup-flipped { 46 | &:after { 47 | border-top-color: $overlayContentBg; 48 | } 49 | 50 | &:before { 51 | @if (nth($overlayContentBorder, 2) == 'none') { 52 | border-top-color: $overlayContentBg; 53 | } 54 | @else { 55 | border-top-color: nth($overlayContentBorder, 3); 56 | } 57 | } 58 | } 59 | 60 | .p-confirm-popup-icon { 61 | font-size: $primeIconFontSize * 1.5; 62 | 63 | &.p-icon { 64 | width: $primeIconFontSize * 1.5; 65 | height: $primeIconFontSize * 1.5; 66 | } 67 | } 68 | 69 | .p-confirm-popup-message { 70 | margin-left: $inlineSpacing * 2; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/overlay/_dialog.scss: -------------------------------------------------------------------------------- 1 | .p-dialog { 2 | border-radius: $borderRadius; 3 | box-shadow: $overlayContainerShadow; 4 | border: $overlayContentBorder; 5 | 6 | .p-dialog-header { 7 | border-bottom: $dialogHeaderBorder; 8 | background: $dialogHeaderBg; 9 | color: $dialogHeaderTextColor; 10 | padding: $dialogHeaderPadding; 11 | border-top-right-radius: $borderRadius; 12 | border-top-left-radius: $borderRadius; 13 | 14 | .p-dialog-title { 15 | font-weight: $dialogHeaderFontWeight; 16 | font-size: $dialogHeaderFontSize; 17 | } 18 | 19 | .p-dialog-header-icon { 20 | @include action-icon(); 21 | margin-right: $inlineSpacing; 22 | 23 | &:last-child { 24 | margin-right: 0; 25 | } 26 | } 27 | } 28 | 29 | .p-dialog-content { 30 | background: $overlayContentBg; 31 | color: $panelContentTextColor; 32 | padding: $dialogContentPadding; 33 | 34 | &:last-of-type { 35 | border-bottom-right-radius: $borderRadius; 36 | border-bottom-left-radius: $borderRadius; 37 | } 38 | } 39 | 40 | .p-dialog-footer { 41 | border-top: $dialogFooterBorder; 42 | background: $overlayFooterBg; 43 | color: $panelFooterTextColor; 44 | padding: $dialogFooterPadding; 45 | text-align: right; 46 | border-bottom-right-radius: $borderRadius; 47 | border-bottom-left-radius: $borderRadius; 48 | 49 | button { 50 | margin: 0 $inlineSpacing 0 0; 51 | width: auto; 52 | } 53 | } 54 | 55 | &.p-dialog-maximized { 56 | .p-dialog-header, .p-dialog-content:last-of-type { 57 | border-radius: 0; 58 | } 59 | } 60 | 61 | 62 | &.p-confirm-dialog { 63 | .p-confirm-dialog-icon { 64 | font-size: $primeIconFontSize * 2; 65 | 66 | &.p-icon { 67 | width: $primeIconFontSize * 2; 68 | height: $primeIconFontSize * 2; 69 | } 70 | } 71 | 72 | .p-confirm-dialog-message { 73 | margin-left: $inlineSpacing * 2; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/overlay/_overlaypanel.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:math'; 2 | 3 | .p-overlaypanel { 4 | background: $overlayContentBg; 5 | color: $panelContentTextColor; 6 | border: $overlayContentBorder; 7 | border-radius: $borderRadius; 8 | box-shadow: $overlayContainerShadow; 9 | 10 | .p-overlaypanel-content { 11 | padding: $panelContentPadding; 12 | } 13 | 14 | .p-overlaypanel-close { 15 | background: $buttonBg; 16 | color: $buttonTextColor; 17 | width: $actionIconWidth; 18 | height: $actionIconHeight; 19 | transition: $actionIconTransition; 20 | border-radius: $actionIconBorderRadius; 21 | position: absolute; 22 | top: math.div(-1 * $actionIconWidth, 2); 23 | right: math.div(-1 * $actionIconWidth, 2); 24 | 25 | &:enabled:hover { 26 | background: $buttonHoverBg; 27 | color: $buttonTextHoverColor; 28 | } 29 | } 30 | 31 | &:after { 32 | border: solid transparent; 33 | border-color: rgba($overlayContentBg, 0); 34 | border-bottom-color: $overlayContentBg; 35 | } 36 | 37 | &:before { 38 | border: solid transparent; 39 | 40 | @if (nth($overlayContentBorder, 2) == 'none') { 41 | border-color: rgba($overlayContentBg, 0); 42 | border-bottom-color: scale-color($overlayContentBg, $lightness: -5%); 43 | } 44 | @else { 45 | border-color: rgba(nth($overlayContentBorder, 3), 0); 46 | border-bottom-color: scale-color(nth($overlayContentBorder, 3), $lightness: -5%); 47 | } 48 | } 49 | 50 | &.p-overlaypanel-flipped { 51 | &:after { 52 | border-top-color: $overlayContentBg; 53 | } 54 | 55 | &:before { 56 | @if (nth($overlayContentBorder, 2) == 'none') { 57 | border-top-color: $overlayContentBg; 58 | } 59 | @else { 60 | border-top-color: nth($overlayContentBorder, 3); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/overlay/_sidebar.scss: -------------------------------------------------------------------------------- 1 | .p-sidebar { 2 | background: $overlayContentBg; 3 | color: $panelContentTextColor; 4 | border: $overlayContentBorder; 5 | box-shadow: $overlayContainerShadow; 6 | 7 | .p-sidebar-header { 8 | padding: $panelHeaderPadding; 9 | 10 | .p-sidebar-close, 11 | .p-sidebar-icon { 12 | @include action-icon(); 13 | } 14 | 15 | & + .p-sidebar-content { 16 | padding-top: 0; 17 | } 18 | } 19 | 20 | .p-sidebar-content { 21 | padding: $panelContentPadding; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/overlay/_tooltip.scss: -------------------------------------------------------------------------------- 1 | .p-tooltip { 2 | .p-tooltip-text { 3 | background: $tooltipBg; 4 | color: $tooltipTextColor; 5 | padding: $tooltipPadding; 6 | box-shadow: $inputOverlayShadow; 7 | border-radius: $borderRadius; 8 | } 9 | 10 | &.p-tooltip-right { 11 | .p-tooltip-arrow { 12 | border-right-color: $tooltipBg; 13 | } 14 | } 15 | 16 | &.p-tooltip-left { 17 | .p-tooltip-arrow { 18 | border-left-color: $tooltipBg; 19 | } 20 | } 21 | 22 | &.p-tooltip-top { 23 | .p-tooltip-arrow { 24 | border-top-color: $tooltipBg; 25 | } 26 | } 27 | 28 | &.p-tooltip-bottom { 29 | .p-tooltip-arrow { 30 | border-bottom-color: $tooltipBg; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/panel/_card.scss: -------------------------------------------------------------------------------- 1 | .p-card { 2 | background: $panelContentBg; 3 | color: $panelContentTextColor; 4 | box-shadow: $cardShadow; 5 | border-radius: $borderRadius; 6 | 7 | .p-card-body { 8 | padding: $cardBodyPadding; 9 | } 10 | 11 | .p-card-title { 12 | font-size: $cardTitleFontSize; 13 | font-weight: $cardTitleFontWeight; 14 | margin-bottom: $inlineSpacing; 15 | } 16 | 17 | .p-card-subtitle { 18 | font-weight: $cardSubTitleFontWeight; 19 | margin-bottom: $inlineSpacing; 20 | color: $cardSubTitleColor; 21 | } 22 | 23 | .p-card-content { 24 | padding: $cardContentPadding; 25 | } 26 | 27 | .p-card-footer { 28 | padding: $cardFooterPadding; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/panel/_divider.scss: -------------------------------------------------------------------------------- 1 | .p-divider { 2 | .p-divider-content { 3 | background-color: $panelContentBg; 4 | } 5 | 6 | &.p-divider-horizontal { 7 | margin: $dividerHorizontalMargin; 8 | padding: $dividerHorizontalPadding; 9 | 10 | &:before { 11 | border-top: $dividerSize $dividerColor; 12 | } 13 | 14 | .p-divider-content { 15 | padding: 0 $inlineSpacing; 16 | } 17 | } 18 | 19 | &.p-divider-vertical { 20 | margin: $dividerVerticalMargin; 21 | padding: $dividerVerticalPadding; 22 | 23 | &:before { 24 | border-left: $dividerSize $dividerColor; 25 | } 26 | 27 | .p-divider-content { 28 | padding: $inlineSpacing 0; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/panel/_fieldset.scss: -------------------------------------------------------------------------------- 1 | .p-fieldset { 2 | border: $panelContentBorder; 3 | background: $panelContentBg; 4 | color: $panelContentTextColor; 5 | border-radius: $borderRadius; 6 | 7 | .p-fieldset-legend { 8 | padding: $panelHeaderPadding; 9 | border: $panelHeaderBorder; 10 | color: $panelHeaderTextColor; 11 | background: $panelHeaderBg; 12 | font-weight: $panelHeaderFontWeight; 13 | border-radius: $borderRadius; 14 | } 15 | 16 | &.p-fieldset-toggleable { 17 | .p-fieldset-legend { 18 | padding: 0; 19 | transition: $actionIconTransition; 20 | 21 | a { 22 | padding: $panelHeaderPadding; 23 | color: $panelHeaderTextColor; 24 | border-radius: $borderRadius; 25 | transition: $listItemTransition; 26 | 27 | .p-fieldset-toggler { 28 | margin-right: $inlineSpacing; 29 | } 30 | 31 | &:focus { 32 | @include focused(); 33 | } 34 | } 35 | 36 | &:hover { 37 | background: $panelHeaderHoverBg; 38 | border-color: $panelHeaderHoverBorderColor; 39 | color: $panelHeaderTextHoverColor; 40 | } 41 | } 42 | } 43 | 44 | .p-fieldset-content { 45 | padding: $panelContentPadding; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/panel/_panel.scss: -------------------------------------------------------------------------------- 1 | .p-panel { 2 | .p-panel-header { 3 | border: $panelHeaderBorder; 4 | padding: $panelHeaderPadding; 5 | background: $panelHeaderBg; 6 | color: $panelHeaderTextColor; 7 | border-top-right-radius: $borderRadius; 8 | border-top-left-radius: $borderRadius; 9 | 10 | .p-panel-title { 11 | font-weight: $panelHeaderFontWeight; 12 | } 13 | 14 | .p-panel-header-icon { 15 | @include action-icon(); 16 | } 17 | } 18 | 19 | &.p-panel-toggleable { 20 | .p-panel-header { 21 | padding: $panelToggleableHeaderPadding; 22 | } 23 | } 24 | 25 | .p-panel-content { 26 | padding: $panelContentPadding; 27 | border: $panelContentBorder; 28 | background: $panelContentBg; 29 | color: $panelContentTextColor; 30 | border-bottom-right-radius: $borderRadius; 31 | border-bottom-left-radius: $borderRadius; 32 | border-top: 0 none; 33 | } 34 | 35 | .p-panel-footer { 36 | padding: $panelFooterPadding; 37 | border: $panelFooterBorder; 38 | background: $panelFooterBg; 39 | color: $panelFooterTextColor; 40 | border-top: 0 none; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/panel/_scrollpanel.scss: -------------------------------------------------------------------------------- 1 | .p-scrollpanel { 2 | .p-scrollpanel-bar { 3 | background: $scrollPanelTrackBg; 4 | border: $scrollPanelTrackBorder; 5 | } 6 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/panel/_splitter.scss: -------------------------------------------------------------------------------- 1 | .p-splitter { 2 | border: $panelContentBorder; 3 | background: $panelContentBg; 4 | border-radius: $borderRadius; 5 | color: $panelContentTextColor; 6 | 7 | .p-splitter-gutter { 8 | transition: $actionIconTransition; 9 | background: $splitterGutterBg; 10 | 11 | .p-splitter-gutter-handle { 12 | background: $splitterGutterHandleBg; 13 | } 14 | } 15 | 16 | .p-splitter-gutter-resizing { 17 | background: $splitterGutterHandleBg; 18 | } 19 | } -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/panel/_tabview.scss: -------------------------------------------------------------------------------- 1 | .p-tabview { 2 | .p-tabview-nav { 3 | background: $tabviewNavBg; 4 | border: $tabviewNavBorder; 5 | border-width: $tabviewNavBorderWidth; 6 | 7 | li { 8 | margin-right: $tabviewHeaderSpacing; 9 | 10 | .p-tabview-nav-link { 11 | border: $tabviewHeaderBorder; 12 | border-width: $tabviewHeaderBorderWidth; 13 | border-color: $tabviewHeaderBorderColor; 14 | background: $tabviewHeaderBg; 15 | color: $tabviewHeaderTextColor; 16 | padding: $tabviewHeaderPadding; 17 | font-weight: $tabviewHeaderFontWeight; 18 | border-top-right-radius: $borderRadius; 19 | border-top-left-radius: $borderRadius; 20 | transition: $listItemTransition; 21 | margin: $tabviewHeaderMargin; 22 | 23 | &:not(.p-disabled):focus { 24 | @include focused-inset(); 25 | } 26 | } 27 | 28 | &:not(.p-highlight):not(.p-disabled):hover { 29 | .p-tabview-nav-link { 30 | background: $tabviewHeaderHoverBg; 31 | border-color: $tabviewHeaderHoverBorderColor; 32 | color: $tabviewHeaderTextHoverColor; 33 | } 34 | } 35 | 36 | &.p-highlight { 37 | .p-tabview-nav-link { 38 | background: $tabviewHeaderActiveBg; 39 | border-color: $tabviewHeaderActiveBorderColor; 40 | color: $tabviewHeaderTextActiveColor; 41 | } 42 | } 43 | } 44 | } 45 | 46 | .p-tabview-close { 47 | margin-left: $inlineSpacing; 48 | } 49 | 50 | .p-tabview-nav-btn.p-link { 51 | background: $tabviewHeaderActiveBg; 52 | color: $tabviewHeaderTextActiveColor; 53 | width: $buttonIconOnlyWidth; 54 | box-shadow: $raisedButtonShadow; 55 | border-radius: 0; 56 | 57 | &:focus { 58 | @include focused-inset(); 59 | } 60 | } 61 | 62 | .p-tabview-panels { 63 | background: $tabviewContentBg; 64 | padding: $tabviewContentPadding; 65 | border: $tabviewContentBorder; 66 | color: $tabviewContentTextColor; 67 | border-bottom-right-radius: $borderRadius; 68 | border-bottom-left-radius: $borderRadius; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /web/src-web/src/theme/theme-base/components/panel/_toolbar.scss: -------------------------------------------------------------------------------- 1 | .p-toolbar { 2 | background: $panelHeaderBg; 3 | border: $panelHeaderBorder; 4 | padding: $panelHeaderPadding; 5 | border-radius: $borderRadius; 6 | gap: $inlineSpacing; 7 | 8 | .p-toolbar-separator { 9 | margin: 0 $inlineSpacing; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /web/src-web/src/theme/themes/fluent/fluent-light/_fonts.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsieradzki/makoon/fae802aad8f435e534298a693f49f71377cb8a00/web/src-web/src/theme/themes/fluent/fluent-light/_fonts.scss -------------------------------------------------------------------------------- /web/src-web/src/theme/themes/fluent/fluent-light/theme.scss: -------------------------------------------------------------------------------- 1 | @import './_variables'; 2 | @import './_fonts'; 3 | @import '../../../theme-base/_components'; 4 | @import './_extensions'; 5 | -------------------------------------------------------------------------------- /web/src-web/src/utils/api.ts: -------------------------------------------------------------------------------- 1 | import {wrapWithProcessingIndicator} from "@/store/processing-indicator-store"; 2 | import {AxiosError} from "axios"; 3 | import applicationStore from "@/store/application-store"; 4 | 5 | 6 | export async function apiCall(fn: () => Promise, callKey: string = Math.random().toString(16)): Promise { 7 | return wrapWithProcessingIndicator(callKey, async () => { 8 | try { 9 | return await fn() 10 | } catch (e: any) { 11 | if (e instanceof AxiosError) { 12 | console.error(e.cause); 13 | } else { 14 | console.error(e) 15 | } 16 | applicationStore.throwError(e) 17 | throw e 18 | } 19 | }) 20 | } -------------------------------------------------------------------------------- /web/src-web/src/utils/hooks.ts: -------------------------------------------------------------------------------- 1 | import {useEffect, useRef} from "react"; 2 | import clusterManagementStore from "@/store/cluster-management-store"; 3 | 4 | export function useOnFirstMount(fn: () => Promise) { 5 | const alreadyInitialized = useRef(false) 6 | useEffect(() => { 7 | if (!alreadyInitialized.current) { 8 | alreadyInitialized.current = true 9 | fn() 10 | .then(() => { 11 | }) 12 | .catch(e => { 13 | console.error(e) 14 | }) 15 | } 16 | }, []) 17 | } 18 | 19 | export function useAsyncEffect(fn: () => Promise, deps: any[] = []) { 20 | useEffect(() => { 21 | fn() 22 | .then(() => { 23 | }) 24 | .catch(e => { 25 | console.error(e) 26 | }) 27 | }, deps) 28 | } 29 | 30 | export function useInterval(fn: () => Promise, interval = 5000) { 31 | const nodesStatusRequestFinish = useRef(true); 32 | useEffect(() => { 33 | const readTaskLogInterval = setInterval(async () => { 34 | if (nodesStatusRequestFinish.current) { 35 | nodesStatusRequestFinish.current = false; 36 | await fn(); 37 | nodesStatusRequestFinish.current = true; 38 | } else { 39 | } 40 | }, interval); 41 | return () => { 42 | clearInterval(readTaskLogInterval); 43 | } 44 | }, []) 45 | } 46 | 47 | -------------------------------------------------------------------------------- /web/src-web/src/utils/nodes.ts: -------------------------------------------------------------------------------- 1 | import {ClusterNode, ClusterNodeType} from "@/api/model"; 2 | 3 | export function generateNode(iNodes: ClusterNode[], nodeType: ClusterNodeType, defaultMasterNode: ClusterNode): ClusterNode { 4 | let nodes = iNodes.slice(); 5 | 6 | const latestNodeWithNodeType: ClusterNode | null = nodes 7 | .sort(vmIdDesc) 8 | .find((e: ClusterNode) => e.nodeType === nodeType) 9 | || null; 10 | 11 | if (latestNodeWithNodeType == null) { 12 | const anyNode: ClusterNode | null = 13 | nodes.length > 0 14 | ? nodes.sort(vmIdAsc)[0] 15 | : null; 16 | if (anyNode == null) { 17 | return defaultMasterNode; 18 | } else { 19 | const nodeBaseOnAnotherType = createNextNodeDefinition( 20 | anyNode, 21 | nodeType == "worker" 22 | ? nodes.length > 5 ? nodes.length : 5 23 | : -5); 24 | nodeBaseOnAnotherType.name = nodeType + "-1" 25 | 26 | nodeBaseOnAnotherType.nodeType = nodeType; 27 | return nodeBaseOnAnotherType; 28 | } 29 | 30 | } 31 | return createNextNodeDefinition(latestNodeWithNodeType); 32 | } 33 | 34 | function createNextNodeDefinition(node: ClusterNode, step = 1): ClusterNode { 35 | const namePrefix = node.name.substring(0, node.name.lastIndexOf("-")) 36 | const nameNumber = node.name.substring(node.name.lastIndexOf("-") + 1) 37 | 38 | const lastOctetIdx = node.ipAddress.lastIndexOf("."); 39 | const firstOctets = node.ipAddress.substring(0, lastOctetIdx + 1); 40 | const lastOctet = node.ipAddress.substring(lastOctetIdx + 1); 41 | return { 42 | vmId: node.vmId + step, 43 | storagePool: node.storagePool, 44 | cores: node.cores, 45 | memory: node.memory, 46 | nodeType: node.nodeType, 47 | name: namePrefix + "-" + (Number(nameNumber) + step).toString(), 48 | ipAddress: firstOctets + (Number(lastOctet) + step).toString() 49 | } 50 | } 51 | 52 | function vmIdAsc(a: ClusterNode, b: ClusterNode): number { 53 | return a.vmId - b.vmId; 54 | } 55 | 56 | function vmIdDesc(a: ClusterNode, b: ClusterNode): number { 57 | return b.vmId - a.vmId; 58 | } 59 | -------------------------------------------------------------------------------- /web/src-web/src/utils/patterns.ts: -------------------------------------------------------------------------------- 1 | export const hostnameMain = new RegExp(`^[a-zA-Z0-9|-]+$`) 2 | export const hostnameStart = new RegExp(`^[a-zA-Z0-9]+.*$`) 3 | export const hostnameEnd = new RegExp(`^.*[a-zA-Z0-9]+$`) 4 | -------------------------------------------------------------------------------- /web/src-web/src/utils/size.ts: -------------------------------------------------------------------------------- 1 | export function toHumanReadableSize(valInMiB?: number): String { 2 | if (!valInMiB) { 3 | return `0 MiB` 4 | } 5 | 6 | if (valInMiB < 1024) { 7 | return `${toFixed(valInMiB)} MiB` 8 | } 9 | 10 | let val = valInMiB / 1024; 11 | return `${toFixed(val)} GiB`; 12 | } 13 | 14 | function toFixed(val: number): String { 15 | return val.toFixed(2); 16 | } -------------------------------------------------------------------------------- /web/src-web/src/views/cluster-creator/CreatorNavigator.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {CreatorNavigationContext, CreatorNavigationContextType} from "@/views/cluster-creator/context"; 3 | import {Button} from "primereact/button"; 4 | 5 | type Props = { 6 | previousDisabled?: boolean; 7 | nextDisabled?: boolean; 8 | previousHidden?: boolean; 9 | nextHidden?: boolean; 10 | onNext?: () => Promise 11 | onPrevious?: () => Promise 12 | } 13 | const Navigator = (props: Props) => { 14 | return ( 15 |
16 |
17 | 18 | {props.previousHidden 19 | ? null 20 | :
21 |
30 | } 31 |
32 | {props.nextHidden 33 | ? null 34 | :
44 | 45 | ); 46 | }; 47 | 48 | export default Navigator; -------------------------------------------------------------------------------- /web/src-web/src/views/cluster-creator/context.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {ClusterCreatorStore} from "@/store/cluster-creator-store"; 3 | 4 | export type CreatorNavigationContextType = { 5 | goPrevious: () => void; 6 | goNext: () => void; 7 | } 8 | export const CreatorNavigationContext = React.createContext({} as CreatorNavigationContextType) 9 | export const ClusterCreatorStoreContext = React.createContext({} as ClusterCreatorStore) 10 | 11 | export interface CreatorNavigation { 12 | next: () => Promise 13 | previous: () => Promise 14 | } 15 | 16 | export interface StepProps { 17 | nextDisabled: (valid: boolean) => void 18 | previousDisabled: (valid: boolean) => void 19 | onNext: () => Promise 20 | onPrevious: () => Promise 21 | } 22 | -------------------------------------------------------------------------------- /web/src-web/src/views/cluster-creator/steps/apps/index.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useImperativeHandle} from 'react'; 2 | import {observer} from "mobx-react-lite"; 3 | import HelmAppsSection from "@/views/cluster-creator/steps/apps/HelmAppsSection"; 4 | import {CreatorNavigation, StepProps} from "@/views/cluster-creator/context"; 5 | 6 | const Index = (props: StepProps, ref: any) => { 7 | useImperativeHandle(ref, () => ({ 8 | async next(): Promise { 9 | await props.onNext(); 10 | }, 11 | async previous(): Promise { 12 | await props.onPrevious(); 13 | }, 14 | } as CreatorNavigation)); 15 | 16 | useEffect(() => { 17 | props.nextDisabled(false); 18 | props.previousDisabled(false); 19 | }, []); 20 | 21 | return <> 22 |
23 | 24 | 25 | }; 26 | 27 | export default observer(React.forwardRef(Index)); -------------------------------------------------------------------------------- /web/src-web/src/views/cluster-creator/steps/nodes/NodesSection.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext, useState} from 'react'; 2 | import {observer} from "mobx-react-lite"; 3 | import {ClusterNode, ClusterNodeType} from "@/api/model"; 4 | import Section from "@/components/Section"; 5 | import TableNodes from "@/views/cluster-creator/steps/nodes/TableNodes"; 6 | import {Button} from "primereact/button"; 7 | import CreatorNodeDialog from "@/views/cluster-creator/steps/nodes/CreatorNodeDialog"; 8 | import {ClusterCreatorStoreContext} from "@/views/cluster-creator/context"; 9 | 10 | type Props = { 11 | title: string 12 | nodes: ClusterNode[] 13 | clusterName: string 14 | nodeType: ClusterNodeType 15 | } 16 | const NodesSection = (props: Props) => { 17 | 18 | const creatorStore = useContext(ClusterCreatorStoreContext) 19 | const onClickNodeHandler = (id: any) => { 20 | setSelectedNodeId(id); 21 | setShowNodeDialog(true); 22 | } 23 | 24 | const title =
25 |
26 |
{props.title}
27 |
33 | 34 |
35 | const [showNodeDialog, setShowNodeDialog] = useState(false); 36 | const [selectedNodeId, setSelectedNodeId] = useState(null); 37 | const nodeDialog = showNodeDialog 38 | ? { 41 | setShowNodeDialog(false); 42 | setSelectedNodeId(null); 43 | }}/> 44 | : null; 45 | return ( 46 |
47 | {nodeDialog} 48 | 49 |
50 | ); 51 | }; 52 | 53 | export default observer(NodesSection); -------------------------------------------------------------------------------- /web/src-web/src/views/cluster-creator/steps/nodes/TableNodes.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ClusterNode} from "@/api/model"; 3 | import {DataTable} from "primereact/datatable"; 4 | import {Column} from "primereact/column"; 5 | 6 | 7 | type Props = { 8 | nodes: ClusterNode[] 9 | clusterName: string 10 | selectedId: string | null 11 | onClick: (id: any) => void 12 | } 13 | const TableNodes = (props: Props) => { 14 | return <> 15 | i.vmId.toString() == props.selectedId)} 19 | onSelectionChange={(e) => props.onClick((e.value as ClusterNode).vmId)} 20 | tableStyle={{minWidth: '50rem'}} selectionMode="single" scrollable scrollHeight="flex"> 21 | 22 | props.clusterName + "-" + r.name}> 23 | 24 | r.cores + " cores"}> 25 | r.memory + " MiB"}> 26 |
27 | {r.ipAddress} 28 |

31 |
}>
32 | 33 |
34 | 35 | }; 36 | 37 | export default TableNodes; -------------------------------------------------------------------------------- /web/src-web/src/views/cluster-creator/steps/nodes/index.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext, useEffect, useImperativeHandle} from "react"; 2 | import {observer} from "mobx-react-lite"; 3 | import {ClusterNodeType} from "@/api/model"; 4 | import {ClusterCreatorStoreContext, CreatorNavigation, StepProps} from "@/views/cluster-creator/context"; 5 | import NodesSection from "@/views/cluster-creator/steps/nodes/NodesSection"; 6 | import {autorun} from "mobx"; 7 | 8 | 9 | const NodesStep = (props: StepProps, ref: any) => { 10 | 11 | useImperativeHandle(ref, () => ({ 12 | async next(): Promise { 13 | await props.onNext(); 14 | }, 15 | async previous(): Promise { 16 | await props.onPrevious(); 17 | }, 18 | } as CreatorNavigation)); 19 | 20 | const clusterStore = useContext(ClusterCreatorStoreContext) 21 | 22 | useEffect(() => { 23 | autorun(() => { 24 | if (clusterStore.masterNodes.length == 0) { 25 | props.nextDisabled(true) 26 | return 27 | } 28 | 29 | let notValid = false; 30 | for (const node of clusterStore.cluster.nodes) { 31 | if (node.storagePool.length == 0 || node.ipAddress.length == 0) { 32 | notValid = true; 33 | break; 34 | } 35 | } 36 | props.nextDisabled(notValid) 37 | }); 38 | }, []); 39 | 40 | return <> 41 |
42 | 46 | 50 | 51 | } 52 | 53 | export default observer(React.forwardRef(NodesStep)); 54 | -------------------------------------------------------------------------------- /web/src-web/src/views/cluster-creator/steps/workloads/WorkloadsSection.tsx: -------------------------------------------------------------------------------- 1 | import React, {useContext, useState} from 'react'; 2 | import {observer} from "mobx-react-lite"; 3 | import Section from "@/components/Section"; 4 | import {ClusterCreatorStoreContext} from "@/views/cluster-creator/context"; 5 | import {Button} from "primereact/button"; 6 | import {DataTable} from "primereact/datatable"; 7 | import {ClusterResource} from "@/api/model"; 8 | import {Column} from "primereact/column"; 9 | import CreatorWorkloadsDialog from "@/views/cluster-creator/steps/workloads/CreatorWorkloadsDialog"; 10 | 11 | 12 | const WorkloadsSection = () => { 13 | const clusterStore = useContext(ClusterCreatorStoreContext) 14 | const isSelectedWorkload = function (id: string): boolean { 15 | return selectedWorkloadId != null; 16 | } 17 | 18 | const onSelectWorkload = (id: string) => { 19 | setSelectedWorkloadId(id); 20 | setShowWorkloadDialog(true); 21 | } 22 | 23 | const addWorkload = function () { 24 | setSelectedWorkloadId(null); 25 | setShowWorkloadDialog(true); 26 | } 27 | 28 | const [showWorkloadDialog, setShowWorkloadDialog] = useState(false); 29 | const [selectedWorkloadId, setSelectedWorkloadId] = useState(null); 30 | const workloadDialog = showWorkloadDialog 31 | ? { 33 | setShowWorkloadDialog(false); 34 | setSelectedWorkloadId(null); 35 | }}/> 36 | : null; 37 | return ( 38 |
40 |
Workloads
41 |
55 | ); 56 | }; 57 | 58 | export default observer(WorkloadsSection); -------------------------------------------------------------------------------- /web/src-web/src/views/cluster-creator/steps/workloads/index.tsx: -------------------------------------------------------------------------------- 1 | import WorkloadsSection from "@/views/cluster-creator/steps/workloads/WorkloadsSection"; 2 | import {observer} from "mobx-react-lite"; 3 | import {CreatorNavigation, StepProps} from "@/views/cluster-creator/context"; 4 | import React, {useEffect, useImperativeHandle} from 'react'; 5 | 6 | const WorkloadsStep = (props: StepProps, ref: any) => { 7 | useImperativeHandle(ref, () => ({ 8 | async next(): Promise { 9 | await props.onNext(); 10 | }, 11 | async previous(): Promise { 12 | await props.onPrevious(); 13 | }, 14 | } as CreatorNavigation)); 15 | 16 | useEffect(() => { 17 | props.nextDisabled(false); 18 | props.previousDisabled(false); 19 | }, []); 20 | 21 | return <> 22 |
23 | 24 | 25 | 26 | } 27 | 28 | export default observer(React.forwardRef(WorkloadsStep)); -------------------------------------------------------------------------------- /web/src-web/src/views/cluster-list/components/ClusterListPanel.tsx: -------------------------------------------------------------------------------- 1 | import {observer} from "mobx-react-lite"; 2 | import {DataTable} from "primereact/datatable"; 3 | import {Column} from "primereact/column"; 4 | import Panel from "@/components/Panel"; 5 | import React, {useState} from "react"; 6 | import {useOnFirstMount} from "@/utils/hooks"; 7 | import ClusterStatus from "@/views/cluster-list/components/ClusterStatus"; 8 | import clustersListStore from "@/store/clusters-list-store"; 9 | import {useNavigate} from "react-router-dom"; 10 | import {ClusterHeader} from "@/api/model"; 11 | 12 | const ClusterListPanel = () => { 13 | const navigate = useNavigate(); 14 | useOnFirstMount(async () => { 15 | await clustersListStore.loadClusters() 16 | }) 17 | 18 | const [selectedProduct, setSelectedProduct] = useState(); 19 | const goToClusterManagement = (clusterName: string) => { 20 | navigate(`/cluster/${clusterName}`) 21 | } 22 | return 23 | { 27 | goToClusterManagement((e.value as ClusterHeader).name) 28 | }} 29 | tableStyle={{minWidth: '50rem'}} 30 | selectionMode="single" 31 | scrollable 32 | scrollHeight="flex"> 33 | 34 | 35 | r.coresSum + " cores"}> 36 | r.memorySum + " MiB"}> 37 | r.diskSizeSum + " GiB"}> 38 | }> 39 | 40 | 41 | } 42 | 43 | export default observer(ClusterListPanel) -------------------------------------------------------------------------------- /web/src-web/src/views/cluster-list/components/ClusterStatus.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {ClusterStatus as ClusterStatusValue} from "@/api/model"; 3 | 4 | type Props = { 5 | status: ClusterStatusValue; 6 | className?: string 7 | } 8 | const ClusterStatus = (props: Props) => { 9 | switch (props.status) { 10 | case ClusterStatusValue.Pending: 11 | return 12 | case ClusterStatusValue.Creating: 13 | return 14 | case ClusterStatusValue.Sync: 15 | return 16 | case ClusterStatusValue.OutOfSync: 17 | return 18 | case ClusterStatusValue.Destroying: 19 | return 20 | case ClusterStatusValue.Error: 21 | return 22 | } 23 | } 24 | 25 | export default ClusterStatus -------------------------------------------------------------------------------- /web/src-web/src/views/cluster-list/components/UsedResourcesPanel.tsx: -------------------------------------------------------------------------------- 1 | import Panel from "@/components/Panel"; 2 | import React from "react"; 3 | import {observer} from "mobx-react-lite"; 4 | import clustersListStore from "@/store/clusters-list-store"; 5 | 6 | const UsedResourcesPanel = () => { 7 | return 8 |
9 |
10 |
11 | {clustersListStore.cpuSum} 12 |
13 |
cores
14 |
15 |
16 |
CPU
17 |
18 | 19 | 20 | 21 |
22 |
23 | {clustersListStore.ramSum} 24 |
25 |
MiB
26 |
27 |
28 |
MEMORY
29 |
30 | 31 | 32 | 33 |
34 |
35 | {clustersListStore.disksSizeSum} 36 |
37 |
GiB
38 |
39 |
40 |
STORAGE
41 |
42 |
43 |
44 | } 45 | 46 | export default observer(UsedResourcesPanel); -------------------------------------------------------------------------------- /web/src-web/src/views/cluster-list/index.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from "react"; 2 | import MainContainer from "@/components/MainContainer"; 3 | import {observer} from "mobx-react-lite"; 4 | import {Button} from "primereact/button"; 5 | import UsedResourcesPanel from "@/views/cluster-list/components/UsedResourcesPanel"; 6 | import ClusterListPanel from "@/views/cluster-list/components/ClusterListPanel"; 7 | import clustersListStore from "@/store/clusters-list-store"; 8 | import ClusterCreator from "@/views/cluster-creator"; 9 | import {useInterval} from "@/utils/hooks"; 10 | 11 | const ClusterList = () => { 12 | const [creatorVisible, setCreatorVisible] = useState(false); 13 | 14 | useInterval(async () => { 15 | await clustersListStore.loadClusters(); 16 | }); 17 | 18 | return { 22 | setCreatorVisible(true); 23 | }}/> 24 | }}> 25 | { 26 | creatorVisible 27 | ? setCreatorVisible(false)}/> 28 | : null 29 | } 30 | 31 | 32 | 33 | 34 | } 35 | 36 | export default observer(ClusterList); -------------------------------------------------------------------------------- /web/src-web/src/views/clusters-management/components/apps/HelmAppStatus.tsx: -------------------------------------------------------------------------------- 1 | import { Tag } from "primereact/tag"; 2 | import React from "react"; 3 | import { ProgressSpinner } from 'primereact/progressspinner'; 4 | import { AppStatusType } from "@/api/model"; 5 | 6 | type HelmAppStatusComponentProps = { 7 | status: AppStatusType | null 8 | className?: string 9 | } 10 | 11 | const HelmAppStatus = (props: HelmAppStatusComponentProps) => { 12 | switch (props.status) { 13 | case AppStatusType.Unknown: 14 | return 15 | case AppStatusType.Deployed: 16 | return 18 | case AppStatusType.Uninstalled: 19 | return 21 | case AppStatusType.Superseded: 22 | return 24 | case AppStatusType.Failed: 25 | return 27 | case AppStatusType.Uninstalling: 28 | return 30 | case AppStatusType.PendingInstall: 31 | return 33 | case AppStatusType.PendingUpgrade: 34 | return 36 | case AppStatusType.PendingRollback: 37 | return 39 | case AppStatusType.NotInstalled: 40 | return 42 | case null: 43 | return 44 | default: 45 | return 47 | } 48 | } 49 | 50 | export default HelmAppStatus -------------------------------------------------------------------------------- /web/src-web/src/views/clusters-management/components/cluster-logs/LogLevel.tsx: -------------------------------------------------------------------------------- 1 | import {Tag} from "primereact/tag"; 2 | import React from "react"; 3 | import {ActionLogLevel} from "@/api/model"; 4 | 5 | type Props = { 6 | value: ActionLogLevel; 7 | className?: string 8 | } 9 | const LogLevel = (props: Props) => { 10 | switch (props.value) { 11 | case ActionLogLevel.Info: 12 | return 13 | case ActionLogLevel.Error: 14 | return 15 | } 16 | } 17 | 18 | export default LogLevel; -------------------------------------------------------------------------------- /web/src-web/src/views/clusters-management/components/cluster-logs/index.tsx: -------------------------------------------------------------------------------- 1 | import Panel from "@/components/Panel"; 2 | import React from "react"; 3 | import {useAsyncEffect, useInterval} from "@/utils/hooks"; 4 | import {observer} from "mobx-react-lite"; 5 | import {DataTable} from "primereact/datatable"; 6 | import {Column} from "primereact/column"; 7 | import {Button} from "primereact/button"; 8 | import clusterManagementStore from "@/store/cluster-management-store"; 9 | import LogLevel from "@/views/clusters-management/components/cluster-logs/LogLevel"; 10 | 11 | const Logs = () => { 12 | useAsyncEffect(async () => { 13 | if (clusterManagementStore.cluster.clusterName.length > 0) { 14 | await clusterManagementStore.loadLogs(clusterManagementStore.cluster.clusterName); 15 | } 16 | }, [clusterManagementStore.cluster]); 17 | 18 | useInterval(async ()=>{ 19 | await clusterManagementStore.loadLogs(clusterManagementStore.cluster.clusterName); 20 | 21 | });`` 22 | 23 | return 25 | 26 |