commit 2a65387ddc466961930a64a4131747dee72d0a8a Author: 徐涛 Date: Mon Apr 1 14:51:43 2024 +0800 feat(project):放置全新的项目结构。 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 0000000..8576f74 Binary files /dev/null and b/license-ui/bun.lockb differ 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()], +})