From 2a65387ddc466961930a64a4131747dee72d0a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Mon, 1 Apr 2024 14:51:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(project):=E6=94=BE=E7=BD=AE=E5=85=A8?= =?UTF-8?q?=E6=96=B0=E7=9A=84=E9=A1=B9=E7=9B=AE=E7=BB=93=E6=9E=84=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 303 +++++++++++++++++++++++++++++++ cert-tools/Cargo.toml | 9 + cert-tools/src/main.rs | 3 + cert_lib/Cargo.toml | 11 ++ cert_lib/src/errors.rs | 9 + cert_lib/src/lib.rs | 101 +++++++++++ cert_lib/src/root_certificate.rs | 42 +++++ license-server/Cargo.toml | 9 + license-server/src/main.rs | 3 + license-ui/.eslintrc.cjs | 18 ++ license-ui/.gitignore | 24 +++ license-ui/README.md | 30 +++ license-ui/bun.lockb | Bin 0 -> 77938 bytes license-ui/index.html | 13 ++ license-ui/package.json | 28 +++ license-ui/public/vite.svg | 1 + license-ui/src/App.css | 42 +++++ license-ui/src/App.tsx | 35 ++++ license-ui/src/assets/react.svg | 1 + license-ui/src/index.css | 68 +++++++ license-ui/src/main.tsx | 10 + license-ui/src/vite-env.d.ts | 1 + license-ui/tsconfig.json | 25 +++ license-ui/tsconfig.node.json | 11 ++ license-ui/vite.config.ts | 7 + 25 files changed, 804 insertions(+) create mode 100644 .gitignore create mode 100644 cert-tools/Cargo.toml create mode 100644 cert-tools/src/main.rs create mode 100644 cert_lib/Cargo.toml create mode 100644 cert_lib/src/errors.rs create mode 100644 cert_lib/src/lib.rs create mode 100644 cert_lib/src/root_certificate.rs create mode 100644 license-server/Cargo.toml create mode 100644 license-server/src/main.rs create mode 100644 license-ui/.eslintrc.cjs create mode 100644 license-ui/.gitignore create mode 100644 license-ui/README.md create mode 100755 license-ui/bun.lockb create mode 100644 license-ui/index.html create mode 100644 license-ui/package.json create mode 100644 license-ui/public/vite.svg create mode 100644 license-ui/src/App.css create mode 100644 license-ui/src/App.tsx create mode 100644 license-ui/src/assets/react.svg create mode 100644 license-ui/src/index.css create mode 100644 license-ui/src/main.tsx create mode 100644 license-ui/src/vite-env.d.ts create mode 100644 license-ui/tsconfig.json create mode 100644 license-ui/tsconfig.node.json create mode 100644 license-ui/vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53186e1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,303 @@ + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/cert-tools/Cargo.toml b/cert-tools/Cargo.toml new file mode 100644 index 0000000..fe80359 --- /dev/null +++ b/cert-tools/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "cert_tools" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cert_lib = { path = "../cert_lib" } \ No newline at end of file diff --git a/cert-tools/src/main.rs b/cert-tools/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/cert-tools/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/cert_lib/Cargo.toml b/cert_lib/Cargo.toml new file mode 100644 index 0000000..d21ecfa --- /dev/null +++ b/cert_lib/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "cert_lib" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.81" +openssl = "0.10.64" +thiserror = "1.0.58" diff --git a/cert_lib/src/errors.rs b/cert_lib/src/errors.rs new file mode 100644 index 0000000..af73a78 --- /dev/null +++ b/cert_lib/src/errors.rs @@ -0,0 +1,9 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum CertificateGenerateError { + #[error("Error generating RSA key")] + X509Error(#[from] openssl::error::ErrorStack), + #[error("IO Error")] + IOError(#[from] std::io::Error), +} diff --git a/cert_lib/src/lib.rs b/cert_lib/src/lib.rs new file mode 100644 index 0000000..62ad555 --- /dev/null +++ b/cert_lib/src/lib.rs @@ -0,0 +1,101 @@ +use std::fs::{self, File}; +use std::io::{BufWriter, Write}; +use std::path::PathBuf; +use std::{io, path::Path}; + +use openssl::bn::BigNumContext; +use openssl::x509::X509; +use openssl::{ + asn1::{Asn1Integer, Asn1Time}, + bn::BigNum, + pkey::PKey, + rsa::Rsa, + x509::X509Builder, +}; + +pub mod errors; +mod root_certificate; + +/// 生成证书,公钥保存为.pem文件,私钥保存为.key文件 +/// +/// - `storage_path`:证书存储路径 +/// - `certificate_name`:证书名称 +/// - `key_length`:密钥长度 +/// - `available_days`:证书有效天数 +/// - `version`:证书版本 +/// - `serial_number`:证书序列号,如果不设定则默认为1 +pub fn generate_certificate( + storage_path: &str, + certificate_name: &str, + key_length: u32, + available_days: u32, + version: i32, + serial_number: Option, +) -> anyhow::Result<()> { + let rsa = Rsa::generate(key_length)?; + let private_key = rsa.private_key_to_pem()?; + let mut builder = X509Builder::new()?; + builder.set_version(version)?; + let pkey = PKey::from_rsa(rsa)?; + builder.set_pubkey(&pkey)?; + + let not_before = Asn1Time::days_from_now(0)?; + let not_after = Asn1Time::days_from_now(available_days)?; + builder.set_not_before(¬_before)?; + builder.set_not_after(¬_after)?; + let serial_number = BigNum::from_u32(serial_number.unwrap_or(1))?; + let sn = Asn1Integer::from_bn(&serial_number)?; + builder.set_serial_number(&sn)?; + + let certificate = builder.build(); + + let store_path = PathBuf::from(storage_path); + ensure_path_exists(&store_path).unwrap(); + + let cert_path = store_path.clone().join(format!("{}.pem", certificate_name)); + let cert_file = File::create(cert_path)?; + let mut writer = BufWriter::new(cert_file); + writer.write_all(&certificate.to_pem()?)?; + writer.flush()?; + + let private_key_path = store_path.join(format!("{}.key", certificate_name)); + let private_key_file = File::create(private_key_path)?; + let mut writer = BufWriter::new(private_key_file); + writer.write_all(&private_key)?; + writer.flush()?; + + Ok(()) +} + +/// 确保指定路径存在,如果不存在则创建 +/// +/// - `target_path`:目标路径 +fn ensure_path_exists>(target_path: P) -> Result<(), io::Error> { + let path = target_path.as_ref(); + if !path.exists() { + fs::create_dir_all(path)?; + } + + Ok(()) +} + +/// 计算用于net-filter中power插件中Equal所使用的值。 +pub fn calculate_power_euqal_result(cert: X509) -> anyhow::Result { + let x = BigNum::from_slice(cert.signature().as_slice())?; + let y = BigNum::from_u32(65537)?; + let root_cert_pub_key = root_certificate::x509_certificate()?.public_key()?.rsa()?; + let z = root_cert_pub_key.n(); + let cert_pub_key = cert.public_key()?.rsa()?; + let n = cert_pub_key.n(); + + let mut ctx = BigNumContext::new()?; + let mut result = BigNum::new()?; + result.mod_exp(&x, &y, &n, &mut ctx)?; + Ok(format!( + "EQUAL,{},{},{}->{}", + x.to_dec_str()?, + y.to_dec_str()?, + z.to_dec_str()?, + result.to_dec_str()? + )) +} diff --git a/cert_lib/src/root_certificate.rs b/cert_lib/src/root_certificate.rs new file mode 100644 index 0000000..bce7cd9 --- /dev/null +++ b/cert_lib/src/root_certificate.rs @@ -0,0 +1,42 @@ +use openssl::{error::ErrorStack, x509::X509}; + +/// 根证书内容。 +fn certificate() -> String { + String::from( + r#"-----BEGIN CERTIFICATE----- +MIIFOzCCAyOgAwIBAgIJANJssYOyg3nhMA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV +BAMMDUpldFByb2ZpbGUgQ0EwHhcNMTUxMDAyMTEwMDU2WhcNNDUxMDI0MTEwMDU2 +WjAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBMIICIjANBgkqhkiG9w0BAQEFAAOC +Ag8AMIICCgKCAgEA0tQuEA8784NabB1+T2XBhpB+2P1qjewHiSajAV8dfIeWJOYG +y+ShXiuedj8rL8VCdU+yH7Ux/6IvTcT3nwM/E/3rjJIgLnbZNerFm15Eez+XpWBl +m5fDBJhEGhPc89Y31GpTzW0vCLmhJ44XwvYPntWxYISUrqeR3zoUQrCEp1C6mXNX +EpqIGIVbJ6JVa/YI+pwbfuP51o0ZtF2rzvgfPzKtkpYQ7m7KgA8g8ktRXyNrz8bo +iwg7RRPeqs4uL/RK8d2KLpgLqcAB9WDpcEQzPWegbDrFO1F3z4UVNH6hrMfOLGVA +xoiQhNFhZj6RumBXlPS0rmCOCkUkWrDr3l6Z3spUVgoeea+QdX682j6t7JnakaOw +jzwY777SrZoi9mFFpLVhfb4haq4IWyKSHR3/0BlWXgcgI6w6LXm+V+ZgLVDON52F +LcxnfftaBJz2yclEwBohq38rYEpb+28+JBvHJYqcZRaldHYLjjmb8XXvf2MyFeXr +SopYkdzCvzmiEJAewrEbPUaTllogUQmnv7Rv9sZ9jfdJ/cEn8e7GSGjHIbnjV2ZM +Q9vTpWjvsT/cqatbxzdBo/iEg5i9yohOC9aBfpIHPXFw+fEj7VLvktxZY6qThYXR +Rus1WErPgxDzVpNp+4gXovAYOxsZak5oTV74ynv1aQ93HSndGkKUE/qA/JECAwEA +AaOBhzCBhDAdBgNVHQ4EFgQUo562SGdCEjZBvW3gubSgUouX8bMwSAYDVR0jBEEw +P4AUo562SGdCEjZBvW3gubSgUouX8bOhHKQaMBgxFjAUBgNVBAMMDUpldFByb2Zp +bGUgQ0GCCQDSbLGDsoN54TAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkq +hkiG9w0BAQsFAAOCAgEAjrPAZ4xC7sNiSSqh69s3KJD3Ti4etaxcrSnD7r9rJYpK +BMviCKZRKFbLv+iaF5JK5QWuWdlgA37ol7mLeoF7aIA9b60Ag2OpgRICRG79QY7o +uLviF/yRMqm6yno7NYkGLd61e5Huu+BfT459MWG9RVkG/DY0sGfkyTHJS5xrjBV6 +hjLG0lf3orwqOlqSNRmhvn9sMzwAP3ILLM5VJC5jNF1zAk0jrqKz64vuA8PLJZlL +S9TZJIYwdesCGfnN2AETvzf3qxLcGTF038zKOHUMnjZuFW1ba/12fDK5GJ4i5y+n +fDWVZVUDYOPUixEZ1cwzmf9Tx3hR8tRjMWQmHixcNC8XEkVfztID5XeHtDeQ+uPk +X+jTDXbRb+77BP6n41briXhm57AwUI3TqqJFvoiFyx5JvVWG3ZqlVaeU/U9e0gxn +8qyR+ZA3BGbtUSDDs8LDnE67URzK+L+q0F2BC758lSPNB2qsJeQ63bYyzf0du3wB +/gb2+xJijAvscU3KgNpkxfGklvJD/oDUIqZQAnNcHe7QEf8iG2WqaMJIyXZlW3me +0rn+cgvxHPt6N4EBh5GgNZR4l0eaFEV+fxVsydOQYo1RIyFMXtafFBqQl6DDxujl +FeU3FZ+Bcp12t7dlM4E0/sS1XdL47CfGVj4Bp+/VbF862HmkAbd7shs7sDQkHbU= +-----END CERTIFICATE-----"#, + ) +} + +/// 获取x509格式根证书。 +pub fn x509_certificate() -> Result { + X509::from_pem(certificate().as_bytes()) +} diff --git a/license-server/Cargo.toml b/license-server/Cargo.toml new file mode 100644 index 0000000..d938071 --- /dev/null +++ b/license-server/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "license_server" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cert_lib = { path = "../cert_lib" } \ No newline at end of file diff --git a/license-server/src/main.rs b/license-server/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/license-server/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/license-ui/.eslintrc.cjs b/license-ui/.eslintrc.cjs new file mode 100644 index 0000000..d6c9537 --- /dev/null +++ b/license-ui/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/license-ui/.gitignore b/license-ui/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/license-ui/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/license-ui/README.md b/license-ui/README.md new file mode 100644 index 0000000..0d6babe --- /dev/null +++ b/license-ui/README.md @@ -0,0 +1,30 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default { + // other rules... + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + }, +} +``` + +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/license-ui/bun.lockb b/license-ui/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..8576f74aa4d71c941b821df69344d4c7712a57c1 GIT binary patch literal 77938 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 literal 0 HcmV?d00001 diff --git a/license-ui/index.html b/license-ui/index.html new file mode 100644 index 0000000..e4b78ea --- /dev/null +++ b/license-ui/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/license-ui/package.json b/license-ui/package.json new file mode 100644 index 0000000..4ad608e --- /dev/null +++ b/license-ui/package.json @@ -0,0 +1,28 @@ +{ + "name": "license-ui", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } +} diff --git a/license-ui/public/vite.svg b/license-ui/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/license-ui/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/license-ui/src/App.css b/license-ui/src/App.css new file mode 100644 index 0000000..b9d355d --- /dev/null +++ b/license-ui/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/license-ui/src/App.tsx b/license-ui/src/App.tsx new file mode 100644 index 0000000..afe48ac --- /dev/null +++ b/license-ui/src/App.tsx @@ -0,0 +1,35 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import viteLogo from '/vite.svg' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( + <> + +

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+ + ) +} + +export default App diff --git a/license-ui/src/assets/react.svg b/license-ui/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/license-ui/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/license-ui/src/index.css b/license-ui/src/index.css new file mode 100644 index 0000000..6119ad9 --- /dev/null +++ b/license-ui/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/license-ui/src/main.tsx b/license-ui/src/main.tsx new file mode 100644 index 0000000..3d7150d --- /dev/null +++ b/license-ui/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/license-ui/src/vite-env.d.ts b/license-ui/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/license-ui/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/license-ui/tsconfig.json b/license-ui/tsconfig.json new file mode 100644 index 0000000..a7fc6fb --- /dev/null +++ b/license-ui/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/license-ui/tsconfig.node.json b/license-ui/tsconfig.node.json new file mode 100644 index 0000000..97ede7e --- /dev/null +++ b/license-ui/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/license-ui/vite.config.ts b/license-ui/vite.config.ts new file mode 100644 index 0000000..861b04b --- /dev/null +++ b/license-ui/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react-swc' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +})