├── signature ├── src │ └── lib.rs └── Cargo.toml ├── ui ├── .npmrc ├── client │ ├── src │ │ ├── mocks │ │ │ ├── server.ts │ │ │ ├── stub-new-work │ │ │ │ ├── index.ts │ │ │ │ └── projects.ts │ │ │ ├── browser.ts │ │ │ ├── config.ts │ │ │ └── config.test.ts │ │ ├── app │ │ │ ├── pages │ │ │ │ ├── home │ │ │ │ │ ├── index.ts │ │ │ │ │ └── home.tsx │ │ │ │ ├── documents │ │ │ │ │ └── index.ts │ │ │ │ ├── project-list │ │ │ │ │ ├── index.ts │ │ │ │ │ └── components │ │ │ │ │ │ └── project-form.tsx │ │ │ │ └── project-details │ │ │ │ │ ├── index.ts │ │ │ │ │ └── project-details.tsx │ │ │ ├── react-app-env.d.ts │ │ │ ├── layout │ │ │ │ ├── index.ts │ │ │ │ ├── layout-constants.ts │ │ │ │ ├── default-layout.tsx │ │ │ │ ├── sidebar.tsx │ │ │ │ └── about.tsx │ │ │ ├── axios-config │ │ │ │ ├── index.ts │ │ │ │ └── apiInit.ts │ │ │ ├── hooks │ │ │ │ ├── table-controls │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── getPaginationHubRequestParams.ts │ │ │ │ │ ├── getHubRequestParams.ts │ │ │ │ │ └── getSortHubRequestParams.ts │ │ │ │ ├── useCreateEditModalState.ts │ │ │ │ ├── useStorage.ts │ │ │ │ └── useSelectionState.ts │ │ │ ├── env.ts │ │ │ ├── components │ │ │ │ ├── HookFormPFFields │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── HookFormPFTextArea.tsx │ │ │ │ │ ├── HookFormPFSelect.tsx │ │ │ │ │ ├── HookFormPFTextInput.tsx │ │ │ │ │ └── HookFormPFGroupController.tsx │ │ │ │ ├── StateNoData.tsx │ │ │ │ ├── AppPlaceholder.tsx │ │ │ │ ├── ConditionalTooltip.tsx │ │ │ │ ├── StateNoResults.tsx │ │ │ │ ├── NoDataEmptyState.tsx │ │ │ │ ├── KeyDisplayToggle.tsx │ │ │ │ ├── StateError.tsx │ │ │ │ ├── Notifications.tsx │ │ │ │ ├── AppTableActionButtons.tsx │ │ │ │ ├── OidcProvider.tsx │ │ │ │ ├── NotificationsContext.tsx │ │ │ │ ├── ConfirmDialog.tsx │ │ │ │ └── ToolbarBulkSelector.tsx │ │ │ ├── setupTests.ts │ │ │ ├── Constants.ts │ │ │ ├── utils │ │ │ │ ├── utils.test.ts │ │ │ │ ├── type-utils.ts │ │ │ │ └── utils.ts │ │ │ ├── i18n.ts │ │ │ ├── reportWebVitals.ts │ │ │ ├── App.css │ │ │ ├── App.tsx │ │ │ ├── images │ │ │ │ ├── pfbg-icon.svg │ │ │ │ └── avatar.svg │ │ │ ├── oidc.ts │ │ │ ├── queries │ │ │ │ ├── ubl-documents.ts │ │ │ │ ├── projects.ts │ │ │ │ └── credentials.ts │ │ │ ├── context │ │ │ │ └── ProjectContext.tsx │ │ │ ├── Routes.tsx │ │ │ └── api │ │ │ │ ├── models.ts │ │ │ │ └── rest.ts │ │ └── index.tsx │ ├── public │ │ ├── robots.txt │ │ ├── openubl-favicon.ico │ │ ├── manifest.json │ │ ├── index.html.ejs │ │ └── locales │ │ │ ├── en │ │ │ └── translation.json │ │ │ └── es │ │ │ └── translation.json │ ├── types │ │ ├── globals.d.ts │ │ ├── typings.d.ts │ │ ├── @hookform_resolvers_2.9.11.d.ts │ │ └── array-filter-Boolean.ts │ ├── config │ │ ├── monacoConstants.ts │ │ ├── stylePaths.js │ │ ├── jest.config.ts │ │ ├── webpack.prod.ts │ │ └── webpack.dev.ts │ ├── i18next-parser.config.js │ ├── tsconfig.json │ └── package.json ├── .dockerignore ├── README.md ├── .prettierignore ├── .editorconfig ├── .prettierrc.mjs ├── common │ ├── tsconfig.json │ ├── rollup.config.js │ ├── src │ │ ├── index.ts │ │ ├── proxies.ts │ │ └── environment.ts │ └── package.json ├── .gitignore ├── server │ ├── rollup.config.js │ ├── package.json │ └── src │ │ └── index.js ├── entrypoint.sh ├── Dockerfile ├── scripts │ └── verify_lock.mjs ├── .eslintrc.cjs └── package.json ├── common ├── src │ ├── lib.rs │ └── config.rs └── Cargo.toml ├── api ├── src │ ├── lib.rs │ ├── system │ │ ├── error.rs │ │ ├── mod.rs │ │ └── credentials.rs │ └── db.rs └── Cargo.toml ├── entity ├── src │ ├── prelude.rs │ ├── mod.rs │ ├── lib.rs │ ├── document.rs │ ├── keystore.rs │ ├── send_rule.rs │ ├── keystore_config.rs │ ├── credentials.rs │ └── delivery.rs └── Cargo.toml ├── .devcontainer ├── postCreateCommand.sh ├── Dockerfile └── devcontainer.json ├── migration ├── src │ ├── main.rs │ ├── lib.rs │ ├── m20240113_213636_create_keystore.rs │ ├── m20240117_142858_create_send_rule.rs │ ├── m20240101_104121_create_document.rs │ ├── m20240113_213657_create_keystore_config.rs │ ├── m20240114_154538_create_credentials.rs │ └── m20240717_214515_create_delivery.rs ├── Cargo.toml └── README.md ├── deploy └── compose │ ├── scripts │ ├── minio │ │ └── setup.sh │ └── keycloak │ │ └── setup.sh │ └── compose.yaml ├── README.md ├── cli ├── Cargo.toml └── src │ └── main.rs ├── server ├── src │ ├── server │ │ ├── health.rs │ │ ├── mod.rs │ │ └── credentials.rs │ ├── lib.rs │ └── dto.rs └── Cargo.toml ├── storage ├── Cargo.toml └── src │ └── config.rs ├── .gitignore └── Cargo.toml /signature/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ui/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /ui/client/src/mocks/server.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /common/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | -------------------------------------------------------------------------------- /ui/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | */dist/ 3 | -------------------------------------------------------------------------------- /api/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod db; 2 | pub mod system; 3 | -------------------------------------------------------------------------------- /ui/README.md: -------------------------------------------------------------------------------- 1 | ## Spog UI 2 | 3 | export AUTH_REQUIRED=true -------------------------------------------------------------------------------- /entity/src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 2 | -------------------------------------------------------------------------------- /ui/client/src/app/pages/home/index.ts: -------------------------------------------------------------------------------- 1 | export { Home as default } from "./home"; 2 | -------------------------------------------------------------------------------- /ui/client/src/app/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /ui/client/src/app/layout/index.ts: -------------------------------------------------------------------------------- 1 | export { DefaultLayout } from "./default-layout"; 2 | -------------------------------------------------------------------------------- /ui/client/src/app/axios-config/index.ts: -------------------------------------------------------------------------------- 1 | export { initInterceptors } from "./apiInit"; 2 | -------------------------------------------------------------------------------- /ui/client/src/app/hooks/table-controls/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./getHubRequestParams"; 2 | -------------------------------------------------------------------------------- /ui/client/src/app/pages/documents/index.ts: -------------------------------------------------------------------------------- 1 | export { Projects as default } from "./documents"; 2 | -------------------------------------------------------------------------------- /entity/src/mod.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 2 | 3 | pub mod prelude; 4 | -------------------------------------------------------------------------------- /ui/client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /ui/client/src/app/pages/project-list/index.ts: -------------------------------------------------------------------------------- 1 | export { ProjectList as default } from "./project-list"; 2 | -------------------------------------------------------------------------------- /ui/client/src/app/pages/project-details/index.ts: -------------------------------------------------------------------------------- 1 | export { ProjectDetails as default } from "./project-details"; 2 | -------------------------------------------------------------------------------- /.devcontainer/postCreateCommand.sh: -------------------------------------------------------------------------------- 1 | ## Git autocomplete 2 | echo "source /usr/share/bash-completion/completions/git" >> ~/.bashrc 3 | -------------------------------------------------------------------------------- /ui/client/public/openubl-favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/project-openubl/ublhub/HEAD/ui/client/public/openubl-favicon.ico -------------------------------------------------------------------------------- /ui/client/src/app/layout/layout-constants.ts: -------------------------------------------------------------------------------- 1 | type ThemeType = "light" | "dark"; 2 | export const LayoutTheme: ThemeType = "dark"; 3 | -------------------------------------------------------------------------------- /migration/src/main.rs: -------------------------------------------------------------------------------- 1 | use sea_orm_migration::prelude::*; 2 | 3 | #[async_std::main] 4 | async fn main() { 5 | cli::run_cli(migration::Migrator).await; 6 | } 7 | -------------------------------------------------------------------------------- /common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openubl-common" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | clap = { workspace = true, features = ["derive", "env"] } 8 | -------------------------------------------------------------------------------- /ui/client/src/app/env.ts: -------------------------------------------------------------------------------- 1 | import { decodeEnv, buildOpenublEnv } from "@openubl-ui/common"; 2 | 3 | export const ENV = buildOpenublEnv(decodeEnv(window._env)); 4 | 5 | export default ENV; 6 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/rust:bullseye 2 | RUN apt-get update && apt-get -y install pkg-config xmlsec1 libxml2-dev libxmlsec1-dev libxmlsec1-openssl libclang-dev 3 | -------------------------------------------------------------------------------- /ui/client/src/app/components/HookFormPFFields/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./HookFormPFGroupController"; 2 | export * from "./HookFormPFTextInput"; 3 | export * from "./HookFormPFTextArea"; 4 | export * from "./HookFormPFSelect"; 5 | -------------------------------------------------------------------------------- /ui/client/types/globals.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | declare global { 4 | interface Window { 5 | /** 6 | * base64 encoded JS object containing any environment configurations. 7 | */ 8 | _env: string; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /entity/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10 2 | 3 | pub mod prelude; 4 | 5 | pub mod credentials; 6 | pub mod delivery; 7 | pub mod document; 8 | pub mod keystore; 9 | pub mod keystore_config; 10 | pub mod send_rule; 11 | -------------------------------------------------------------------------------- /ui/.prettierignore: -------------------------------------------------------------------------------- 1 | # Library, IDE and build locations 2 | **/node_modules/ 3 | **/coverage/ 4 | **/dist/ 5 | .vscode/ 6 | .idea/ 7 | .eslintcache/ 8 | 9 | # 10 | # NOTE: Could ignore anything that eslint will look at since eslint also applies 11 | # prettier. 12 | # 13 | -------------------------------------------------------------------------------- /ui/client/src/app/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import "@testing-library/jest-dom"; 6 | -------------------------------------------------------------------------------- /entity/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openubl-entity" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | sea-orm = { workspace = true, features = [ 9 | "sqlx-sqlite", 10 | "sqlx-postgres", 11 | "runtime-tokio-rustls", 12 | "macros", 13 | ] } 14 | -------------------------------------------------------------------------------- /ui/.editorconfig: -------------------------------------------------------------------------------- 1 | root=true 2 | 3 | [*] 4 | # standard prettier behaviors 5 | charset = utf-8 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | # Configurable prettier behaviors 10 | end_of_line = lf 11 | indent_style = space 12 | indent_size = 2 13 | max_line_length = 80 14 | -------------------------------------------------------------------------------- /signature/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openubl-signature" 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 | anyhow = { workspace = true } 10 | thiserror = { workspace = true } 11 | -------------------------------------------------------------------------------- /api/src/system/error.rs: -------------------------------------------------------------------------------- 1 | use sea_orm::DbErr; 2 | 3 | #[derive(Debug, thiserror::Error)] 4 | pub enum Error { 5 | #[error(transparent)] 6 | Json(#[from] serde_json::Error), 7 | 8 | #[error(transparent)] 9 | Database(#[from] DbErr), 10 | 11 | #[error(transparent)] 12 | Any(#[from] anyhow::Error), 13 | } 14 | -------------------------------------------------------------------------------- /ui/client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "openubl-ui", 3 | "name": "Openubl UI", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /deploy/compose/scripts/minio/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -l 2 | 3 | # Connect to minio 4 | /usr/bin/mc config host add myminio http://minio:9000 admin password; 5 | 6 | # Create buckets 7 | /usr/bin/mc mb myminio/openubl || true; 8 | 9 | # Config events 10 | /usr/bin/mc event add myminio/openubl arn:minio:sqs::OPENUBL:nats --event "put,delete"; 11 | 12 | # Restart service 13 | /usr/bin/mc admin service restart myminio; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## dev-env 2 | 3 | Starting: 4 | 5 | ```shell 6 | docker-compose -f openubl/deploy/compose/compose.yaml up 7 | ``` 8 | 9 | ```shell 10 | RUST_LOG=info cargo watch -x 'run -p openubl-cli -- server --db-user user --db-password password --oidc-auth-server-url http://localhost:9001/realms/openubl minio --storage-minio-host http://localhost:9002 --storage-minio-access-key admin --storage-minio-secret-key password' 11 | ``` 12 | -------------------------------------------------------------------------------- /ui/.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import("prettier").Config} */ 2 | const config = { 3 | trailingComma: "es5", // es5 was the default in prettier v2 4 | semi: true, 5 | singleQuote: false, 6 | 7 | // Values used from .editorconfig: 8 | // - printWidth == max_line_length 9 | // - tabWidth == indent_size 10 | // - useTabs == indent_style 11 | // - endOfLine == end_of_line 12 | }; 13 | 14 | export default config; 15 | -------------------------------------------------------------------------------- /ui/common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | 4 | "include": ["src/**/*"], 5 | 6 | "compilerOptions": { 7 | "strict": true, 8 | "target": "es2020", 9 | "module": "Node16", 10 | "moduleResolution": "Node16", 11 | 12 | "outDir": "./dist", 13 | "declaration": true, 14 | "declarationMap": true, 15 | "sourceMap": true, 16 | "inlineSources": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ui/client/src/app/components/StateNoData.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTranslation } from "react-i18next"; 3 | import { NoDataEmptyState } from "./NoDataEmptyState"; 4 | 5 | export const StateNoData: React.FC = () => { 6 | const { t } = useTranslation(); 7 | 8 | return ( 9 | 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openubl-cli" 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 | openubl-server = { workspace = true } 10 | 11 | clap = { workspace = true, features = ["derive", "env"] } 12 | anyhow = { workspace = true } 13 | actix-web = { workspace = true } 14 | log = { workspace = true } 15 | tokio = { workspace = true, features = ["full"] } 16 | -------------------------------------------------------------------------------- /ui/client/src/app/Constants.ts: -------------------------------------------------------------------------------- 1 | import ENV from "./env"; 2 | 3 | export enum BrandType { 4 | Openubl = "openubl", 5 | LF = "lf", 6 | } 7 | export const APP_BRAND = ENV.PROFILE as BrandType; 8 | 9 | // URL param prefixes: should be short, must be unique for each table that uses one 10 | export enum TableURLParamKeyPrefix { 11 | repositories = "r", 12 | tags = "t", 13 | } 14 | 15 | export const isAuthRequired = ENV.AUTH_REQUIRED !== "false"; 16 | export const uploadLimit = "500m"; 17 | -------------------------------------------------------------------------------- /ui/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules/ 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | coverage/ 10 | 11 | # production 12 | dist/ 13 | /qa/build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | 24 | .eslintcache 25 | 26 | # VSCode 27 | .vscode/* 28 | 29 | # Intellij IDEA 30 | .idea/ 31 | -------------------------------------------------------------------------------- /ui/client/src/app/utils/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { getToolbarChipKey } from "./utils"; 2 | 3 | describe("utils", () => { 4 | // getToolbarChipKey 5 | 6 | it("getToolbarChipKey: test 'string'", () => { 7 | const result = getToolbarChipKey("myValue"); 8 | expect(result).toBe("myValue"); 9 | }); 10 | 11 | it("getToolbarChipKey: test 'ToolbarChip'", () => { 12 | const result = getToolbarChipKey({ key: "myKey", node: "myNode" }); 13 | expect(result).toBe("myKey"); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /ui/client/src/app/i18n.ts: -------------------------------------------------------------------------------- 1 | import i18n from "i18next"; 2 | import { initReactI18next } from "react-i18next"; 3 | import Backend from "i18next-http-backend"; 4 | 5 | i18n 6 | .use(Backend) 7 | .use(initReactI18next) 8 | .init({ 9 | lng: "en", 10 | fallbackLng: "en", 11 | debug: false, 12 | react: { 13 | useSuspense: false, 14 | }, 15 | interpolation: { 16 | escapeValue: false, 17 | }, 18 | 19 | returnEmptyString: false, 20 | }); 21 | 22 | export default i18n; 23 | -------------------------------------------------------------------------------- /ui/client/src/app/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from "web-vitals"; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /ui/client/src/app/utils/type-utils.ts: -------------------------------------------------------------------------------- 1 | export type KeyWithValueType = { 2 | [Key in keyof T]-?: T[Key] extends V ? Key : never; 3 | }[keyof T]; 4 | 5 | export type DisallowCharacters< 6 | T extends string, 7 | TInvalidCharacter extends string, 8 | > = T extends `${string}${TInvalidCharacter}${string}` ? never : T; 9 | 10 | export type DiscriminatedArgs = 11 | | ({ [key in TBoolDiscriminatorKey]: true } & TArgs) 12 | | { [key in TBoolDiscriminatorKey]?: false }; 13 | -------------------------------------------------------------------------------- /ui/client/src/app/components/AppPlaceholder.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Bullseye, Spinner } from "@patternfly/react-core"; 3 | 4 | export const AppPlaceholder: React.FC = () => { 5 | return ( 6 | 7 |
8 |
9 | 10 |
11 |
12 |

Loading...

13 |
14 |
15 |
16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /ui/client/src/app/hooks/useCreateEditModalState.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | type ModalState = 4 | | { mode: "create"; resource: null } 5 | | { mode: "create"; resource: T } 6 | | { mode: "edit"; resource: T } 7 | | null; 8 | 9 | export default function useCreateEditModalState() { 10 | const [modalState, setModalState] = React.useState>(null); 11 | const isModalOpen = modalState !== null; 12 | 13 | return { 14 | modalState, 15 | setModalState, 16 | isModalOpen, 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /ui/client/config/monacoConstants.ts: -------------------------------------------------------------------------------- 1 | import { EditorLanguage } from "monaco-editor/esm/metadata"; 2 | 3 | export const LANGUAGES_BY_FILE_EXTENSION = { 4 | java: "java", 5 | go: "go", 6 | xml: "xml", 7 | js: "javascript", 8 | ts: "typescript", 9 | html: "html", 10 | htm: "html", 11 | css: "css", 12 | yaml: "yaml", 13 | yml: "yaml", 14 | json: "json", 15 | md: "markdown", 16 | php: "php", 17 | py: "python", 18 | pl: "perl", 19 | rb: "ruby", 20 | sh: "shell", 21 | bash: "shell", 22 | } as const satisfies Record; 23 | -------------------------------------------------------------------------------- /ui/client/src/mocks/stub-new-work/index.ts: -------------------------------------------------------------------------------- 1 | import { type RestHandler } from "msw"; 2 | import { config } from "../config"; 3 | 4 | import projects from "./projects"; 5 | 6 | const enableMe = (me: string) => 7 | config.stub === "*" || 8 | (Array.isArray(config.stub) ? (config.stub as string[]).includes(me) : false); 9 | 10 | /** 11 | * Return the stub-new-work handlers that are enabled by config. 12 | */ 13 | const enabledStubs: RestHandler[] = [ 14 | ...(enableMe("projects") ? projects : []), 15 | ].filter(Boolean); 16 | 17 | export default enabledStubs; 18 | -------------------------------------------------------------------------------- /ui/client/src/app/components/ConditionalTooltip.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Tooltip, TooltipProps } from "@patternfly/react-core"; 3 | 4 | export interface IConditionalTooltipProps extends TooltipProps { 5 | isTooltipEnabled: boolean; 6 | children: React.ReactElement; 7 | } 8 | 9 | // TODO: lib-ui candidate 10 | export const ConditionalTooltip: React.FC = ({ 11 | isTooltipEnabled, 12 | children, 13 | ...props 14 | }: IConditionalTooltipProps) => 15 | isTooltipEnabled ? {children} : children; 16 | -------------------------------------------------------------------------------- /ui/common/rollup.config.js: -------------------------------------------------------------------------------- 1 | import nodeResolve from "@rollup/plugin-node-resolve"; 2 | import typescript from "@rollup/plugin-typescript"; 3 | 4 | const config = { 5 | input: "src/index.ts", 6 | 7 | output: [ 8 | { 9 | file: "dist/index.mjs", 10 | format: "esm", 11 | sourcemap: true, 12 | }, 13 | { 14 | file: "dist/index.cjs", 15 | format: "cjs", 16 | sourcemap: true, 17 | }, 18 | ], 19 | 20 | watch: { 21 | clearScreen: false, 22 | }, 23 | 24 | plugins: [nodeResolve(), typescript()], 25 | }; 26 | 27 | export default config; 28 | -------------------------------------------------------------------------------- /ui/client/config/stylePaths.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | import * as path from "path"; 4 | 5 | export const stylePaths = [ 6 | // Include our sources 7 | path.resolve(__dirname, "../src"), 8 | 9 | // Include =PF4 paths, even if nested under another package because npm cannot hoist 13 | // a single package to the root node_modules/ 14 | /node_modules\/@patternfly\/patternfly/, 15 | /node_modules\/@patternfly\/react-core\/.*\.css/, 16 | /node_modules\/@patternfly\/react-styles/, 17 | ]; 18 | -------------------------------------------------------------------------------- /server/src/server/health.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{get, web, Responder}; 2 | 3 | use crate::server::Error; 4 | use crate::AppState; 5 | 6 | #[utoipa::path( 7 | responses( 8 | (status = 200, description = "Liveness"), 9 | ), 10 | )] 11 | #[get("/health/live")] 12 | pub async fn liveness(_: web::Data) -> Result { 13 | Ok("Live") 14 | } 15 | 16 | #[utoipa::path( 17 | responses( 18 | (status = 200, description = "Readiness"), 19 | ), 20 | )] 21 | #[get("/health/read")] 22 | pub async fn readiness(_: web::Data) -> Result { 23 | Ok("Read") 24 | } 25 | -------------------------------------------------------------------------------- /ui/client/src/app/App.css: -------------------------------------------------------------------------------- 1 | .pf-c-select__toggle:before { 2 | border-top: var(--pf-c-select__toggle--before--BorderTopWidth) solid 3 | var(--pf-c-select__toggle--before--BorderTopColor) !important; 4 | border-right: var(--pf-c-select__toggle--before--BorderRightWidth) solid 5 | var(--pf-c-select__toggle--before--BorderRightColor) !important; 6 | border-bottom: var(--pf-c-select__toggle--before--BorderBottomWidth) solid 7 | var(--pf-c-select__toggle--before--BorderBottomColor) !important; 8 | border-left: var(--pf-c-select__toggle--before--BorderLeftWidth) solid 9 | var(--pf-c-select__toggle--before--BorderLeftColor) !important; 10 | } 11 | -------------------------------------------------------------------------------- /ui/client/src/app/pages/home/home.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { 4 | PageSection, 5 | PageSectionVariants, 6 | Text, 7 | TextContent, 8 | } from "@patternfly/react-core"; 9 | 10 | export const Home: React.FC = () => { 11 | return ( 12 | <> 13 | 14 | 15 | Trusted Content 16 | Fully hosted and managed service 17 | 18 | 19 | content 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /ui/client/i18next-parser.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | module.exports = { 4 | createOldCatalogs: true, // Save the \_old files 5 | 6 | indentation: 2, // Indentation of the catalog files 7 | 8 | keepRemoved: true, // Keep keys from the catalog that are no longer in code 9 | 10 | lexers: { 11 | js: ["JsxLexer"], 12 | ts: ["JsxLexer"], 13 | jsx: ["JsxLexer"], 14 | tsx: ["JsxLexer"], 15 | 16 | default: ["JsxLexer"], 17 | }, 18 | 19 | locales: ["en", "es"], 20 | 21 | output: "public/locales/$LOCALE/$NAMESPACE.json", 22 | 23 | input: ["src/**/*.{js,jsx,ts,tsx}"], 24 | 25 | sort: true, 26 | verbose: true, 27 | }; 28 | -------------------------------------------------------------------------------- /entity/src/document.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10 2 | 3 | use sea_orm::entity::prelude::*; 4 | 5 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] 6 | #[sea_orm(table_name = "ubl_document")] 7 | pub struct Model { 8 | #[sea_orm(primary_key)] 9 | pub id: i32, 10 | pub file_id: String, 11 | pub supplier_id: String, 12 | pub identifier: String, 13 | pub r#type: String, 14 | pub voided_document_code: Option, 15 | pub digest_value: Option, 16 | pub sha256: String, 17 | } 18 | 19 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 20 | pub enum Relation {} 21 | 22 | impl ActiveModelBehavior for ActiveModel {} 23 | -------------------------------------------------------------------------------- /ui/client/src/app/App.tsx: -------------------------------------------------------------------------------- 1 | import "./App.css"; 2 | import React from "react"; 3 | import { BrowserRouter as Router } from "react-router-dom"; 4 | 5 | import { DefaultLayout } from "./layout"; 6 | import { AppRoutes } from "./Routes"; 7 | import { NotificationsProvider } from "./components/NotificationsContext"; 8 | 9 | import "@patternfly/patternfly/patternfly.css"; 10 | import "@patternfly/patternfly/patternfly-addons.css"; 11 | 12 | const App: React.FC = () => { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | }; 23 | 24 | export default App; 25 | -------------------------------------------------------------------------------- /entity/src/keystore.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10 2 | 3 | use sea_orm::entity::prelude::*; 4 | 5 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] 6 | #[sea_orm(table_name = "keystore")] 7 | pub struct Model { 8 | #[sea_orm(primary_key)] 9 | pub id: i32, 10 | pub name: String, 11 | } 12 | 13 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 14 | pub enum Relation { 15 | #[sea_orm(has_many = "super::keystore_config::Entity")] 16 | KeystoreConfig, 17 | } 18 | 19 | impl Related for Entity { 20 | fn to() -> RelationDef { 21 | Relation::KeystoreConfig.def() 22 | } 23 | } 24 | 25 | impl ActiveModelBehavior for ActiveModel {} 26 | -------------------------------------------------------------------------------- /ui/client/src/app/images/pfbg-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/common/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./environment.js"; 2 | export * from "./proxies.js"; 3 | 4 | /** 5 | * Return a base64 encoded JSON string containing the given `env` object. 6 | */ 7 | export const encodeEnv = (env: object, exclude?: string[]): string => { 8 | const filtered = exclude 9 | ? Object.fromEntries( 10 | Object.entries(env).filter(([key]) => !exclude.includes(key)) 11 | ) 12 | : env; 13 | 14 | return btoa(JSON.stringify(filtered)); 15 | }; 16 | 17 | /** 18 | * Return an objects from a base64 encoded JSON string. 19 | */ 20 | export const decodeEnv = (env: string): object => 21 | !env ? undefined : JSON.parse(atob(env)); 22 | 23 | // TODO: Include `index.html.ejs` to `index.html` template file processing... 24 | -------------------------------------------------------------------------------- /api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openubl-api" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | openubl-entity = { workspace = true } 8 | openubl-common = { workspace = true } 9 | openubl-migration = { workspace = true } 10 | openubl-storage = { workspace = true } 11 | 12 | xhandler = { workspace = true } 13 | 14 | sea-orm = { workspace = true, features = [ 15 | "sea-query-binder", 16 | "sqlx-sqlite", 17 | "sqlx-postgres", 18 | "runtime-tokio-rustls", 19 | "macros", 20 | ] } 21 | sea-query = { workspace = true } 22 | async-trait = { workspace = true } 23 | anyhow = { workspace = true } 24 | thiserror = { workspace = true } 25 | serde_json = { workspace = true } 26 | serde = { workspace = true, features = ["derive"] } 27 | -------------------------------------------------------------------------------- /ui/server/rollup.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | import json from "@rollup/plugin-json"; 4 | import nodeResolve from "@rollup/plugin-node-resolve"; 5 | import commonjs from "@rollup/plugin-commonjs"; 6 | import run from "@rollup/plugin-run"; 7 | 8 | const buildAndRun = process.env?.ROLLUP_RUN === "true"; 9 | 10 | export default { 11 | input: "src/index.js", 12 | output: { 13 | file: "dist/index.js", 14 | format: "esm", 15 | sourcemap: true, 16 | }, 17 | watch: { 18 | clearScreen: false, 19 | }, 20 | 21 | plugins: [ 22 | nodeResolve({ 23 | preferBuiltins: true, 24 | }), 25 | commonjs(), 26 | json(), 27 | buildAndRun && 28 | run({ 29 | execArgv: ["-r", "source-map-support/register"], 30 | }), 31 | ], 32 | }; 33 | -------------------------------------------------------------------------------- /migration/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openubl-migration" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [lib] 8 | name = "migration" 9 | path = "src/lib.rs" 10 | 11 | [dependencies] 12 | async-std = { version = "1", features = ["attributes", "tokio1"] } 13 | 14 | [dependencies.sea-orm-migration] 15 | version = "1.0.0" 16 | features = [ 17 | # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. 18 | # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. 19 | # e.g. 20 | "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature 21 | "sqlx-sqlite", # `DATABASE_DRIVER` feature 22 | "sqlx-postgres", # `DATABASE_DRIVER` feature 23 | ] 24 | -------------------------------------------------------------------------------- /ui/client/src/app/components/StateNoResults.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTranslation } from "react-i18next"; 3 | 4 | import { 5 | EmptyState, 6 | EmptyStateBody, 7 | EmptyStateIcon, 8 | EmptyStateVariant, 9 | Title, 10 | } from "@patternfly/react-core"; 11 | import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon"; 12 | 13 | export const StateNoResults: React.FC = () => { 14 | const { t } = useTranslation(); 15 | 16 | return ( 17 | 18 | 19 | 20 | {t("message.noResultsFoundTitle")} 21 | 22 | {t("message.noResultsFoundBody")} 23 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /ui/client/src/app/layout/default-layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Page } from "@patternfly/react-core"; 4 | 5 | import { HeaderApp } from "./header"; 6 | import { SidebarApp } from "./sidebar"; 7 | import { Notifications } from "@app/components/Notifications"; 8 | import { PageContentWithDrawerProvider } from "@app/components/PageDrawerContext"; 9 | 10 | interface DefaultLayoutProps { 11 | children?: React.ReactNode; 12 | } 13 | 14 | export const DefaultLayout: React.FC = ({ children }) => { 15 | return ( 16 | } sidebar={} isManagedSidebar> 17 | 18 | {children} 19 | 20 | 21 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /ui/client/src/app/oidc.ts: -------------------------------------------------------------------------------- 1 | import { OidcClientSettings, User } from "oidc-client-ts"; 2 | import { ENV } from "./env"; 3 | 4 | const OIDC_SERVER_URL = 5 | ENV.OIDC_SERVER_URL || "http://localhost:9001/realms/openubl"; 6 | const OIDC_CLIENT_ID = ENV.OIDC_CLIENT_ID || "openubl-ui"; 7 | 8 | export const oidcClientSettings: OidcClientSettings = { 9 | authority: OIDC_SERVER_URL, 10 | client_id: OIDC_CLIENT_ID, 11 | redirect_uri: window.location.href, 12 | post_logout_redirect_uri: window.location.href.split("?")[0], 13 | }; 14 | 15 | export function getUser() { 16 | const oidcStorage = sessionStorage.getItem( 17 | `oidc.user:${OIDC_SERVER_URL}:${OIDC_CLIENT_ID}` 18 | ); 19 | if (!oidcStorage) { 20 | return null; 21 | } 22 | 23 | return User.fromStorageString(oidcStorage); 24 | } 25 | -------------------------------------------------------------------------------- /storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openubl-storage" 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 | clap = { workspace = true, features = ["derive", "env"] } 10 | minio = { workspace = true } 11 | serde = { workspace = true, features = ["derive"] } 12 | anyhow = { workspace = true } 13 | uuid = { workspace = true, features = ["v4"] } 14 | thiserror = { workspace = true } 15 | zip = { workspace = true } 16 | tempfile = { workspace = true } 17 | reqwest = { workspace = true } 18 | aws-sdk-s3 = { workspace = true } 19 | tokio = { workspace = true } 20 | aws-config = { workspace = true } 21 | aws-smithy-runtime = { workspace = true } 22 | aws-smithy-runtime-api = { workspace = true } 23 | -------------------------------------------------------------------------------- /ui/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@openubl-ui/server", 3 | "version": "0.1.0", 4 | "license": "Apache-2.0", 5 | "private": true, 6 | "type": "module", 7 | "scripts": { 8 | "clean": "rimraf ./dist", 9 | "lint": "eslint .", 10 | "build": "NODE_ENV=production rollup -c", 11 | "start:dev": "NODE_ENV=development ROLLUP_RUN=true rollup -c -w", 12 | "start": "npm run build && node --enable-source-maps dist/index.js" 13 | }, 14 | "lint-staged": { 15 | "*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}": "eslint --fix", 16 | "*.{css,json,md,yaml,yml}": "prettier --write" 17 | }, 18 | "dependencies": { 19 | "cookie-parser": "^1.4.6", 20 | "ejs": "^3.1.7", 21 | "express": "^4.17.3", 22 | "http-proxy-middleware": "^2.0.6", 23 | "http-terminator": "^3.2.0" 24 | }, 25 | "devDependencies": {} 26 | } 27 | -------------------------------------------------------------------------------- /ui/client/src/app/components/NoDataEmptyState.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | EmptyState, 4 | EmptyStateBody, 5 | EmptyStateIcon, 6 | EmptyStateVariant, 7 | Title, 8 | } from "@patternfly/react-core"; 9 | import CubesIcon from "@patternfly/react-icons/dist/esm/icons/cubes-icon"; 10 | 11 | export interface NoDataEmptyStateProps { 12 | title: string; 13 | description?: string; 14 | } 15 | 16 | export const NoDataEmptyState: React.FC = ({ 17 | title, 18 | description, 19 | }) => { 20 | return ( 21 | 22 | 23 | 24 | {title} 25 | 26 | {description && {description}} 27 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /ui/client/src/app/components/KeyDisplayToggle.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Button } from "@patternfly/react-core"; 3 | import EyeSlashIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon"; 4 | import EyeIcon from "@patternfly/react-icons/dist/js/icons/eye-icon"; 5 | 6 | interface IKeyDisplayToggleProps { 7 | keyName: string; 8 | isKeyHidden: boolean; 9 | onClick: (event: React.MouseEvent) => void; 10 | } 11 | 12 | export const KeyDisplayToggle: React.FC = ({ 13 | keyName, 14 | isKeyHidden, 15 | onClick, 16 | }: IKeyDisplayToggleProps) => ( 17 | 22 | ); 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.versionsBackup 2 | 3 | # Intellij 4 | ################### 5 | .idea 6 | *.iml 7 | 8 | # Eclipse # 9 | ########### 10 | .project 11 | .settings 12 | .classpath 13 | 14 | # NetBeans # 15 | ############ 16 | nbactions.xml 17 | nb-configuration.xml 18 | catalog.xml 19 | nbproject 20 | 21 | # Compiled source # 22 | ################### 23 | *.com 24 | *.class 25 | *.dll 26 | *.exe 27 | *.o 28 | *.so 29 | 30 | # Packages # 31 | ############ 32 | # it's better to unpack these files and commit the raw source 33 | # git has its own built in compression methods 34 | *.7z 35 | *.dmg 36 | *.gz 37 | *.iso 38 | *.jar 39 | *.rar 40 | *.tar 41 | *.zip 42 | 43 | # Logs and databases # 44 | ###################### 45 | *.log 46 | 47 | # Maven # 48 | ######### 49 | target 50 | 51 | # Maven shade 52 | ############# 53 | *dependency-reduced-pom.xml 54 | 55 | /lsp/ 56 | 57 | 58 | # Added by cargo 59 | 60 | /target 61 | 62 | .openubl/ -------------------------------------------------------------------------------- /ui/client/src/app/queries/ubl-documents.ts: -------------------------------------------------------------------------------- 1 | import { keepPreviousData, useQuery } from "@tanstack/react-query"; 2 | 3 | import { HubRequestParams } from "@app/api/models"; 4 | import { getUblDocuments } from "@app/api/rest"; 5 | 6 | export const UblDocumentsQueryKey = "documents"; 7 | 8 | export const useFetchUblDocuments = ( 9 | projectId?: number | string, 10 | params: HubRequestParams = {} 11 | ) => { 12 | const { data, isLoading, error, refetch } = useQuery({ 13 | enabled: projectId !== undefined, 14 | queryKey: [UblDocumentsQueryKey, projectId, params], 15 | queryFn: () => getUblDocuments(projectId, params), 16 | placeholderData: keepPreviousData, 17 | }); 18 | return { 19 | result: { 20 | data: data?.data || [], 21 | total: data?.total ?? 0, 22 | params: data?.params ?? params, 23 | }, 24 | isFetching: isLoading, 25 | fetchError: error, 26 | refetch, 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /ui/client/src/app/components/StateError.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | EmptyState, 4 | EmptyStateIcon, 5 | EmptyStateVariant, 6 | Title, 7 | EmptyStateBody, 8 | } from "@patternfly/react-core"; 9 | import ExclamationCircleIcon from "@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon"; 10 | import { global_danger_color_200 as globalDangerColor200 } from "@patternfly/react-tokens"; 11 | 12 | export const StateError: React.FC = () => { 13 | return ( 14 | 15 | 19 | 20 | Unable to connect 21 | 22 | 23 | There was an error retrieving data. Check your connection and try again. 24 | 25 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /entity/src/send_rule.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10 2 | 3 | use sea_orm::entity::prelude::*; 4 | 5 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] 6 | #[sea_orm(table_name = "send_rule")] 7 | pub struct Model { 8 | #[sea_orm(primary_key)] 9 | pub id: i32, 10 | pub supplier_id: String, 11 | pub credentials_id: i32, 12 | } 13 | 14 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 15 | pub enum Relation { 16 | #[sea_orm( 17 | belongs_to = "super::credentials::Entity", 18 | from = "Column::CredentialsId", 19 | to = "super::credentials::Column::Id", 20 | on_update = "NoAction", 21 | on_delete = "Cascade" 22 | )] 23 | Credentials, 24 | } 25 | 26 | impl Related for Entity { 27 | fn to() -> RelationDef { 28 | Relation::Credentials.def() 29 | } 30 | } 31 | 32 | impl ActiveModelBehavior for ActiveModel {} 33 | -------------------------------------------------------------------------------- /entity/src/keystore_config.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10 2 | 3 | use sea_orm::entity::prelude::*; 4 | 5 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] 6 | #[sea_orm(table_name = "keystore_config")] 7 | pub struct Model { 8 | #[sea_orm(primary_key)] 9 | pub id: i32, 10 | pub name: String, 11 | pub val: String, 12 | pub keystore_id: i32, 13 | } 14 | 15 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 16 | pub enum Relation { 17 | #[sea_orm( 18 | belongs_to = "super::keystore::Entity", 19 | from = "Column::KeystoreId", 20 | to = "super::keystore::Column::Id", 21 | on_update = "NoAction", 22 | on_delete = "Cascade" 23 | )] 24 | Keystore, 25 | } 26 | 27 | impl Related for Entity { 28 | fn to() -> RelationDef { 29 | Relation::Keystore.def() 30 | } 31 | } 32 | 33 | impl ActiveModelBehavior for ActiveModel {} 34 | -------------------------------------------------------------------------------- /migration/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use sea_orm_migration::prelude::*; 2 | 3 | mod m20240101_104121_create_document; 4 | mod m20240113_213636_create_keystore; 5 | mod m20240113_213657_create_keystore_config; 6 | mod m20240114_154538_create_credentials; 7 | mod m20240117_142858_create_send_rule; 8 | mod m20240717_214515_create_delivery; 9 | pub struct Migrator; 10 | 11 | #[async_trait::async_trait] 12 | impl MigratorTrait for Migrator { 13 | fn migrations() -> Vec> { 14 | vec![ 15 | Box::new(m20240101_104121_create_document::Migration), 16 | Box::new(m20240113_213636_create_keystore::Migration), 17 | Box::new(m20240113_213657_create_keystore_config::Migration), 18 | Box::new(m20240114_154538_create_credentials::Migration), 19 | Box::new(m20240117_142858_create_send_rule::Migration), 20 | Box::new(m20240717_214515_create_delivery::Migration), 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ublhub", 3 | "build": { 4 | "dockerfile": "Dockerfile" 5 | }, 6 | "features": { 7 | "ghcr.io/devcontainers/features/rust:1": {}, 8 | "ghcr.io/devcontainers/features/node:1": { 9 | "version": "20" 10 | }, 11 | "ghcr.io/devcontainers/features/docker-in-docker:2": {} 12 | }, 13 | "postCreateCommand": "bash .devcontainer/postCreateCommand.sh", 14 | "customizations": { 15 | "vscode": { 16 | "settings": {}, 17 | "extensions": [ 18 | "vadimcn.vscode-lldb", 19 | "rust-lang.rust-analyzer", 20 | "tamasfe.even-better-toml", 21 | "github.vscode-github-actions", 22 | "github.vscode-pull-request-github" 23 | ] 24 | }, 25 | "codespaces": { 26 | "openFiles": [ 27 | "README.md" 28 | ] 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /entity/src/credentials.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10 2 | 3 | use sea_orm::entity::prelude::*; 4 | 5 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] 6 | #[sea_orm(table_name = "credentials")] 7 | pub struct Model { 8 | #[sea_orm(primary_key)] 9 | pub id: i32, 10 | pub name: String, 11 | pub description: Option, 12 | pub username_sol: String, 13 | pub password_sol: String, 14 | pub client_id: String, 15 | pub client_secret: String, 16 | pub url_invoice: String, 17 | pub url_despatch: String, 18 | pub url_perception_retention: String, 19 | } 20 | 21 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 22 | pub enum Relation { 23 | #[sea_orm(has_many = "super::send_rule::Entity")] 24 | SendRule, 25 | } 26 | 27 | impl Related for Entity { 28 | fn to() -> RelationDef { 29 | Relation::SendRule.def() 30 | } 31 | } 32 | 33 | impl ActiveModelBehavior for ActiveModel {} 34 | -------------------------------------------------------------------------------- /ui/client/src/mocks/browser.ts: -------------------------------------------------------------------------------- 1 | import { type RestHandler, setupWorker, rest } from "msw"; 2 | 3 | import config from "./config"; 4 | import stubNewWork from "./stub-new-work"; 5 | 6 | /** 7 | * Handler to catch unhandled traffic to `/hub/*`, log it, and pass it through to the 8 | * server to handle. This is useful to see traffic, in the console logs, that is not 9 | * being mocked elsewhere. 10 | */ 11 | const passthroughHandler: RestHandler = rest.all("/hub/*", (req) => { 12 | console.log( 13 | "%cmsw passthrough%c \u{1fa83} %s", 14 | "font-weight: bold", 15 | "font-weight: normal", 16 | req.url 17 | ); 18 | return req.passthrough(); 19 | }); 20 | 21 | const handlers = [ 22 | // TODO: Add handlers for a FULL hub mock data set 23 | ...stubNewWork, 24 | config.passthrough && passthroughHandler, 25 | ].filter(Boolean); 26 | 27 | /** 28 | * A setup MSW browser service worker using the handlers configured in the MOCK env var. 29 | */ 30 | export const worker = setupWorker(...handlers); 31 | 32 | export { config } from "./config"; 33 | -------------------------------------------------------------------------------- /ui/common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@openubl-ui/common", 3 | "description": "ESM module for code common to client and server", 4 | "version": "0.1.0", 5 | "license": "Apache-2.0", 6 | "private": true, 7 | "type": "module", 8 | "exports": { 9 | ".": { 10 | "types": "./dist/index.d.ts", 11 | "import": "./dist/index.mjs", 12 | "require": "./dist/index.cjs" 13 | } 14 | }, 15 | "types": "./dist", 16 | "main": "./dist/index.cjs", 17 | "module": "./dist/index.mjs", 18 | "scripts": { 19 | "clean": "rimraf ./dist", 20 | "lint": "eslint .", 21 | "prebuild": "npm run clean", 22 | "build": "NODE_ENV=production rollup -c", 23 | "start:dev": "NODE_ENV=development rollup -c --watch" 24 | }, 25 | "lint-staged": { 26 | "*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}": "eslint --fix", 27 | "*.{css,json,md,yaml,yml}": "prettier --write" 28 | }, 29 | "dependencies": { 30 | "ejs": "^3.1.7", 31 | "express": "^4.17.3", 32 | "http-proxy-middleware": "^2.0.6" 33 | }, 34 | "devDependencies": {} 35 | } 36 | -------------------------------------------------------------------------------- /ui/client/public/index.html.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% if(brandType == 'lf'){ %> 5 | LibreFact 6 | 7 | 8 | <% } else{ %> 9 | Openubl 10 | 11 | 12 | <% } %> 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /ui/client/src/app/context/ProjectContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useState } from "react"; 2 | 3 | interface IProjectContext { 4 | projectId?: number | string; 5 | setProjectId: (projectId: number | string) => void; 6 | } 7 | 8 | const ProjectContext = createContext({ 9 | projectId: undefined, 10 | setProjectId: () => undefined, 11 | }); 12 | 13 | interface IProjectContextProviderProps { 14 | children: React.ReactNode; 15 | } 16 | 17 | export const ProjectContextProvider: React.FC = ({ 18 | children, 19 | }: IProjectContextProviderProps) => { 20 | const [projectid, setProjectId] = useState(); 21 | 22 | return ( 23 | setProjectId(key), 27 | }} 28 | > 29 | {children} 30 | 31 | ); 32 | }; 33 | 34 | export const useProjectContext = (): IProjectContext => 35 | useContext(ProjectContext); 36 | -------------------------------------------------------------------------------- /deploy/compose/scripts/keycloak/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -l 2 | KEYCLOAK_HOME="/opt/keycloak" 3 | 4 | while ! $KEYCLOAK_HOME/bin/kcadm.sh config credentials config --server "$KEYCLOAK_SERVER_URL" --realm master --user "$KEYCLOAK_ADMIN" --password "$KEYCLOAK_ADMIN_PASSWORD" &> /dev/null; do 5 | echo "Waiting for Keycloak to start up..." 6 | sleep 3 7 | done 8 | 9 | # Create realm 10 | $KEYCLOAK_HOME/bin/kcadm.sh create realms -s realm=openubl -s enabled=true -o 11 | 12 | # Create clients 13 | $KEYCLOAK_HOME/bin/kcadm.sh create clients -r openubl -f - << EOF 14 | { 15 | "clientId": "openubl-api", 16 | "secret": "secret" 17 | } 18 | EOF 19 | 20 | $KEYCLOAK_HOME/bin/kcadm.sh create clients -r openubl -f - << EOF 21 | { 22 | "clientId": "openubl-ui", 23 | "publicClient": true, 24 | "redirectUris": ["*"], 25 | "webOrigins": ["*"] 26 | } 27 | EOF 28 | 29 | # Create user 30 | $KEYCLOAK_HOME/bin/kcadm.sh create users -r=openubl -s username=carlos -s enabled=true 31 | $KEYCLOAK_HOME/bin/kcadm.sh set-password -r=openubl --username carlos --new-password carlos -------------------------------------------------------------------------------- /ui/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | 4 | "include": ["src/**/*", "config/**/*", "types/**/*"], 5 | 6 | "compilerOptions": { 7 | "outDir": "dist", 8 | "baseUrl": ".", 9 | "paths": { 10 | "@app/*": ["src/app/*"], 11 | "@assets/*": [ 12 | "../node_modules/@patternfly/react-core/dist/styles/assets/*" 13 | ] 14 | }, 15 | "typeRoots": ["types/", "node_modules/@types", "../node_modules/@types"], 16 | 17 | "target": "es2020", 18 | "module": "es2020", 19 | "moduleResolution": "bundler", 20 | 21 | "allowJs": true, 22 | "checkJs": false, 23 | "esModuleInterop": true, 24 | "forceConsistentCasingInFileNames": true, 25 | "jsx": "react", 26 | "resolveJsonModule": true, 27 | "skipLibCheck": true, 28 | "sourceMap": true, 29 | "strict": true, 30 | 31 | "noEmit": true 32 | }, 33 | 34 | "ts-node": { 35 | "compilerOptions": { 36 | "module": "commonjs" // node16 == commonjs or es2020 depending on package.json/type 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ui/client/src/app/layout/sidebar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { NavLink } from "react-router-dom"; 3 | 4 | import { Nav, NavList, PageSidebar } from "@patternfly/react-core"; 5 | import { css } from "@patternfly/react-styles"; 6 | 7 | import { LayoutTheme } from "./layout-constants"; 8 | 9 | const LINK_CLASS = "pf-v5-c-nav__link"; 10 | const ACTIVE_LINK_CLASS = "pf-m-current"; 11 | 12 | export const SidebarApp: React.FC = () => { 13 | const renderPageNav = () => { 14 | return ( 15 | 29 | ); 30 | }; 31 | 32 | return {renderPageNav()}; 33 | }; 34 | -------------------------------------------------------------------------------- /server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openubl-server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | description = "Web service for managing UBL files from SUNAT" 7 | 8 | [dependencies] 9 | openubl-api = { workspace = true } 10 | openubl-common = { workspace = true } 11 | openubl-entity = { workspace = true } 12 | openubl-storage = { workspace = true } 13 | 14 | xhandler = { workspace = true } 15 | 16 | actix-web = { workspace = true } 17 | serde = { workspace = true, features = ["derive"] } 18 | sea-orm = { workspace = true, features = [ 19 | "sqlx-sqlite", 20 | "sqlx-postgres", 21 | "runtime-tokio-rustls", 22 | "macros", 23 | ] } 24 | clap = { workspace = true, features = ["derive", "env"] } 25 | anyhow = { workspace = true } 26 | env_logger = { workspace = true } 27 | thiserror = { workspace = true } 28 | utoipa = { workspace = true, features = ["actix_extras"] } 29 | utoipa-swagger-ui = { workspace = true, features = ["actix-web"] } 30 | actix-web-httpauth = { workspace = true } 31 | actix-4-jwt-auth = { workspace = true } 32 | actix-multipart = { workspace = true } 33 | minio = { workspace = true } 34 | -------------------------------------------------------------------------------- /ui/client/types/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.avif" { 2 | const src: string; 3 | export default src; 4 | } 5 | 6 | declare module "*.bmp" { 7 | const src: string; 8 | export default src; 9 | } 10 | 11 | declare module "*.gif" { 12 | const src: string; 13 | export default src; 14 | } 15 | 16 | declare module "*.jpg" { 17 | const src: string; 18 | export default src; 19 | } 20 | 21 | declare module "*.jpeg" { 22 | const src: string; 23 | export default src; 24 | } 25 | 26 | declare module "*.png" { 27 | const src: string; 28 | export default src; 29 | } 30 | 31 | declare module "*.webp" { 32 | const src: string; 33 | export default src; 34 | } 35 | 36 | declare module "*.svg" { 37 | import * as React from "react"; 38 | 39 | export const ReactComponent: React.FC< 40 | React.SVGProps & { title?: string } 41 | >; 42 | 43 | const src: string; 44 | export default src; 45 | } 46 | 47 | declare module "*.css" { 48 | const classes: { readonly [key: string]: string }; 49 | export default classes; 50 | } 51 | 52 | declare module "*.xsd" { 53 | const src: string; 54 | export default src; 55 | } 56 | declare module "*.yaml"; 57 | -------------------------------------------------------------------------------- /ui/client/src/app/images/avatar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ui/client/src/app/components/Notifications.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Alert, 4 | AlertActionCloseButton, 5 | AlertGroup, 6 | } from "@patternfly/react-core"; 7 | import { NotificationsContext } from "./NotificationsContext"; 8 | 9 | export const Notifications: React.FunctionComponent = () => { 10 | const appContext = React.useContext(NotificationsContext); 11 | return ( 12 | 13 | {appContext.notifications.map((notification) => { 14 | return ( 15 | { 23 | appContext.dismissNotification(notification.title); 24 | }} 25 | /> 26 | ), 27 | })} 28 | timeout={notification.timeout ? notification.timeout : 4000} 29 | > 30 | {notification.message} 31 | 32 | ); 33 | })} 34 | 35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /ui/common/src/proxies.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from "http-proxy-middleware"; 2 | import { OPENUBL_ENV } from "./environment.js"; 3 | 4 | export const proxyMap: Record = { 5 | "/hub": { 6 | target: OPENUBL_ENV.OPENUBL_HUB_URL || "http://localhost:8080", 7 | logLevel: process.env.DEBUG ? "debug" : "info", 8 | 9 | changeOrigin: true, 10 | pathRewrite: { 11 | "^/hub": "", 12 | }, 13 | 14 | onProxyReq: (proxyReq, req, _res) => { 15 | // Add the Bearer token to the request if it is not already present, AND if 16 | // the token is part of the request as a cookie 17 | if (req.cookies?.keycloak_cookie && !req.headers["authorization"]) { 18 | proxyReq.setHeader( 19 | "Authorization", 20 | `Bearer ${req.cookies.keycloak_cookie}` 21 | ); 22 | } 23 | }, 24 | onProxyRes: (proxyRes, req, res) => { 25 | const includesJsonHeaders = 26 | req.headers.accept?.includes("application/json"); 27 | if ( 28 | (!includesJsonHeaders && proxyRes.statusCode === 401) || 29 | (!includesJsonHeaders && proxyRes.statusMessage === "Unauthorized") 30 | ) { 31 | res.redirect("/"); 32 | } 33 | }, 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /migration/src/m20240113_213636_create_keystore.rs: -------------------------------------------------------------------------------- 1 | use sea_orm_migration::prelude::*; 2 | 3 | #[derive(DeriveMigrationName)] 4 | pub struct Migration; 5 | 6 | #[async_trait::async_trait] 7 | impl MigrationTrait for Migration { 8 | async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { 9 | manager 10 | .create_table( 11 | Table::create() 12 | .table(Keystore::Table) 13 | .if_not_exists() 14 | .col( 15 | ColumnDef::new(Keystore::Id) 16 | .integer() 17 | .not_null() 18 | .auto_increment() 19 | .primary_key(), 20 | ) 21 | .col(ColumnDef::new(Keystore::Name).string().not_null()) 22 | .to_owned(), 23 | ) 24 | .await 25 | } 26 | 27 | async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { 28 | manager 29 | .drop_table(Table::drop().table(Keystore::Table).to_owned()) 30 | .await 31 | } 32 | } 33 | 34 | #[derive(DeriveIden)] 35 | pub enum Keystore { 36 | Table, 37 | Id, 38 | Name, 39 | } 40 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "migration", 5 | "entity", 6 | "common", 7 | "api", 8 | "server", 9 | "cli", 10 | "storage", 11 | "signature", 12 | ] 13 | 14 | default-members = ["cli"] 15 | 16 | [workspace.dependencies] 17 | openubl-entity = { path = "./entity" } 18 | openubl-common = { path = "./common" } 19 | openubl-migration = { path = "./migration" } 20 | openubl-storage = { path = "./storage" } 21 | openubl-server = { path = "./server" } 22 | openubl-api = { path = "./api" } 23 | 24 | xhandler = { git = "https://github.com/project-openubl/xhandler-rust.git", branch = "main"} 25 | 26 | sea-orm = "1.0.0" 27 | sea-query = "0.31" 28 | async-trait = "0.1.75" 29 | anyhow = "1.0.76" 30 | thiserror = "1.0.50" 31 | serde_json = "1.x" 32 | serde = "1.x" 33 | clap = "4.4.11" 34 | actix-web = "4.4.1" 35 | log = "0.4.21" 36 | tokio = "1.38.0" 37 | env_logger = "0.11.5" 38 | utoipa = "4.1.0" 39 | utoipa-swagger-ui = "7.1.0" 40 | actix-web-httpauth = "0.8.1" 41 | actix-4-jwt-auth = "1.2.0" 42 | actix-multipart = "0.7.2" 43 | minio = "0.1.0" 44 | uuid = "1.6.1" 45 | zip = "2.1.6" 46 | tempfile = "3.9.0" 47 | reqwest = "0.11" 48 | aws-sdk-s3 = "1.11.0" 49 | aws-config = "1.1.1" 50 | aws-smithy-runtime = "1.1.1" 51 | aws-smithy-runtime-api = "1.1.1" 52 | -------------------------------------------------------------------------------- /ui/client/types/@hookform_resolvers_2.9.11.d.ts: -------------------------------------------------------------------------------- 1 | // See: https://github.com/react-hook-form/resolvers/pull/527 2 | 3 | // Upgrading to @hookform/resolves@^3 will properly fix the problem, but until 4 | // that is an option (it is built for yup@1), manually pull the types in. 5 | 6 | declare module "@hookform/resolvers/yup" { 7 | // contents of @hookform/resolvers/yup/dist/types.d.ts 8 | import { 9 | FieldValues, 10 | ResolverOptions, 11 | ResolverResult, 12 | } from "react-hook-form"; 13 | import * as Yup from "yup"; 14 | import type Lazy from "yup/lib/Lazy"; 15 | 16 | declare type Options> = Parameters< 17 | T["validate"] 18 | >[1]; 19 | export declare type Resolver = >( 20 | schema: T, 21 | schemaOptions?: Options, 22 | factoryOptions?: { 23 | mode?: "async" | "sync"; 24 | rawValues?: boolean; 25 | } 26 | ) => ( 27 | values: TFieldValues, 28 | context: TContext | undefined, 29 | options: ResolverOptions 30 | ) => Promise>; 31 | export {}; 32 | 33 | // contents of @hookform/resolvers/yup/dist/yup.d.ts 34 | export declare const yupResolver: Resolver; 35 | } 36 | -------------------------------------------------------------------------------- /ui/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [[ -z "$OPENUBL_API_URL" ]]; then 6 | echo "You must provide OPENUBL_API_URL environment variable" 1>&2 7 | exit 1 8 | fi 9 | 10 | if [[ $AUTH_REQUIRED != "false" ]]; then 11 | if [[ -z "$OIDC_CLIENT_ID" ]]; then 12 | echo "You must provide OIDC_CLIENT_ID environment variable" 1>&2 13 | exit 1 14 | fi 15 | if [[ -z "$OIDC_SERVER_URL" ]]; then 16 | echo "You must provide OIDC_SERVER_URL environment variable" 1>&2 17 | exit 1 18 | fi 19 | fi 20 | 21 | # Copy the Kube API and service CA bundle to /opt/app-root/src/ca.crt if they exist 22 | 23 | # Add Kube API CA 24 | if [ -f "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" ]; then 25 | cp /var/run/secrets/kubernetes.io/serviceaccount/ca.crt ${NODE_EXTRA_CA_CERTS} 26 | fi 27 | 28 | # Add service serving CA 29 | if [ -f "/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt" ]; then 30 | cat /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt >> ${NODE_EXTRA_CA_CERTS} 31 | fi 32 | 33 | # Add custom ingress CA if it exists 34 | if [ -f "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem" ]; then 35 | cat /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem >> ${NODE_EXTRA_CA_CERTS} 36 | fi 37 | 38 | exec node --enable-source-maps server/dist/index.js 39 | -------------------------------------------------------------------------------- /ui/client/types/array-filter-Boolean.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Fixes https://github.com/microsoft/TypeScript/issues/16655 for `Array.prototype.filter()` 3 | * For example, using the fix the type of `bar` is `string[]` in the below snippet as it should be. 4 | * 5 | * const foo: (string | null | undefined)[] = []; 6 | * const bar = foo.filter(Boolean); 7 | * 8 | * For related definitions, see https://github.com/microsoft/TypeScript/blob/master/src/lib/es5.d.ts 9 | * 10 | * Original licenses apply, see 11 | * - https://github.com/microsoft/TypeScript/blob/master/LICENSE.txt 12 | * - https://stackoverflow.com/help/licensing 13 | */ 14 | 15 | /** See https://stackoverflow.com/a/51390763/1470607 */ 16 | type Falsy = false | 0 | "" | null | undefined; 17 | 18 | interface Array { 19 | /** 20 | * Returns the elements of an array that meet the condition specified in a callback function. 21 | * @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array. 22 | * @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value. 23 | */ 24 | filter( 25 | predicate: BooleanConstructor, 26 | thisArg?: any 27 | ): Exclude[]; 28 | } 29 | -------------------------------------------------------------------------------- /ui/client/src/app/axios-config/apiInit.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { getUser } from "@app/oidc"; 3 | 4 | export const initInterceptors = () => { 5 | axios.interceptors.request.use( 6 | (config) => { 7 | const user = getUser(); 8 | const token = user?.access_token; 9 | if (token) { 10 | config.headers.Authorization = `Bearer ${token}`; 11 | } 12 | return config; 13 | }, 14 | (error) => { 15 | return Promise.reject(error); 16 | } 17 | ); 18 | 19 | // axios.interceptors.response.use( 20 | // (response) => { 21 | // return response; 22 | // }, 23 | // async (error) => { 24 | // if (error.response && error.response.status === 401) { 25 | // try { 26 | // const refreshed = await keycloak.updateToken(5); 27 | // if (refreshed) { 28 | // const retryConfig = { 29 | // ...error.config, 30 | // headers: { 31 | // ...error.config.headers, 32 | // Authorization: `Bearer ${keycloak.token}`, 33 | // }, 34 | // }; 35 | // return axios(retryConfig); 36 | // } 37 | // } catch (refreshError) { 38 | // keycloak.login(); 39 | // } 40 | // } 41 | // return Promise.reject(error); 42 | // } 43 | // ); 44 | }; 45 | -------------------------------------------------------------------------------- /ui/client/src/app/Routes.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense, lazy } from "react"; 2 | import { useParams, useRoutes } from "react-router-dom"; 3 | 4 | import { Bullseye, Spinner } from "@patternfly/react-core"; 5 | 6 | const Home = lazy(() => import("./pages/home")); 7 | const ProjectList = lazy(() => import("./pages/project-list")); 8 | const ProjectDetails = lazy(() => import("./pages/project-details")); 9 | 10 | export const ViewRepositoryRouteParam = "repositoryId"; 11 | export const ViewPackageRouteParam = "packageId"; 12 | 13 | export enum PathParam { 14 | PROJECT_ID = "projectId", 15 | } 16 | 17 | export const AppRoutes = () => { 18 | const allRoutes = useRoutes([ 19 | { path: "/", element: }, 20 | { path: "/projects", element: }, 21 | { path: `/projects/:${PathParam.PROJECT_ID}`, element: }, 22 | // { path: "/projects/:projectId/documents", element: }, 23 | // { path: "*", element: }, 24 | ]); 25 | 26 | return ( 27 | 30 | 31 | 32 | } 33 | > 34 | {allRoutes} 35 | 36 | ); 37 | }; 38 | 39 | export const useRouteParams = (pathParam: PathParam) => { 40 | const params = useParams(); 41 | return params[pathParam]; 42 | }; 43 | -------------------------------------------------------------------------------- /migration/README.md: -------------------------------------------------------------------------------- 1 | # CLI 2 | 3 | Generate migration and apply it: 4 | 5 | ```shell 6 | sea-orm-cli migrate generate NAME_OF_MIGRATION 7 | sea-orm-cli migrate -u postgres://user:password@localhost/openubl 8 | ``` 9 | 10 | Generate entity files of database `openubl` to `entity/src` 11 | 12 | ```shell 13 | sea-orm-cli generate entity -u postgres://user:password@localhost/openubl -o entity/src 14 | ``` 15 | 16 | # Running Migrator CLI 17 | 18 | - Generate a new migration file 19 | ```sh 20 | cargo run -- generate MIGRATION_NAME 21 | ``` 22 | - Apply all pending migrations 23 | ```sh 24 | cargo run 25 | ``` 26 | ```sh 27 | cargo run -- up 28 | ``` 29 | - Apply first 10 pending migrations 30 | ```sh 31 | cargo run -- up -n 10 32 | ``` 33 | - Rollback last applied migrations 34 | ```sh 35 | cargo run -- down 36 | ``` 37 | - Rollback last 10 applied migrations 38 | ```sh 39 | cargo run -- down -n 10 40 | ``` 41 | - Drop all tables from the database, then reapply all migrations 42 | ```sh 43 | cargo run -- fresh 44 | ``` 45 | - Rollback all applied migrations, then reapply all migrations 46 | ```sh 47 | cargo run -- refresh 48 | ``` 49 | - Rollback all applied migrations 50 | ```sh 51 | cargo run -- reset 52 | ``` 53 | - Check the status of all migrations 54 | ```sh 55 | cargo run -- status 56 | ``` 57 | -------------------------------------------------------------------------------- /ui/client/public/locales/en/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": { 3 | "cancel": "Cancel", 4 | "clearAllFilters": "Clear all filters", 5 | "createNew": "Create new", 6 | "delete": "Delete", 7 | "edit": "Edit", 8 | "filterBy": "Filter by {{what}}", 9 | "selectAll": "Select all", 10 | "selectNone": "Select none", 11 | "selectPage": "Select page" 12 | }, 13 | "dialog": { 14 | "message": { 15 | "delete": "This action cannot be undone" 16 | }, 17 | "title": { 18 | "deleteWithName": "Delete {{what}} \"{{name}}\"", 19 | "new": "New {{what}}" 20 | } 21 | }, 22 | "message": { 23 | "noDataAvailableBody": "No data available", 24 | "noDataAvailableTitle": "No data available", 25 | "noResultsFoundBody": "No resulst found", 26 | "noResultsFoundTitle": "No results found" 27 | }, 28 | "terms": { 29 | "cancel": "Cancel", 30 | "create": "Create", 31 | "description": "Description", 32 | "name": "Name", 33 | "project": "Project", 34 | "projectDeleted": "Project deleted", 35 | "projects": "Projects", 36 | "save": "Save" 37 | }, 38 | "toastr": { 39 | "fail": { 40 | "create": "Failed to create {{type}}.", 41 | "save": "Failed to save {{type}}." 42 | }, 43 | "success": { 44 | "created": "{{type}} was successfully created.", 45 | "saved": "{{type}} was successfully saved." 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ui/Dockerfile: -------------------------------------------------------------------------------- 1 | # Builder image 2 | FROM registry.access.redhat.com/ubi9/nodejs-18:latest as builder 3 | 4 | USER 1001 5 | COPY --chown=1001 . . 6 | RUN npm clean-install && npm run build && npm run dist 7 | 8 | # Runner image 9 | FROM registry.access.redhat.com/ubi9/nodejs-18-minimal:latest 10 | 11 | # Add ps package to allow liveness probe for k8s cluster 12 | # Add tar package to allow copying files with kubectl scp 13 | USER 0 14 | RUN microdnf -y install tar procps-ng && microdnf clean all 15 | 16 | USER 1001 17 | 18 | LABEL name="project-openubl/openubl-ui" \ 19 | description="Openubl - User Interface" \ 20 | help="For more information visit https://project-openubl.github.io/" \ 21 | license="Apache License 2.0" \ 22 | maintainer="carlosthe19916@gmail.com" \ 23 | summary="Openubl - User Interface" \ 24 | url="https://quay.io/repository/projectopenubl/openubl-ui" \ 25 | usage="podman run -p 80 -v projectopenubl/openubl-ui:latest" \ 26 | io.k8s.display-name="openubl-ui" \ 27 | io.k8s.description="Openubl - User Interface" \ 28 | io.openshift.expose-services="80:http" \ 29 | io.openshift.tags="operator,openubl,ui,nodejs18" \ 30 | io.openshift.min-cpu="100m" \ 31 | io.openshift.min-memory="350Mi" 32 | 33 | COPY --from=builder /opt/app-root/src/dist /opt/app-root/dist/ 34 | 35 | ENV DEBUG=1 36 | 37 | WORKDIR /opt/app-root/dist 38 | ENTRYPOINT ["./entrypoint.sh"] 39 | -------------------------------------------------------------------------------- /ui/client/src/app/components/AppTableActionButtons.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button, Flex, FlexItem } from "@patternfly/react-core"; 3 | import { ConditionalTooltip } from "./ConditionalTooltip"; 4 | 5 | export interface AppTableActionButtonsProps { 6 | isDeleteEnabled?: boolean; 7 | tooltipMessage?: string; 8 | onEdit: () => void; 9 | onDelete: () => void; 10 | } 11 | 12 | export const AppTableActionButtons: React.FC = ({ 13 | isDeleteEnabled = false, 14 | tooltipMessage = "", 15 | onEdit, 16 | onDelete, 17 | }) => { 18 | return ( 19 | <> 20 | 21 | 22 | 30 | 31 | 32 | 36 | 45 | 46 | 47 | 48 | 49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /ui/client/public/locales/es/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": { 3 | "cancel": "Cancelar", 4 | "clearAllFilters": "Limpiar todos los filtros", 5 | "createNew": "Crear nuevo", 6 | "delete": "Eliminar", 7 | "edit": "Editar", 8 | "filterBy": "Filtrar por {{what}}", 9 | "selectAll": "Seleccionar todos", 10 | "selectNone": "Seleccionar ninguno", 11 | "selectPage": "Seleccionar página" 12 | }, 13 | "dialog": { 14 | "message": { 15 | "delete": "Esta acción no puede ser revertida." 16 | }, 17 | "title": { 18 | "deleteWithName": "Eliminar {{what}} \"{{name}}\"?", 19 | "new": "Nuevo {{what}}" 20 | } 21 | }, 22 | "message": { 23 | "noDataAvailableBody": "No hay datos disponibles para ser mostrados aquí.", 24 | "noDataAvailableTitle": "No hay datos disponibles", 25 | "noResultsFoundBody": "Ningún resultado coincide con los criterios del filtro. Elimine todos los filtros o borre todos los filtros para mostrar los resultados.", 26 | "noResultsFoundTitle": "No se han encontrado resultados" 27 | }, 28 | "terms": { 29 | "cancel": "Cancelar", 30 | "create": "Crear", 31 | "description": "Descripción", 32 | "name": "Nombre", 33 | "project": "Proyecto", 34 | "projectDeleted": "Proyecto eliminado", 35 | "projects": "Proyectos", 36 | "save": "Guardar" 37 | }, 38 | "toastr": { 39 | "fail": { 40 | "create": "Error al crear {{type}}.", 41 | "save": "Error al guardar {{type}}." 42 | }, 43 | "success": { 44 | "created": "{{type}} fue creado.", 45 | "saved": "{{type}} fue guardado." 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ui/client/src/app/components/OidcProvider.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from "react"; 2 | import { AuthProvider, useAuth } from "react-oidc-context"; 3 | import { oidcClientSettings } from "@app/oidc"; 4 | import ENV from "@app/env"; 5 | import { AppPlaceholder } from "./AppPlaceholder"; 6 | import { initInterceptors } from "@app/axios-config"; 7 | 8 | interface IOidcProviderProps { 9 | children: React.ReactNode; 10 | } 11 | 12 | export const OidcProvider: React.FC = ({ children }) => { 13 | return ENV.AUTH_REQUIRED !== "true" ? ( 14 | <>{children} 15 | ) : ( 16 | 20 | window.history.replaceState( 21 | {}, 22 | document.title, 23 | window.location.pathname 24 | ) 25 | } 26 | > 27 | {children} 28 | 29 | ); 30 | }; 31 | 32 | const AuthEnabledOidcProvider: React.FC = ({ 33 | children, 34 | }) => { 35 | const auth = useAuth(); 36 | 37 | React.useEffect(() => { 38 | if (!auth.isAuthenticated && !auth.isLoading) { 39 | auth.signinRedirect(); 40 | } 41 | }, [auth.isAuthenticated, auth.isLoading]); 42 | 43 | React.useEffect(() => { 44 | initInterceptors(); 45 | }, []); 46 | 47 | if (auth.isAuthenticated) { 48 | return }>{children}; 49 | } else if (auth.isLoading) { 50 | return ; 51 | } else { 52 | return

Login in...

; 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /ui/client/src/app/api/models.ts: -------------------------------------------------------------------------------- 1 | export enum MimeType { 2 | TAR = "tar", 3 | YAML = "yaml", 4 | } 5 | 6 | /** Mark an object as "New" therefore does not have an `id` field. */ 7 | export type New = Omit; 8 | 9 | export interface HubFilter { 10 | field: string; 11 | operator?: "=" | "!=" | "~" | ">" | ">=" | "<" | "<="; 12 | value: 13 | | string 14 | | number 15 | | { 16 | list: (string | number)[]; 17 | operator?: "AND" | "OR"; 18 | }; 19 | } 20 | 21 | export interface HubRequestParams { 22 | filters?: HubFilter[]; 23 | sort?: { 24 | field: string; 25 | direction: "asc" | "desc"; 26 | }; 27 | page?: { 28 | pageNumber: number; // 1-indexed 29 | itemsPerPage: number; 30 | }; 31 | } 32 | 33 | export interface HubPaginatedResult { 34 | data: T[]; 35 | total: number; 36 | params: HubRequestParams; 37 | } 38 | 39 | // 40 | 41 | export interface Project { 42 | id: number; 43 | name: string; 44 | description?: string; 45 | } 46 | 47 | export interface UblDocument { 48 | id: number; 49 | file_id: string; 50 | document_type: string; 51 | document_id: string; 52 | supplier_id: string; 53 | voided_document_code?: string; 54 | } 55 | 56 | export interface Credentials { 57 | id: number; 58 | name: string; 59 | description?: string; 60 | supplier_ids_applied_to: string[]; 61 | soap?: { 62 | username_sol: string; 63 | password_sol: string; 64 | url_invoice: string; 65 | url_perception_retention: string; 66 | }; 67 | rest?: { 68 | client_id: string; 69 | client_secret: string; 70 | url_despatch: string; 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /storage/src/config.rs: -------------------------------------------------------------------------------- 1 | #[derive(clap::Subcommand, Debug)] 2 | pub enum Storage { 3 | Local(LocalStorage), 4 | Minio(MinioStorage), 5 | S3(S3Storage), 6 | } 7 | 8 | #[derive(clap::Args, Debug)] 9 | pub struct LocalStorage { 10 | #[arg( 11 | id = "storage-local-dir", 12 | long, 13 | env = "STORAGE_LOCAL_DIR", 14 | default_value = "storage" 15 | )] 16 | pub local_dir: String, 17 | } 18 | 19 | #[derive(clap::Args, Debug)] 20 | pub struct MinioStorage { 21 | #[arg(id = "storage-minio-host", long, env = "STORAGE_MINIO_HOST")] 22 | pub host: String, 23 | 24 | #[arg( 25 | id = "storage-minio-bucket", 26 | long, 27 | env = "STORAGE_MINIO_BUCKET", 28 | default_value = "openubl" 29 | )] 30 | pub bucket: String, 31 | 32 | #[arg( 33 | id = "storage-minio-access-key", 34 | long, 35 | env = "STORAGE_MINIO_ACCESS_KEY" 36 | )] 37 | pub access_key: String, 38 | 39 | #[arg( 40 | id = "storage-minio-secret-key", 41 | long, 42 | env = "STORAGE_MINIO_SECRET_KEY" 43 | )] 44 | pub secret_key: String, 45 | } 46 | 47 | #[derive(clap::Args, Debug)] 48 | pub struct S3Storage { 49 | #[arg(id = "storage-s3-region", long, env = "STORAGE_S3_REGION")] 50 | pub region: String, 51 | 52 | #[arg( 53 | id = "storage-s3-bucket", 54 | long, 55 | env = "STORAGE_S3_BUCKET", 56 | default_value = "openubl" 57 | )] 58 | pub bucket: String, 59 | 60 | #[arg(id = "storage-s3-access-key", long, env = "STORAGE_S3_ACCESS_KEY")] 61 | pub access_key: String, 62 | 63 | #[arg(id = "storage-s3-secret-key", long, env = "STORAGE_S3_SECRET_KEY")] 64 | pub secret_key: String, 65 | } 66 | -------------------------------------------------------------------------------- /migration/src/m20240117_142858_create_send_rule.rs: -------------------------------------------------------------------------------- 1 | use crate::m20240114_154538_create_credentials::Credentials; 2 | use sea_orm_migration::prelude::*; 3 | 4 | #[derive(DeriveMigrationName)] 5 | pub struct Migration; 6 | 7 | #[async_trait::async_trait] 8 | impl MigrationTrait for Migration { 9 | async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { 10 | manager 11 | .create_table( 12 | Table::create() 13 | .table(SendRule::Table) 14 | .if_not_exists() 15 | .col( 16 | ColumnDef::new(SendRule::Id) 17 | .integer() 18 | .not_null() 19 | .auto_increment() 20 | .primary_key(), 21 | ) 22 | .col(ColumnDef::new(SendRule::SupplierId).string().not_null()) 23 | .col(ColumnDef::new(SendRule::CredentialsId).integer().not_null()) 24 | .foreign_key( 25 | ForeignKey::create() 26 | .from_col(SendRule::CredentialsId) 27 | .to(Credentials::Table, Credentials::Id) 28 | .on_delete(ForeignKeyAction::Cascade), 29 | ) 30 | .to_owned(), 31 | ) 32 | .await 33 | } 34 | 35 | async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { 36 | manager 37 | .drop_table(Table::drop().table(SendRule::Table).to_owned()) 38 | .await 39 | } 40 | } 41 | 42 | #[derive(DeriveIden)] 43 | enum SendRule { 44 | Table, 45 | Id, 46 | SupplierId, 47 | CredentialsId, 48 | } 49 | -------------------------------------------------------------------------------- /ui/client/src/app/pages/project-details/project-details.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | import { 5 | Breadcrumb, 6 | BreadcrumbItem, 7 | PageSection, 8 | PageSectionVariants, 9 | Tab, 10 | TabTitleText, 11 | Tabs, 12 | Text, 13 | TextContent 14 | } from "@patternfly/react-core"; 15 | 16 | import { 17 | useFetchProjectById 18 | } from "@app/queries/projects"; 19 | 20 | import { PathParam, useRouteParams } from "@app/Routes"; 21 | import { CredentialsTable } from "./credentials/credentials-table"; 22 | 23 | export const ProjectDetails: React.FC = () => { 24 | const projectId = useRouteParams(PathParam.PROJECT_ID); 25 | const { project } = useFetchProjectById(projectId); 26 | 27 | return ( 28 | <> 29 | 30 | 31 | 32 | Proyectos 33 | 34 | 35 | Proyecto detalles 36 | 37 | 38 | 39 | 40 | 41 | {project?.name} 42 | 43 | 44 | 45 | Documentos}> 46 | documentos 47 | 48 | Credenciales}> 49 | {projectId && } 50 | 51 | 52 | 53 | 54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /ui/client/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | 4 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 5 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; 6 | 7 | import ENV from "@app/env"; 8 | import App from "@app/App"; 9 | import reportWebVitals from "@app/reportWebVitals"; 10 | 11 | import dayjs from "dayjs"; 12 | import isSameOrBefore from "dayjs/plugin/isSameOrBefore"; 13 | import utc from "dayjs/plugin/utc"; 14 | import timezone from "dayjs/plugin/timezone"; 15 | import customParseFormat from "dayjs/plugin/customParseFormat"; 16 | import { OidcProvider } from "@app/components/OidcProvider"; 17 | 18 | dayjs.extend(utc); 19 | dayjs.extend(timezone); 20 | dayjs.extend(customParseFormat); 21 | dayjs.extend(isSameOrBefore); 22 | 23 | const queryClient = new QueryClient(); 24 | 25 | const root = ReactDOM.createRoot( 26 | document.getElementById("root") as HTMLElement 27 | ); 28 | 29 | const renderApp = () => { 30 | return root.render( 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | }; 41 | 42 | if (ENV.NODE_ENV === "development") { 43 | import("./mocks/browser").then((browserMocks) => { 44 | if (browserMocks.config.enabled) { 45 | browserMocks.worker.start(); 46 | } 47 | renderApp(); 48 | }); 49 | } else { 50 | renderApp(); 51 | } 52 | 53 | // If you want to start measuring performance in your app, pass a function 54 | // to log results (for example: reportWebVitals(console.log)) 55 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 56 | reportWebVitals(); 57 | -------------------------------------------------------------------------------- /migration/src/m20240101_104121_create_document.rs: -------------------------------------------------------------------------------- 1 | use sea_orm_migration::prelude::*; 2 | 3 | #[derive(DeriveMigrationName)] 4 | pub struct Migration; 5 | 6 | #[async_trait::async_trait] 7 | impl MigrationTrait for Migration { 8 | async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { 9 | manager 10 | .create_table( 11 | Table::create() 12 | .table(Document::Table) 13 | .if_not_exists() 14 | .col( 15 | ColumnDef::new(Document::Id) 16 | .integer() 17 | .not_null() 18 | .auto_increment() 19 | .primary_key(), 20 | ) 21 | .col(ColumnDef::new(Document::FileId).string().not_null()) 22 | .col(ColumnDef::new(Document::SupplierId).string().not_null()) 23 | .col(ColumnDef::new(Document::Identifier).string().not_null()) 24 | .col(ColumnDef::new(Document::Type).string().not_null()) 25 | .col(ColumnDef::new(Document::VoidedDocumentCode).string()) 26 | .col(ColumnDef::new(Document::DigestValue).string()) 27 | .col(ColumnDef::new(Document::Sha256).string().not_null()) 28 | .to_owned(), 29 | ) 30 | .await 31 | } 32 | 33 | async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { 34 | manager 35 | .drop_table(Table::drop().table(Document::Table).to_owned()) 36 | .await 37 | } 38 | } 39 | 40 | #[derive(DeriveIden)] 41 | pub enum Document { 42 | Table, 43 | Id, 44 | FileId, 45 | Type, 46 | Identifier, 47 | SupplierId, 48 | VoidedDocumentCode, 49 | DigestValue, 50 | Sha256, 51 | } 52 | -------------------------------------------------------------------------------- /ui/client/src/app/hooks/table-controls/getPaginationHubRequestParams.ts: -------------------------------------------------------------------------------- 1 | import { HubRequestParams } from "@app/api/models"; 2 | import { PaginationState } from "@mturley-latest/react-table-batteries"; 3 | 4 | /** 5 | * Args for getPaginationHubRequestParams 6 | * - Partially satisfied by the object returned by useTableControlState (ITableControlState) 7 | */ 8 | export interface IGetPaginationHubRequestParamsArgs { 9 | /** 10 | * The "source of truth" state for the pagination feature (returned by usePaginationState) 11 | */ 12 | pagination?: PaginationState; 13 | } 14 | 15 | /** 16 | * Given the state for the pagination feature and additional arguments, returns params the hub API needs to apply the current pagination. 17 | * - Makes up part of the object returned by getHubRequestParams 18 | * @see getHubRequestParams 19 | */ 20 | export const getPaginationHubRequestParams = ({ 21 | pagination: paginationState, 22 | }: IGetPaginationHubRequestParamsArgs): Partial => { 23 | if (!paginationState) return {}; 24 | const { pageNumber, itemsPerPage } = paginationState; 25 | return { page: { pageNumber, itemsPerPage } }; 26 | }; 27 | 28 | /** 29 | * Converts the values returned by getPaginationHubRequestParams into the URL query strings expected by the hub API 30 | * - Appends converted URL params to the given `serializedParams` object for use in the hub API request 31 | * - Constructs part of the object returned by serializeRequestParamsForHub 32 | * @see serializeRequestParamsForHub 33 | */ 34 | export const serializePaginationRequestParamsForHub = ( 35 | deserializedParams: HubRequestParams, 36 | serializedParams: URLSearchParams 37 | ) => { 38 | const { page } = deserializedParams; 39 | if (page) { 40 | const { pageNumber, itemsPerPage } = page; 41 | serializedParams.append("limit", String(itemsPerPage)); 42 | serializedParams.append("offset", String((pageNumber - 1) * itemsPerPage)); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /ui/client/src/app/components/HookFormPFFields/HookFormPFTextArea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { FieldValues, Path } from "react-hook-form"; 3 | import { TextArea, TextAreaProps } from "@patternfly/react-core"; 4 | import { getValidatedFromErrors } from "@app/utils/utils"; 5 | import { 6 | HookFormPFGroupController, 7 | BaseHookFormPFGroupControllerProps, 8 | extractGroupControllerProps, 9 | } from "./HookFormPFGroupController"; 10 | 11 | export type HookFormPFTextAreaProps< 12 | TFieldValues extends FieldValues, 13 | TName extends Path, 14 | > = TextAreaProps & BaseHookFormPFGroupControllerProps; 15 | 16 | export const HookFormPFTextArea = < 17 | TFieldValues extends FieldValues = FieldValues, 18 | TName extends Path = Path, 19 | >( 20 | props: HookFormPFTextAreaProps 21 | ) => { 22 | const { extractedProps, remainingProps } = extractGroupControllerProps< 23 | TFieldValues, 24 | TName, 25 | HookFormPFTextAreaProps 26 | >(props); 27 | const { fieldId, helperText, isRequired, errorsSuppressed } = extractedProps; 28 | return ( 29 | 30 | {...extractedProps} 31 | renderInput={({ 32 | field: { onChange, onBlur, value, name, ref }, 33 | fieldState: { isDirty, error, isTouched }, 34 | }) => ( 35 |