Compare commits

..

9 Commits

Author SHA1 Message Date
徐涛
f4edb07629 feat(build):完成Docker镜像的编译配置和构建。 2024-04-07 22:26:39 +08:00
徐涛
9b9d222546 build(server):增加使用Docker部署功能。 2024-04-07 21:40:35 +08:00
徐涛
86d7823aae fix(ui):修正影响编译的问题。 2024-04-07 21:38:05 +08:00
徐涛
019652ca67 enhance(ui): 2024-04-07 18:42:21 +08:00
徐涛
6d97d437a2 feat(ui):增加产品伴随选择。 2024-04-07 17:33:45 +08:00
徐涛
2af30101cc enhance(server):更新产品数据。 2024-04-07 17:24:34 +08:00
徐涛
d5e46e186e enhance(server):增补产品数据。 2024-04-07 17:09:06 +08:00
徐涛
475524d36d feat(tools):增加支持直接生成新的netfilter压缩包文件。 2024-04-07 16:32:09 +08:00
徐涛
4413b6a72c build(tools):增加操作zip文件的支持。 2024-04-07 16:31:32 +08:00
15 changed files with 814 additions and 244 deletions

47
Dockerfile Normal file
View File

@@ -0,0 +1,47 @@
FROM rust:1.77-bullseye AS builder
ADD sources.list /etc/apt/
RUN apt-get update && apt-get install -y \
libc6-dev \
libclang-dev \
pkg-config \
libssl-dev
ADD crates.conf /root/.cargo/config
RUN USER=root cargo new --bin license_service
ADD ./cert_lib /cert_lib
WORKDIR /license_service
COPY ./license_server/Cargo.toml ./Cargo.toml
RUN cargo build --release && rm src/*.rs target/release/deps/license_server*
ADD ./license_server/src ./src
RUN cargo build --release
FROM debian:bullseye-slim AS deployer
ADD sources.list /etc/apt/
RUN apt-get update && \
apt-get install -y ca-certificates tzdata && \
rm -rf /var/lib/apt/lists/*
EXPOSE 8080
ENV TZ=Asia/Shanghai \
APP_USER=license_usr
RUN groupadd service && \
useradd -g service $APP_USER && \
mkdir -p /license_service
COPY --from=builder /license_service/target/release/license_server /license_service/license_server
RUN chown -R $APP_USER:service /license_service
USER $APP_USER
WORKDIR /license_service
VOLUME ["/license_service/license.key", "/license_service/license.pem", "/license_service/netfilter.zip", "/license_service/products.json"]
CMD ["./license_server"]

View File

@@ -10,3 +10,4 @@ cert_lib = { path = "../cert_lib" }
clap = { version = "4.5.4", features = ["derive"] }
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.115"
zip = "0.6.6"

View File

@@ -1,10 +1,11 @@
use std::{
fs::File,
io::{BufWriter, Write},
io::{BufWriter, Read, Write},
path::PathBuf,
};
use clap::Args;
use zip::{write::FileOptions, ZipWriter};
#[derive(Debug, Args)]
pub struct PowerEqualResultOption {
@@ -12,13 +13,70 @@ pub struct PowerEqualResultOption {
key_file: PathBuf,
#[arg(short, long, default_value = "false", help = "Export to power.conf")]
export: bool,
#[arg(
short,
long,
default_value = "false",
help = "Replace power.conf in given zip file, will override export option"
)]
zip_file: Option<PathBuf>,
}
pub fn calculate_equal_result(options: PowerEqualResultOption) {
let cert = cert_lib::load_certificate(options.key_file).expect("load certificate failed");
let result =
cert_lib::calculate_power_euqal_result(cert).expect("calculate equal result failed");
if options.export {
if let Some(zip_file) = options.zip_file {
let mut buffer: Vec<u8> = Vec::new();
{
let mut buf_writer = BufWriter::new(&mut buffer);
buf_writer
.write("[Args]\n\n[Result]\n".as_bytes())
.expect("write power.conf failed");
buf_writer
.write(result.as_bytes())
.expect("write power.conf failed");
buf_writer.flush().expect("write power.conf failed");
}
// 将zip_file的文件名增加一个`_replaced`后缀并形成一个新的位于相同目录的PathBuf实例。
let mut zip_file_path = zip_file.clone();
let file_name = zip_file_path.file_stem().unwrap().to_str().unwrap();
let mut zip_file_name = file_name.to_string();
zip_file_name.push_str("_replaced.");
zip_file_name.push_str(&zip_file_path.extension().unwrap().to_string_lossy());
zip_file_path.set_file_name(zip_file_name);
// 创建一个新的zip文件并将zip_file的内容复制到新的zip文件中。
let zip_file = File::open(zip_file).expect("open zip file failed");
let mut zip_file = zip::ZipArchive::new(zip_file).expect("open zip file failed");
let zip_file_path = File::create(zip_file_path).expect("create zip file failed");
let mut zip_file_writer = ZipWriter::new(zip_file_path);
for i in 0..zip_file.len() {
let mut file = zip_file.by_index(i).expect("get file from zip failed");
let options = FileOptions::default()
.compression_method(file.compression())
.unix_permissions(file.unix_mode().unwrap());
let file_name = file.name().to_owned();
let file_name = file_name.replace("power.conf", "power.conf");
let content = if file_name.ends_with("power.conf") {
buffer.clone()
} else {
let mut content = Vec::new();
file.read_to_end(&mut content).expect("read file failed");
content
};
zip_file_writer
.start_file(file_name, options)
.expect("start output file failed");
zip_file_writer
.write_all(&content)
.expect("write output file failed");
}
zip_file_writer.finish().expect("write zip file failed");
} else if options.export {
let mut power_conf_path = PathBuf::new();
power_conf_path.push(".");
power_conf_path.push("power.conf");

14
crates.conf Normal file
View File

@@ -0,0 +1,14 @@
[source.crates-io]
# To use sparse index, change 'rsproxy' to 'rsproxy-sparse'
replace-with = 'rsproxy-sparse'
[source.rsproxy]
registry = "https://rsproxy.cn/crates.io-index"
[source.rsproxy-sparse]
registry = "sparse+https://rsproxy.cn/index/"
[registries.rsproxy]
index = "https://rsproxy.cn/crates.io-index"
[net]
git-fetch-with-cli = true

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,3 @@
#![feature(diagnostic_namespace)]
#![allow(dead_code)]
use tracing::{error, info};

View File

@@ -7,6 +7,7 @@ use tokio::{fs::File, io::AsyncReadExt};
pub struct Product {
pub id: String,
pub name: String,
pub couple: Vec<String>,
}
static PRODUCTS: OnceLock<Vec<Product>> = OnceLock::new();

View File

@@ -9,9 +9,9 @@ export function LicenseCode() {
const licenseCode = useLicenseCodeStore((state) => state.licenceCode);
const copyLicenseCode = async () => {
if (not(isEmpty(licenseCode))) {
await navigator.clipboard.writeText(licenseCode);
await navigator.clipboard.writeText(licenseCode ?? "");
notifications.show({
title: "授权码已复制",
message: "授权码已复制",
color: "green",
});
}

View File

@@ -80,7 +80,7 @@ async function generateLicense(form: LicenseInfoForm, selectedProducts: string[]
const validDays = now.add(validYears, "year").diff(now, "day");
if (isEmpty(selectedProducts)) {
notifications.show({
title: "至少需要选择一个产品",
message: "至少需要选择一个产品",
color: "red",
});
return "";

View File

@@ -14,3 +14,6 @@
overflow-y: auto;
min-height: 0;
}
.product-item {
cursor: pointer;
}

View File

@@ -91,7 +91,13 @@ function ProductItem(props: ProductItemProps) {
}, [props.id, selected, append, remove]);
return (
<Box px="sm" py="xs" bg={selected ? "green" : "gray"} onClick={handleSelectAction}>
<Box
px="sm"
py="xs"
bg={selected ? "green" : "gray"}
className={classes["product-item"]}
onClick={handleSelectAction}
>
<Text size="xs" c={selected ? "white" : "gray"}>
{props.name}
</Text>

View File

@@ -1,9 +1,10 @@
import { concat, pluck, uniq } from "ramda";
import { concat, find, pluck, propEq, uniq } from "ramda";
import { create } from "zustand";
interface Product {
id: string;
name: string;
couple: string[];
}
interface ProductsStore {
@@ -18,8 +19,12 @@ interface ProductsStore {
export const useProductsStore = create<ProductsStore>((set, get) => ({
products: [],
selectedProducts: [],
append: (code: string) =>
set((state) => ({ selectedProducts: [...state.selectedProducts, code] })),
append: (code: string) => {
const selectedProduct: Product | undefined = find(propEq(code, "id"), get().products);
set((state) => ({
selectedProducts: uniq([...state.selectedProducts, code, ...(selectedProduct?.couple ?? [])]),
}));
},
remove: (code: string) =>
set((state) => ({ selectedProducts: state.selectedProducts.filter((item) => item !== code) })),
unselectAll: () => set({ selectedProducts: [] }),

View File

@@ -4,5 +4,11 @@ export const theme = createTheme({
focusRing: "never",
fontSmoothing: true,
defaultRadius: "xs",
lineHeights: "xs",
lineHeights: {
xs: "1.2",
sm: "1.25",
md: "1.35",
lg: "1.4",
xl: "1.5",
},
});

View File

@@ -1,21 +1,5 @@
import cx, { ClassDictionary } from "clsx";
import { defaultTo, isEmpty, isNil, prop } from "ramda";
import { ChangeHandler } from "react-hook-form";
export function convertFormEvent(
name: string,
event: InputEvent,
property: string = "value",
defaultValue?: unknown = null
): Parameters<ChangeHandler> {
return {
target: {
name,
value: defaultTo(defaultValue)(prop(property, event.currentTarget)),
},
type: event.type,
};
}
import { isEmpty, isNil, prop } from "ramda";
export function composite(classesDefination: ClassDictionary, ...classes: string[]) {
/** @type {import("clsx").ClassArray} */

15
sources.list Normal file
View File

@@ -0,0 +1,15 @@
deb http://mirrors.163.com/debian/ bullseye main non-free contrib
deb http://mirrors.163.com/debian/ bullseye-updates main non-free contrib
deb http://mirrors.163.com/debian/ bullseye-backports main non-free contrib
deb-src http://mirrors.163.com/debian/ bullseye main non-free contrib
deb-src http://mirrors.163.com/debian/ bullseye-updates main non-free contrib
deb-src http://mirrors.163.com/debian/ bullseye-backports main non-free contrib
#deb http://mirrors.163.com/debian-security/ bullseye/updates main non-free contrib
#deb-src http://mirrors.163.com/debian-security/ bullseye/updates main non-free contrib