jetbra-server-go 用golang 重复造个轮子吧

为在openwrt上食用,故用golang 翻译 novice.li 大佬的 jetbra-server,先表示感谢,配合jetbra-agent食用,很完美,再次感谢,(PS:简单测试就没自己写前端,偷偷借用了一下前端文件)

//
package main

import (
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"math/big"
	"net/http"

	"github.com/gin-gonic/gin"
)

var privateKey *rsa.PrivateKey
var crt *x509.Certificate

type License struct {
	LicenseID          string    `json:"licenseId"`
	LicenseeName       string    `json:"licenseeName"`
	AssigneeName       string    `json:"assigneeName"`
	AssigneeEmail      string    `json:"assigneeEmail"`
	LicenseRestriction string    `json:"licenseRestriction"`
	CheckConcurrentUse bool      `json:"checkConcurrentUse"`
	Products           []Product `json:"products"`
	Metadata           string    `json:"metadata"`
	Hash               string    `json:"hash"`
	GracePeriodDays    int       `json:"gracePeriodDays"`
	AutoProlongated    bool      `json:"autoProlongated"`
	IsAutoProlongated  bool      `json:"isAutoProlongated"`
}

type Product struct {
	Code         string `json:"code"`
	FallbackDate string `json:"fallbackDate"`
	PaidUpTo     string `json:"paidUpTo"`
	Extended     bool   `json:"extended"`
}

func generateLicenseID() string {
	const allowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	const licenseLength = 10
	b := make([]byte, licenseLength)
	for i := range b {
		index, _ := rand.Int(rand.Reader, big.NewInt(int64(len(allowedCharacters))))
		b[i] = allowedCharacters[index.Int64()]
	}
	return string(b)
}

func generateLicense(c *gin.Context) {

	var license License
	if err := c.ShouldBindJSON(&license); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	license.LicenseID = generateLicenseID()
	licenseStr, _ := json.Marshal(license)
	fmt.Printf("licenseStr:%s\n", licenseStr)
	// Sign the license using SHA1withRSA
	hashed := sha1.Sum(licenseStr)
	signature, _ := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA1, hashed[:])

	licensePartBase64 := base64.StdEncoding.EncodeToString(licenseStr)
	signatureBase64 := base64.StdEncoding.EncodeToString(signature)
	crtBase64 := base64.StdEncoding.EncodeToString(crt.Raw)

	licenseResult := fmt.Sprintf("%s-%s-%s-%s", license.LicenseID, licensePartBase64, signatureBase64, crtBase64)
	fmt.Printf("licenseResult:%s\n", licenseResult)
	c.JSON(http.StatusOK, gin.H{"license": licenseResult})
}

func index(c *gin.Context) {
	c.HTML(http.StatusOK, "/index.html", gin.H{
		"title":        "请选择",
		"licenseeName": "Evaluator",
		"assigneeName": "Evaluator",
		"expiryDate":   "2099-12-31",
	})
}

func cors() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := c.Request.Method
		origin := c.Request.Header.Get("Origin")

		if origin != "" {
			c.Header("Access-Control-Allow-Origin", "*") // 可将将 * 替换为指定的域名
			c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
			c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
			c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
			c.Header("Access-Control-Allow-Credentials", "true")
		}
		if method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
		}
		c.Next()
	}
}
func main() {
	// Load private key and certificate
	privateKeyPEM := []byte(`-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----`)
	block, _ := pem.Decode(privateKeyPEM)
	privateKey, _ = x509.ParsePKCS1PrivateKey(block.Bytes)

	crtPEM := []byte(`-----BEGIN CERTIFICATE-----
MIIEtTCCAp2gAwIBAgIUDyuccmylba71lZQAQic5TJiAhwwwDQYJKoZIhvcNAQEL
BQAwGDEWMBQGA1UEAwwNSmV0UHJvZmlsZSBDQTAeFw0yMzA5MjkxNDA2MTJaFw0z
MzA5MjcxNDA2MTJaMBExDzANBgNVBAMMBk5vdmljZTCCAiIwDQYJKoZIhvcNAQEB
BQADggIPADCCAgoCggIBALenqcGP2ZxGkYqmKA9c4Hzf8+YD1smvmOxKjd+bmTLr
utM/hXv1cj1rW3/lqyDtdDk7K6W8/TDq1CRrEt+Do6l30DxhAiC34aH8DmGwgq77
xEoLimvH5LpePxflF+tbB1RZtFgFDOIYLdSQaKFH2JDgVKxhLiV3S6jniPhkCtWW
rTs+E6vq4N15Bm3NnM5AJILqjtUbOjNfaxVq6RrOoTc0R3Fqqo6yvxo/+JYa2UnH
IC+r2dbKuDLMUrtgnydEUdJNX0zH9FtcdELvr48uc9mY038TWUsZUK1pnQbxA2bP
yA4qnYJ9IvUgO6LtLXvGFm137YQMS1N41AHDBOrwoNI8UoDX+qI3rM96biFOFvn7
Edky7rByzybt3H+zxdojfjvpL1E0NO98BT9zfufHAaAxZtlmDOu5LDJe3CGurnyR
MRExbtc+Qjl1mUh6tG4lakAwdsoxry0GdG72yaYyb9it53kaFks/T/s7Z7bRJzVF
zQDV1Y4bzUtk43vKm2vztBVlQkBkZY5f2Jbe5Ig3b8swQzBnOT0mrL5SPUhwmQ6I
xkEWztj55OEujBMmRr92oESuq9ZYMaeLidKWVR3/++HA8BRZaRGEKtSHZCbFEFdi
hDxxJv9Xh6NuT/ewJ6HYp+0NQpFnUnJ72n8wV+tudpam7aKcdzVmz7cNwOhG2Ls7
AgMBAAEwDQYJKoZIhvcNAQELBQADggIBAIdeaQfKni7tXtcywC3zJvGzaaj242pS
WB1y40HW8jub0uHjTLsBPX27iA/5rb+rNXtUWX/f2K+DU4IgaIiiHhkDrMsw7piv
azqwA9h7/uA0A5nepmTYf/HY4W6P2stbeqInNsFRZXS7Jg4Q5LgEtHKo/H8USjtV
w9apmE3BCElkXRuelXMsSllpR/JEVv/8NPLmnHSY02q4KMVW2ozXtaAxSYQmZswy
P1YnBcnRukoI4igobpcKQXwGoQCIUlec8LbFXYM9V2eNCwgABqd4r67m7QJq31Y/
1TJysQdMH+hoPFy9rqNCxSq3ptpuzcYAk6qVf58PrrYH/6bHwiYPAayvvdzNPOhM
9OCwomfcazhK3y7HyS8aBLntTQYFf7vYzZxPMDybYTvJM+ClCNnVD7Q9fttIJ6eM
XFsXb8YK1uGNjQW8Y4WHk1MCHuD9ZumWu/CtAhBn6tllTQWwNMaPOQvKf1kr1Kt5
etrONY+B6O+Oi75SZbDuGz7PIF9nMPy4WB/8XgKdVFtKJ7/zLIPHgY8IKgbx/VTz
6uBhYo8wOf3xzzweMnn06UcfV3JGNvtMuV4vlkZNNxXeifsgzHugCvJX0nybhfBh
fIqVyfK6t0eKJqrvp54XFEtJGR+lf3pBfTdcOI6QFEPKGZKoQz8Ck+BC/WBDtbjc
/uYKczZ8DKZu
-----END CERTIFICATE-----`)
	block, _ = pem.Decode(crtPEM)
	crt, _ = x509.ParseCertificate(block.Bytes)
	//初始化路由
	r := gin.Default()
	r.Use(cors())
	r.Static("static", "static")
	//加载模板
	r.LoadHTMLGlob("templates/*")
	r.GET("/", index)
	r.POST("/generateLicense", generateLicense)
	r.Run("0.0.0.0:8080")
}
48 Likes

支持一下

3 Likes

厉害厉害

3 Likes

mark

3 Likes

没有啦,就是简单翻译了一下大佬的东西,献丑了

3 Likes

请问大佬,index.html从何而来

4 Likes

index.html 来自jetbra-server,其他的请拜读novice.li佬的帖子哈

{{define "/index.html"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>{{.title}}</title>
    <style>
        .form {
            background-color: #15172b;
            border-radius: 20px;
            box-sizing: border-box;
            height: 500px;
            padding: 20px;
            width: 320px;
        }

        .title {
            color: #eee;
            font-family: sans-serif;
            font-size: 36px;
            font-weight: 600;
            margin-top: 30px;
        }

        .subtitle {
            color: #eee;
            font-family: sans-serif;
            font-size: 16px;
            font-weight: 600;
            margin-top: 10px;
        }

        .input-container {
            height: 50px;
            position: relative;
            width: 100%;
        }

        .ic1 {
            margin-top: 40px;
        }

        .ic2 {
            margin-top: 30px;
        }

        .input {
            background-color: #303245;
            border-radius: 12px;
            border: 0;
            box-sizing: border-box;
            color: #eee;
            font-size: 18px;
            height: 100%;
            outline: 0;
            padding: 4px 20px 0;
            width: 100%;
        }

        .cut {
            background-color: #15172b;
            border-radius: 10px;
            height: 20px;
            left: 20px;
            position: absolute;
            top: -20px;
            transform: translateY(0);
            transition: transform 200ms;
            width: 76px;
        }

        .cut-short {
            width: 50px;
        }

        .input:focus ~ .cut,
        .input:not(:placeholder-shown) ~ .cut {
            transform: translateY(8px);
        }

        .placeholder {
            color: #65657b;
            font-family: sans-serif;
            left: 20px;
            line-height: 14px;
            pointer-events: none;
            position: absolute;
            transform-origin: 0 50%;
            transition: transform 200ms, color 200ms;
            top: 20px;
        }

        .input:focus ~ .placeholder,
        .input:not(:placeholder-shown) ~ .placeholder {
            transform: translateY(-30px) translateX(10px) scale(0.75);
        }

        .input:not(:placeholder-shown) ~ .placeholder {
            color: #808097;
        }

        .input:focus ~ .placeholder {
            color: #dc2f55;
        }

        .submit {
            background-color: #08d;
            border-radius: 12px;
            border: 0;
            box-sizing: border-box;
            color: #eee;
            cursor: pointer;
            font-size: 18px;
            height: 50px;
            margin-top: 38px;
            text-align: center;
            width: 100%;
        }

        .submit:active {
            background-color: #06b;
        }

    </style>
    <style>
        :root {
            --text-grey: #9e9e9e;
            --text-main: rgba(0, 0, 0, 0.87);
            --spacing: 4px;
            --size: 64px;
            --radius: 1.5rem;
            --accent: #5380f7;
            --text-sm: 0.875rem;
            --main-bg: #fff;
            --card-bg: #fff;
            --hover-color: #eee;
            --border-color: rgba(0, 0, 0, 0.05);
            --grey-400: rgba(0, 0, 0, 0.04);
            --grey-600: rgba(0, 0, 0, 0.06);
        }

        @media (prefers-color-scheme: dark) {
            :root {
                --main-bg: rgb(0, 0, 0);
                --card-bg: rgb(31, 34, 38);
                --text-main: #d9d9d9;
                --text-grey: #6e767d;
                --accent: #1d9bf0;
                --hover-color: rgba(255, 255, 255, 0.07);
                --border-color: #4b4648;
            }
        }

        body {
            font-size: 1rem;
            line-height: 1.5;
            word-wrap: break-word;
            font-kerning: normal;
            font-family: 'Gotham SSm A', 'Gotham SSm B', 'Arial Unicode MS', Helvetica, sans-serif;
            margin: 0;
            padding: 0;
            -webkit-font-smoothing: antialiased;
            background-color: var(--main-bg);
        }

        * ul, * ol {
            list-style: none;
            padding: 0;
            margin: 0;
        }

        *[role='button'], button {
            cursor: pointer;
        }

        .color-primary {
            color: var(--text-main);
        }


        .mt-0 {
            margin-top: 0;
        }


        .radius-1 {
            border-radius: var(--radius);
        }

        .px-6 {
            padding-left: calc(var(--spacing) * 6);
            padding-right: calc(var(--spacing) * 6);
        }

        .py-10 {
            padding-top: calc(var(--spacing) * 10);
            padding-bottom: calc(var(--spacing) * 10);
        }

        .pd-6 {
            padding: calc(var(--spacing) * 6);
        }


        .pt-1 {
            padding-top: var(--spacing);
        }

        .pb-0 {
            padding-bottom: 0;
        }

        .overflow-hidden {
            overflow: hidden;
        }

        .flex {
            display: flex;
        }

        .justify-between {
            justify-content: space-between;
        }

        .justify-center {
            justify-content: center;
        }


        .items-center {
            align-items: center;
        }

        .shrink-0 {
            flex-shrink: 0;
        }

        .text-grey {
            color: var(--text-grey);
        }

        .text-sm {
            font-size: 0.875rem;
        }

        .bg-card {
            background-color: var(--card-bg);
        }

        .truncate {
            display: -webkit-box;
            -webkit-box-orient: vertical;
            -webkit-line-clamp: var(--line, 3);
            overflow: hidden;
        }

        .truncate-1 {
            --line: 1;
        }

        .overflow-ellipsis {
            text-overflow: ellipsis;
        }

        .z-grid {
            display: grid;
            grid-gap: var(--gutter, 1rem);
            grid-template-columns: repeat(auto-fill, minmax(min(var(--space, 10rem), 100%), 1fr));
        }


        .card {
            box-shadow: rgb(0 0 0 / 30%) 0 8px 40px -12px;
            border-radius: 1.5rem;
            transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) 0ms;
            width: 90%;
            position: relative;
            overflow: visible;
            background-color: var(--card-bg);
            margin: 0 auto;
        }

        .card:hover {
            transform: translateY(-2px);
        }

        .card:hover .mask {
            bottom: -1.5rem;
        }

        .card:hover .mask-c-1 {
            bottom: -2.5rem;
        }

        .container {
            padding-top: calc(var(--spacing) * 10);
        }

        .container p {
            position: relative;
            cursor: pointer;
            transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
        }

        .container p::after {
            content: attr(data-content);
            position: absolute;
            transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
            color: transparent;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: var(--radius);
        }

        .container p:hover {
            color: transparent;
        }

        .container p:hover::after {
            color: var(--text-main);
            background-color: var(--hover-color);
        }

        .toggle li {
            z-index: 99;
            position: relative;
            background: transparent;
            padding: 0 20px;
            color: var(--text-main);
            transition: background-color 250ms ease-out;
        }

        .toggle li:hover {
            background-color: var(--hover-color);
        }

        .toggle li.active a {
            color: var(--accent);
        }

        .toggle li:first-child {
            border-top-left-radius: var(--text-sm);
            border-top-right-radius: var(--text-sm);
        }

        .toggle li:last-child {
            border-bottom-left-radius: var(--text-sm);
            border-bottom-right-radius: var(--text-sm);
        }

        .toggle li:last-child a {
            border-bottom: 0;
        }

        .toggle li a {
            display: block;
            border-bottom: 1px solid var(--border-color);
            padding: 16px 0;
            color: inherit;
            text-decoration: none;
            white-space: nowrap;
        }

        .icon {
            background-image: url('/static/images/icons.svg?t=4567');
            background-size: 64px;
            background-position-x: 0;
        }

        .icon-ii {
            background-position-y: -448px;
        }

        .icon-ps {
            background-position-y: -512px;
        }

        .icon-ac {
            background-position-y: -192px;
        }

        .icon-db {
            background-position-y: -320px;
        }

        .icon-rm {
            background-position-y: -896px;
        }

        .icon-ws {
            background-position-y: -960px;
        }

        .icon-rd {
            background-position-y: -832px;
        }

        .icon-cl {
            background-position-y: -256px;
        }

        .icon-pc {
            background-position-y: -576px;
        }

        .icon-go {
            background-position-y: -384px;
        }

        .icon-ds {
            background-position-y: -1792px;
        }

        .icon-dc {
            background-position-y: -1408px;
        }

        .icon-dpn {
            background-position-y: -1536px;
        }

        .icon-dm {
            background-position-y: -1472px;
        }

        .mask {
            transition: 0.2s;
            position: absolute;
            z-index: -1;
            width: 88%;
            height: 100%;
            bottom: 0;
            border-radius: 1.5rem;
            background-color: var(--grey-600);
            left: 50%;
            transform: translateX(-50%);
        }

        .mask-c-1 {
            bottom: 0;
            width: 72%;
            background-color: var(--grey-400);
        }

        .avatar-wrapper {
            position: relative;
            width: var(--size);
            height: var(--size);
            font-size: 1.25rem;
            user-select: none;
            transform: translateY(50%);
        }

        .avatar-wrapper img, .avatar-wrapper .icon {
            width: 100%;
            height: 100%;
            margin: 0;
            background-color: var(--card-bg);
            color: transparent;
            object-fit: cover;
            text-align: center;
            text-indent: 10000px;
        }


        header.tip a {
            color: var(--accent);
            text-decoration: none;
        }

        header.tip p {
            word-break: break-word;
            word-wrap: break-word;
        }


        main hr {
            margin: 0;
            padding: 0;
            background: var(--border-color);
            height: 1px;
            border: none;
        }

        footer {
            --_size: 40px;
            padding-top: var(--_size);
            width: 96%;
            margin: calc(var(--spacing) * 10) auto 0;
            padding-bottom: var(--_size);
            border-top: 1px solid var(--border-color);
            -moz-box-align: center;
            -webkit-box-pack: justify;
        }

        footer .lt-panel p:nth-of-type(1) {
            color: inherit;
        }
    </style>
</head>
<body style="width:100%;height:100%;">
<main class="px-6 z-grid py-10" style="--space: 20rem; --gutter: 3.5rem">
    <article class="card" data-product="IntelliJ IDEA" data-product-codes="II,PCWMP,PSI">
        <header>
            <div class="flex items-center justify-between px-6 pt-1 pb-0 bg-card radius-1">
                <div class="avatar-wrapper flex items-center justify-center overflow-hidden shrink-0">
                    <div class="icon icon-ii" role="img"></div>
                </div>
            </div>
            <hr/>
        </header>
        <div class="pd-6 overflow-hidden bg-card container radius-1">
            <h1 class="truncate truncate-1 color-primary mt-0 overflow-ellipsis" title='IntelliJ IDEA'>IntelliJ
                IDEA</h1>
            <p title="单据复制激活码到剪切板" class="truncate text-sm text-grey" onclick="copyLicense(this)"
               data-content="复制到剪切板">
                **********
            </p>
        </div>
        <div class="mask"></div>
        <div class="mask mask-c-1"></div>
    </article>
    <article class="card" data-product="PhpStorm" data-product-codes="PS,PCWMP,PSI">
        <header>
            <div class="flex items-center justify-between px-6 pt-1 pb-0 bg-card radius-1">
                <div class="avatar-wrapper flex items-center justify-center overflow-hidden shrink-0">
                    <div class="icon icon-ps" role="img"></div>
                </div>
            </div>
            <hr/>
        </header>
        <div class="pd-6 overflow-hidden bg-card container radius-1">
            <h1 class="truncate truncate-1 color-primary mt-0 overflow-ellipsis" title='PhpStorm'>PhpStorm</h1>
            <p title="单据复制激活码到剪切板" class="truncate text-sm text-grey" onclick="copyLicense(this)"
               data-content="复制到剪切板">
                **********
            </p>
        </div>
        <div class="mask"></div>
        <div class="mask mask-c-1"></div>
    </article>
    <article class="card" data-product="AppCode" data-product-codes="AC,PCWMP,PSI">
        <header>
            <div class="flex items-center justify-between px-6 pt-1 pb-0 bg-card radius-1">
                <div class="avatar-wrapper flex items-center justify-center overflow-hidden shrink-0">
                    <div class="icon icon-ac" role="img"></div>
                </div>
            </div>
            <hr/>
        </header>
        <div class="pd-6 overflow-hidden bg-card container radius-1">
            <h1 class="truncate truncate-1 color-primary mt-0 overflow-ellipsis" title='AppCode'>AppCode</h1>
            <p title="单据复制激活码到剪切板" class="truncate text-sm text-grey" onclick="copyLicense(this)"
               data-content="复制到剪切板">
                **********
            </p>
        </div>
        <div class="mask"></div>
        <div class="mask mask-c-1"></div>
    </article>
    <article class="card" data-product="DataGrip" data-product-codes="DB,PSI,PDB">
        <header>
            <div class="flex items-center justify-between px-6 pt-1 pb-0 bg-card radius-1">
                <div class="avatar-wrapper flex items-center justify-center overflow-hidden shrink-0">
                    <div class="icon icon-db" role="img"></div>
                </div>
            </div>
            <hr/>
        </header>
        <div class="pd-6 overflow-hidden bg-card container radius-1">
            <h1 class="truncate truncate-1 color-primary mt-0 overflow-ellipsis" title='DataGrip'>DataGrip</h1>
            <p title="单据复制激活码到剪切板" class="truncate text-sm text-grey" onclick="copyLicense(this)"
               data-content="复制到剪切板">
                **********
            </p>
        </div>
        <div class="mask"></div>
        <div class="mask mask-c-1"></div>
    </article>
    <article class="card" data-product="RubyMine" data-product-codes="RM,PCWMP,PSI">
        <header>
            <div class="flex items-center justify-between px-6 pt-1 pb-0 bg-card radius-1">
                <div class="avatar-wrapper flex items-center justify-center overflow-hidden shrink-0">
                    <div class="icon icon-rm" role="img"></div>
                </div>
            </div>
            <hr/>
        </header>
        <div class="pd-6 overflow-hidden bg-card container radius-1">
            <h1 class="truncate truncate-1 color-primary mt-0 overflow-ellipsis" title='RubyMine'>RubyMine</h1>
            <p title="单据复制激活码到剪切板" class="truncate text-sm text-grey" onclick="copyLicense(this)"
               data-content="复制到剪切板">
                **********
            </p>
        </div>
        <div class="mask"></div>
        <div class="mask mask-c-1"></div>
    </article>
    <article class="card" data-product="WebStorm" data-product-codes="WS,PCWMP,PSI">
        <header>
            <div class="flex items-center justify-between px-6 pt-1 pb-0 bg-card radius-1">
                <div class="avatar-wrapper flex items-center justify-center overflow-hidden shrink-0">
                    <div class="icon icon-ws" role="img"></div>
                </div>
            </div>
            <hr/>
        </header>
        <div class="pd-6 overflow-hidden bg-card container radius-1">
            <h1 class="truncate truncate-1 color-primary mt-0 overflow-ellipsis" title='WebStorm'>WebStorm</h1>
            <p title="单据复制激活码到剪切板" class="truncate text-sm text-grey" onclick="copyLicense(this)"
               data-content="复制到剪切板">
                **********
            </p>
        </div>
        <div class="mask"></div>
        <div class="mask mask-c-1"></div>
    </article>
    <article class="card" data-product="Rider" data-product-codes="RD,PDB,PSI,PCWMP">
        <header>
            <div class="flex items-center justify-between px-6 pt-1 pb-0 bg-card radius-1">
                <div class="avatar-wrapper flex items-center justify-center overflow-hidden shrink-0">
                    <div class="icon icon-rd" role="img"></div>
                </div>
            </div>
            <hr/>
        </header>
        <div class="pd-6 overflow-hidden bg-card container radius-1">
            <h1 class="truncate truncate-1 color-primary mt-0 overflow-ellipsis" title='Rider'>Rider</h1>
            <p title="单据复制激活码到剪切板" class="truncate text-sm text-grey" onclick="copyLicense(this)"
               data-content="复制到剪切板">
                **********
            </p>
        </div>
        <div class="mask"></div>
        <div class="mask mask-c-1"></div>
    </article>
    <article class="card" data-product="CLion" data-product-codes="CL,PSI,PCWMP">
        <header>
            <div class="flex items-center justify-between px-6 pt-1 pb-0 bg-card radius-1">
                <div class="avatar-wrapper flex items-center justify-center overflow-hidden shrink-0">
                    <div class="icon icon-cl" role="img"></div>
                </div>
            </div>
            <hr/>
        </header>
        <div class="pd-6 overflow-hidden bg-card container radius-1">
            <h1 class="truncate truncate-1 color-primary mt-0 overflow-ellipsis" title='CLion'>CLion</h1>
            <p title="单据复制激活码到剪切板" class="truncate text-sm text-grey" onclick="copyLicense(this)"
               data-content="复制到剪切板">
                **********
            </p>
        </div>
        <div class="mask"></div>
        <div class="mask mask-c-1"></div>
    </article>
    <article class="card" data-product="PyCharm" data-product-codes="PC,PSI,PCWMP">
        <header>
            <div class="flex items-center justify-between px-6 pt-1 pb-0 bg-card radius-1">
                <div class="avatar-wrapper flex items-center justify-center overflow-hidden shrink-0">
                    <div class="icon icon-pc" role="img"></div>
                </div>
            </div>
            <hr/>
        </header>
        <div class="pd-6 overflow-hidden bg-card container radius-1">
            <h1 class="truncate truncate-1 color-primary mt-0 overflow-ellipsis" title='PyCharm'>PyCharm</h1>
            <p title="单据复制激活码到剪切板" class="truncate text-sm text-grey" onclick="copyLicense(this)"
               data-content="复制到剪切板">
                **********
            </p>
        </div>
        <div class="mask"></div>
        <div class="mask mask-c-1"></div>
    </article>
    <article class="card" data-product="GoLand" data-product-codes="GO,PSI,PCWMP">
        <header>
            <div class="flex items-center justify-between px-6 pt-1 pb-0 bg-card radius-1">
                <div class="avatar-wrapper flex items-center justify-center overflow-hidden shrink-0">
                    <div class="icon icon-go" role="img"></div>
                </div>
            </div>
            <hr/>
        </header>
        <div class="pd-6 overflow-hidden bg-card container radius-1">
            <h1 class="truncate truncate-1 color-primary mt-0 overflow-ellipsis" title='GoLand'>GoLand</h1>
            <p title="单据复制激活码到剪切板" class="truncate text-sm text-grey" onclick="copyLicense(this)"
               data-content="复制到剪切板">
                **********
            </p>
        </div>
        <div class="mask"></div>
        <div class="mask mask-c-1"></div>
    </article>
    <article class="card" data-product="DataSpell" data-product-codes="DS,PSI,PDB,PCWMP">
        <header>
            <div class="flex items-center justify-between px-6 pt-1 pb-0 bg-card radius-1">
                <div class="avatar-wrapper flex items-center justify-center overflow-hidden shrink-0">
                    <div class="icon icon-ds" role="img"></div>
                </div>
            </div>
            <hr/>
        </header>
        <div class="pd-6 overflow-hidden bg-card container radius-1">
            <h1 class="truncate truncate-1 color-primary mt-0 overflow-ellipsis" title='DataSpell'>DataSpell</h1>
            <p title="单据复制激活码到剪切板" class="truncate text-sm text-grey" onclick="copyLicense(this)"
               data-content="复制到剪切板">
                **********
            </p>
        </div>
        <div class="mask"></div>
        <div class="mask mask-c-1"></div>
    </article>
    <article class="card" data-product="dotCover" data-product-codes="DC">
        <header>
            <div class="flex items-center justify-between px-6 pt-1 pb-0 bg-card radius-1">
                <div class="avatar-wrapper flex items-center justify-center overflow-hidden shrink-0">
                    <div class="icon icon-dc" role="img"></div>
                </div>
            </div>
            <hr/>
        </header>
        <div class="pd-6 overflow-hidden bg-card container radius-1">
            <h1 class="truncate truncate-1 color-primary mt-0 overflow-ellipsis" title='dotCover'>dotCover</h1>
            <p title="单据复制激活码到剪切板" class="truncate text-sm text-grey" onclick="copyLicense(this)"
               data-content="复制到剪切板">
                **********
            </p>
        </div>
        <div class="mask"></div>
        <div class="mask mask-c-1"></div>
    </article>
    <article class="card" data-product="dotTrace" data-product-codes="DPN,DP">
        <header>
            <div class="flex items-center justify-between px-6 pt-1 pb-0 bg-card radius-1">
                <div class="avatar-wrapper flex items-center justify-center overflow-hidden shrink-0">
                    <div class="icon icon-dpn" role="img"></div>
                </div>
            </div>
            <hr/>
        </header>
        <div class="pd-6 overflow-hidden bg-card container radius-1">
            <h1 class="truncate truncate-1 color-primary mt-0 overflow-ellipsis" title='dotTrace'>dotTrace</h1>
            <p title="单据复制激活码到剪切板" class="truncate text-sm text-grey" onclick="copyLicense(this)"
               data-content="复制到剪切板">
                **********
            </p>
        </div>
        <div class="mask"></div>
        <div class="mask mask-c-1"></div>
    </article>
    <article class="card" data-product="dotMemory" data-product-codes="DM">
        <header>
            <div class="flex items-center justify-between px-6 pt-1 pb-0 bg-card radius-1">
                <div class="avatar-wrapper flex items-center justify-center overflow-hidden shrink-0">
                    <div class="icon icon-dm" role="img"></div>
                </div>
            </div>
            <hr/>
        </header>
        <div class="pd-6 overflow-hidden bg-card container radius-1">
            <h1 class="truncate truncate-1 color-primary mt-0 overflow-ellipsis" title='dotMemory'>dotMemory</h1>
            <p title="单据复制激活码到剪切板" class="truncate text-sm text-grey" onclick="copyLicense(this)"
               data-content="复制到剪切板">
                **********
            </p>
        </div>
        <div class="mask"></div>
        <div class="mask mask-c-1"></div>
    </article>
</main>
<footer class="flex items-center justify-between">
    <div class="text-sm text-grey">Theme by QieTuZai</div>
    <div class="text-sm text-grey">Copy from ja-netfilter</div>
</footer>
<div id ="mask" style="position:fixed;top:0;left:0;z-index:998;width:100%;height:100%;display:none;background-color:#000;opacity:0.5;overflow:hidden;"></div>
<div id="form" style="position:fixed;top:20%;left:40%;width:40%;height:500px;z-index:999;display:none;">
    <div class="form">
        <div class="title">欢迎</div>
        <div class="subtitle">请输入授权信息</div>
        <div class="input-container ic1">
            <input id="licenseeName" class="input" type="text" placeholder=" " value="{{.licenseeName}}"/>
            <div class="cut"></div>
            <label for="licenseeName" class="placeholder">证书名称</label>
        </div>
        <div class="input-container ic2">
            <input id="assigneeName" class="input" type="text" placeholder=" " value="{{.assigneeName}}"/>
            <div class="cut"></div>
            <label for="assigneeName" class="placeholder">姓名</label>
        </div>
        <div class="input-container ic2">
            <input id="expiryDate" class="input" type="text" placeholder="" value="{{.expiryDate}}"/>
            <div class="cut cut-short"></div>
            <label for="expiryDate" class="placeholder">过期时间</label>
        </div>
        <button  class="submit" onclick="submitLicenseInfo(this)">确认</button>
    </div>
</div>


<script>
    if (localStorage.getItem('licenseInfo') === null) {
        document.getElementById('mask').style.display = 'block'
        document.getElementById('form').style.display = 'block'
    }
    window.submitLicenseInfo = function () {
        let licenseeName = document.getElementById('licenseeName').value
        let assigneeName = document.getElementById('assigneeName').value
        let expiryDate = document.getElementById('expiryDate').value
        let licenseInfo = {
            licenseeName: licenseeName,
            assigneeName: assigneeName,
            expiryDate: expiryDate
        }
        localStorage.setItem('licenseInfo', JSON.stringify(licenseInfo))
        document.getElementById('mask').style.display = 'none'
        document.getElementById('form').style.display = 'none'
    }
</script>

<script>
    //@see https://zhuanlan.zhihu.com/p/597944027
    const copyText = async (val) => {
        if (navigator.clipboard && navigator.permissions) {
            await navigator.clipboard.writeText(val)
        } else {
            const textArea = document.createElement('textArea')
            textArea.value = val
            textArea.style.width = 0
            textArea.style.position = 'fixed'
            textArea.style.left = '-999px'
            textArea.style.top = '10px'
            textArea.setAttribute('readonly', 'readonly')
            document.body.appendChild(textArea)

            textArea.select()
            document.execCommand('copy')
            document.body.removeChild(textArea)
        }
    }
    // const codes = ["YTD", "QDGO", "MF", "DG", "PS", "QA", "IIE", "YTWE", "FLS", "DLE", "RFU", "PPS", "PCWMP", "II", "TCC", "RSU", "PCC", "RC", "PCE", "FLIJ", "TBA", "DL", "SPP", "QDCLD", "SPA", "DMCLP", "PSW", "GW", "PSI", "IIU", "DMU", "PWS", "HB", "WS", "PCP", "KT", "DCCLT", "RSCLT", "WRS", "RSC", "RRD", "TC", "IIC", "QDPY", "DPK", "DC", "PDB", "DPPS", "QDPHP", "GO", "HCC", "RDCPPP", "QDJVMC", "CL", "DM", "CWML", "FLL", "RR", "QDJS", "RS", "RM", "DS", "MPS", "DPN", "US", "CLN", "DPCLT", "RSV", "MPSIIP", "DB", "QDANDC", "AC", "QDJVM", "PRB", "RD", "CWMR", "SP", "RS0", "DP", "RSF", "PGO", "QDPYC", "PPC", "PC", "EHS", "RSCHB", "FL", "QDNET", "JCD"]
    window.copyLicense = async function (e) {

        while (localStorage.getItem('licenseInfo') === null) {
            document.getElementById('mask').style.display = 'block'
            document.getElementById('form').style.display = 'block'
            await new Promise(r => setTimeout(r, 1000));
        }
        let licenseInfo =  JSON.parse(localStorage.getItem('licenseInfo'))
        let codes = e.closest('.card').dataset.productCodes.split(',')
        let products = Array.from(codes).map((code) => {
            return {
                code: code,
                fallbackDate: licenseInfo.expiryDate,
                paidUpTo: licenseInfo.expiryDate
            }
        })
        let data = {
            "licenseeName": licenseInfo.licenseeName,
            "assigneeName": licenseInfo.assigneeName,
            "assigneeEmail": "",
            "licenseRestriction": "",
            "checkConcurrentUse": false,
            "products": products,
            "metadata": "0120230102PPAA013009",
            "hash": "41472961/0:1563609451",
            "gracePeriodDays": 7,
            "autoProlongated": true,
            "isAutoProlongated": true
        }
        let resp = await fetch('/generateLicense', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(data)
        }).then(response => response.json())

        copyText(resp.license)
            .then(() => {
                alert("激活码已复制(Tips:请配合 ja-netfilter 食用~~~)");
            })
    }
</script>
</body>
</html>
{{end}}
2 Likes

项目没了,期待大佬啥时候可以传个压缩包。谢谢

2 Likes

n佬看到你的贴,删库跑路了

3 Likes

那我岂不是成了罪人 :joy:

2 Likes

[email protected] 谢谢大佬 @winffy2009

2 Likes

其实我也造了个rust版的,由于我服务器配置太低,java的跑不动,换成rust这内存占有太香了

8 Likes

我有个自己写的jetbra-server

27 Likes

:sunglasses: :sunglasses:

2 Likes

好像有人back了一份

27 Likes

大哥威武,有机会用上

1 Like

6666

2 Likes

x509,SHA1withRSA 用的那几个crate。n佬

1 Like

求大佬来个zip啊

1 Like

自己编译吧