Compare commits

...

11 Commits

Author SHA1 Message Date
MengMengCode 39ccbad29a release: v1.1.29 2026-08-04 05:17:08 +08:00
MengMengCode a736e156bb fix something 2026-08-04 05:13:45 +08:00
MengMengCode 23a0541f7c feat: rename functions and variables for clarity in safehttp package; enhance URL validation tests 2026-08-04 05:05:45 +08:00
Meng Meng a28ae727f3 Merge pull request #37 from LusineStar/fix/windows-firstlogon-drive-scan
fix(kvm): Windows 首启脚本盘符扫描的批处理语法错误,导致 RDP/入站端口全被丢弃
2026-07-26 21:47:11 +08:00
LusineStar 57d6b19c05 fix(kvm): correct Windows FirstLogon drive-scan batch syntax
The unattend FirstLogonCommands runs via 'cmd.exe /c' (command-line context),
where a FOR loop variable must be %d, not the batch-file form %%d. The %%d
form raised '%%d was unexpected at this time', so the else branch that locates
and runs FirstLogon.ps1 from the unattend ISO never executed.

Combined with $OEM$ not being processed from a separate ISO (so
C:\CLICD\FirstLogon.ps1 is never staged), the entire Windows post-install
init was skipped: RDP was never enabled, the firewall was never opened, and
the network profile stayed Public, so the guest dropped all inbound traffic.
The host NAT DNAT for 3389 was correct, but the guest silently dropped it,
making the mapping appear to have no effect.
2026-07-26 21:42:06 +08:00
Meng Meng bae028ade4 Merge pull request #36 from MengMengCode/copilot/fix-code-scanning-alerts-146
Triage CodeQL alert #146 as false positive (go/request-forgery)
2026-07-26 04:49:15 +08:00
copilot-swe-agent[bot] 2188bddb93 Initial plan 2026-07-25 20:46:03 +00:00
MengMengCode 79de3d5552 Merge branch 'main' of https://github.com/MengMengCode/CLICD 2026-07-26 04:39:02 +08:00
MengMengCode 70679b6fbb feat: upgrade Node.js version and update dependencies for frontend and docs 2026-07-26 04:38:56 +08:00
Meng Meng d739dcbaa4 Merge pull request #35 from MengMengCode/dependabot/npm_and_yarn/docs/postcss-8.5.23
build(deps-dev): bump postcss from 8.5.15 to 8.5.23 in /docs
2026-07-26 04:25:22 +08:00
dependabot[bot] 9eaf5002df build(deps-dev): bump postcss from 8.5.15 to 8.5.23 in /docs
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.15 to 8.5.23.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.15...8.5.23)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.23
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-25 20:23:42 +00:00
26 changed files with 189 additions and 173 deletions
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version: "22.22.0"
cache: npm
cache-dependency-path: |
frontend/package-lock.json
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
node-version: "22.22.0"
cache: npm
cache-dependency-path: docs/package-lock.json
+2 -2
View File
@@ -84,12 +84,12 @@ func TestCustomLXCImageCreateRejectsInvalidSource(t *testing.T) {
}
}
func TestCustomImageCreateRejectsPrivateNetworkSource(t *testing.T) {
func TestCustomImageCreateRejectsMetadataSource(t *testing.T) {
for _, imageType := range []string{"lxc", "kvm"} {
t.Run(imageType, func(t *testing.T) {
payload := map[string]string{
"type": imageType,
"name": "Private Network Source",
"name": "Metadata Source",
"distro": "ubuntu",
"release": "noble",
"arch": runtime.GOARCH,
+1 -1
View File
@@ -1906,7 +1906,7 @@ func windowsAutounattendXML(hostname, adminPassword string, windows11 bool) stri
hostname = "clicd-win"
}
hostname = sanitizeWindowsComputerName(hostname)
setupCommand := `cmd.exe /c if exist C:\CLICD\FirstLogon.ps1 (powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\CLICD\FirstLogon.ps1) else (for %%d in (D E F G H I J K L M N O P Q R S T U V W X Y Z) do @if exist %%d:\FirstLogon.ps1 powershell.exe -NoProfile -ExecutionPolicy Bypass -File %%d:\FirstLogon.ps1)`
setupCommand := `cmd.exe /c if exist C:\CLICD\FirstLogon.ps1 (powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\CLICD\FirstLogon.ps1) else (for %d in (D E F G H I J K L M N O P Q R S T U V W X Y Z) do @if exist %d:\FirstLogon.ps1 powershell.exe -NoProfile -ExecutionPolicy Bypass -File %d:\FirstLogon.ps1)`
compatibilityCommands := ""
if windows11 {
compatibilityCommands = `
+24 -24
View File
@@ -14,17 +14,14 @@ import (
const maxRedirects = 10
var blockedPrefixes = []netip.Prefix{
var blockedDownloadPrefixes = []netip.Prefix{
netip.MustParsePrefix("0.0.0.0/8"),
netip.MustParsePrefix("10.0.0.0/8"),
netip.MustParsePrefix("100.64.0.0/10"),
netip.MustParsePrefix("100.100.100.200/32"),
netip.MustParsePrefix("127.0.0.0/8"),
netip.MustParsePrefix("169.254.0.0/16"),
netip.MustParsePrefix("172.16.0.0/12"),
netip.MustParsePrefix("192.0.0.0/24"),
netip.MustParsePrefix("192.0.2.0/24"),
netip.MustParsePrefix("192.88.99.0/24"),
netip.MustParsePrefix("192.168.0.0/16"),
netip.MustParsePrefix("198.18.0.0/15"),
netip.MustParsePrefix("198.51.100.0/24"),
netip.MustParsePrefix("203.0.113.0/24"),
@@ -40,7 +37,7 @@ var blockedPrefixes = []netip.Prefix{
netip.MustParsePrefix("2001:db8::/32"),
netip.MustParsePrefix("2001:20::/28"),
netip.MustParsePrefix("2002::/16"),
netip.MustParsePrefix("fc00::/7"),
netip.MustParsePrefix("fd00:ec2::254/128"),
netip.MustParsePrefix("fec0::/10"),
netip.MustParsePrefix("fe80::/10"),
netip.MustParsePrefix("ff00::/8"),
@@ -74,13 +71,15 @@ func ValidateURL(rawURL string) (*url.URL, error) {
return nil, fmt.Errorf("download URL contains an invalid port")
}
}
if addr, err := netip.ParseAddr(parsed.Hostname()); err == nil && !isPublicAddress(addr) {
return nil, fmt.Errorf("download URL resolves to a non-public address")
if addr, err := netip.ParseAddr(parsed.Hostname()); err == nil && !isAllowedDownloadAddress(addr) {
return nil, fmt.Errorf("download URL resolves to a blocked address")
}
return parsed, nil
}
// Get retrieves a resource only when every resolved destination is public.
// Get retrieves a resource only when every resolved destination is safe for
// image downloads. Private network image mirrors are allowed; loopback,
// link-local, metadata, multicast and reserved destinations remain blocked.
func Get(ctx context.Context, rawURL, userAgent string, timeout time.Duration) (*http.Response, error) {
parsed, err := ValidateURL(rawURL)
if err != nil {
@@ -98,7 +97,7 @@ func Get(ctx context.Context, rawURL, userAgent string, timeout time.Duration) (
client := &http.Client{
Timeout: timeout,
Transport: publicTransport(net.DefaultResolver),
Transport: restrictedTransport(net.DefaultResolver),
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if len(via) >= maxRedirects {
return fmt.Errorf("too many redirects")
@@ -117,13 +116,14 @@ func Get(ctx context.Context, rawURL, userAgent string, timeout time.Duration) (
},
}
// All URL components, redirects, DNS answers and dial destinations are
// constrained above and in publicTransport.
// lgtm[go/request-forgery]
// The URL, redirects, DNS answers and dial destinations are constrained
// above and in restrictedTransport. CodeQL cannot infer those checks across
// the custom transport boundary.
// codeql[go/request-forgery]
return client.Do(request)
}
func publicTransport(resolver *net.Resolver) *http.Transport {
func restrictedTransport(resolver *net.Resolver) *http.Transport {
dialer := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
@@ -135,7 +135,7 @@ func publicTransport(resolver *net.Resolver) *http.Transport {
if err != nil {
return nil, fmt.Errorf("invalid download destination: %v", err)
}
addresses, err := resolvePublicHost(ctx, resolver, host)
addresses, err := resolveAllowedHost(ctx, resolver, host)
if err != nil {
return nil, err
}
@@ -159,11 +159,11 @@ func publicTransport(resolver *net.Resolver) *http.Transport {
}
func validateHost(ctx context.Context, resolver *net.Resolver, host string) error {
_, err := resolvePublicHost(ctx, resolver, host)
_, err := resolveAllowedHost(ctx, resolver, host)
return err
}
func resolvePublicHost(ctx context.Context, resolver *net.Resolver, host string) ([]netip.Addr, error) {
func resolveAllowedHost(ctx context.Context, resolver *net.Resolver, host string) ([]netip.Addr, error) {
host = strings.TrimSpace(strings.TrimSuffix(host, "."))
if host == "" {
return nil, fmt.Errorf("download URL host is empty")
@@ -174,8 +174,8 @@ func resolvePublicHost(ctx context.Context, resolver *net.Resolver, host string)
if addr, err := netip.ParseAddr(host); err == nil {
addr = addr.Unmap()
if !isPublicAddress(addr) {
return nil, fmt.Errorf("download URL resolves to a non-public address")
if !isAllowedDownloadAddress(addr) {
return nil, fmt.Errorf("download URL resolves to a blocked address")
}
return []netip.Addr{addr}, nil
}
@@ -190,22 +190,22 @@ func resolvePublicHost(ctx context.Context, resolver *net.Resolver, host string)
result := make([]netip.Addr, 0, len(addresses))
for _, address := range addresses {
address = address.Unmap()
if !isPublicAddress(address) {
return nil, fmt.Errorf("download host resolves to a non-public address")
if !isAllowedDownloadAddress(address) {
return nil, fmt.Errorf("download host resolves to a blocked address")
}
result = append(result, address)
}
return result, nil
}
func isPublicAddress(address netip.Addr) bool {
if !address.IsValid() || address.Zone() != "" || !address.IsGlobalUnicast() || address.IsPrivate() ||
func isAllowedDownloadAddress(address netip.Addr) bool {
if !address.IsValid() || address.Zone() != "" || !address.IsGlobalUnicast() ||
address.IsLoopback() || address.IsLinkLocalUnicast() || address.IsLinkLocalMulticast() ||
address.IsMulticast() || address.IsUnspecified() {
return false
}
address = address.Unmap()
for _, prefix := range blockedPrefixes {
for _, prefix := range blockedDownloadPrefixes {
if prefix.Contains(address) {
return false
}
+80 -9
View File
@@ -2,6 +2,9 @@ package safehttp
import (
"context"
"io"
"net"
"net/http"
"net/netip"
"testing"
"time"
@@ -15,9 +18,8 @@ func TestValidateURLRejectsUnsafeDestinations(t *testing.T) {
"http://127.0.0.1/image",
"http://[::1]/image",
"http://169.254.169.254/latest/meta-data",
"http://10.0.0.1/image",
"http://192.168.1.10/image",
"http://100.64.0.1/image",
"http://100.100.100.200/latest/meta-data",
"http://[fd00:ec2::254]/latest/meta-data",
"http://example.com:99999/image",
} {
if _, err := ValidateURL(rawURL); err == nil {
@@ -26,6 +28,21 @@ func TestValidateURLRejectsUnsafeDestinations(t *testing.T) {
}
}
func TestValidateURLAcceptsPrivateNetworkMirror(t *testing.T) {
t.Parallel()
for _, rawURL := range []string{
"http://10.0.0.10/images/rootfs.tar.xz",
"http://172.16.20.30:8080/images/vm.qcow2",
"https://192.168.1.10/image.iso",
"http://100.64.0.10/image.qcow2",
"http://[fd00::10]/rootfs.tar.xz",
} {
if _, err := ValidateURL(rawURL); err != nil {
t.Errorf("ValidateURL(%q) returned error: %v", rawURL, err)
}
}
}
func TestValidateURLAcceptsPublicHTTPURL(t *testing.T) {
t.Parallel()
parsed, err := ValidateURL("https://example.com/images/rootfs.tar.xz?variant=default")
@@ -37,29 +54,33 @@ func TestValidateURLAcceptsPublicHTTPURL(t *testing.T) {
}
}
func TestIsPublicAddress(t *testing.T) {
func TestIsAllowedDownloadAddress(t *testing.T) {
t.Parallel()
tests := map[string]bool{
"8.8.8.8": true,
"1.1.1.1": true,
"2606:4700:4700::1111": true,
"127.0.0.1": false,
"10.0.0.1": false,
"100.64.0.1": false,
"10.0.0.1": true,
"100.64.0.1": true,
"172.16.0.1": true,
"192.168.1.1": true,
"169.254.169.254": false,
"100.100.100.200": false,
"192.0.2.1": false,
"198.18.0.1": false,
"::1": false,
"64:ff9b::127.0.0.1": false,
"2002:7f00:1::1": false,
"fc00::1": false,
"fc00::1": true,
"fd00:ec2::254": false,
"fec0::1": false,
"fe80::1": false,
"2001:db8::1": false,
}
for raw, expected := range tests {
if actual := isPublicAddress(netip.MustParseAddr(raw)); actual != expected {
t.Errorf("isPublicAddress(%s) = %v, want %v", raw, actual, expected)
if actual := isAllowedDownloadAddress(netip.MustParseAddr(raw)); actual != expected {
t.Errorf("isAllowedDownloadAddress(%s) = %v, want %v", raw, actual, expected)
}
}
}
@@ -72,3 +93,53 @@ func TestGetRejectsLoopbackBeforeRequest(t *testing.T) {
t.Fatal("Get accepted a loopback destination")
}
}
func TestGetAllowsPrivateNetworkMirror(t *testing.T) {
privateIP := privateInterfaceIPv4(t)
listener, err := net.Listen("tcp4", net.JoinHostPort(privateIP.String(), "0"))
if err != nil {
t.Fatalf("failed to listen on private interface: %v", err)
}
defer listener.Close()
server := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, "private mirror ok")
})}
go func() { _ = server.Serve(listener) }()
defer server.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
response, err := Get(ctx, "http://"+listener.Addr().String()+"/image", "test", 5*time.Second)
if err != nil {
t.Fatalf("Get rejected private mirror: %v", err)
}
defer response.Body.Close()
body, err := io.ReadAll(response.Body)
if err != nil {
t.Fatal(err)
}
if string(body) != "private mirror ok" {
t.Fatalf("body = %q, want private mirror response", body)
}
}
func privateInterfaceIPv4(t *testing.T) netip.Addr {
t.Helper()
addresses, err := net.InterfaceAddrs()
if err != nil {
t.Fatal(err)
}
for _, rawAddress := range addresses {
prefix, err := netip.ParsePrefix(rawAddress.String())
if err != nil {
continue
}
address := prefix.Addr().Unmap()
if address.Is4() && address.IsPrivate() && isAllowedDownloadAddress(address) {
return address
}
}
t.Skip("no private IPv4 interface is available")
return netip.Addr{}
}
+1 -1
View File
@@ -1,7 +1,7 @@
package version
var (
Version = "1.1.28"
Version = "1.1.29"
Repo = "MengMengCode/CLICD"
)
+7 -7
View File
@@ -2065,9 +2065,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
"version": "3.3.16",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
"dev": true,
"funding": [
{
@@ -2123,9 +2123,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.15",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"version": "8.5.23",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz",
"integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==",
"dev": true,
"funding": [
{
@@ -2143,7 +2143,7 @@
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.12",
"nanoid": "^3.3.16",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
+2 -1
View File
@@ -12,6 +12,7 @@
},
"overrides": {
"vite": "6.4.3",
"esbuild": "0.28.1"
"esbuild": "0.28.1",
"postcss": "8.5.23"
}
}
+47 -103
View File
@@ -1,28 +1,28 @@
{
"name": "clicd-frontend",
"version": "1.1.27",
"version": "1.1.28",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "clicd-frontend",
"version": "1.1.27",
"version": "1.1.28",
"dependencies": {
"@novnc/novnc": "1.5.0",
"@xterm/addon-fit": "^0.11.0",
"@xterm/xterm": "^6.0.0",
"axios": "^1.18.0",
"lucide-react": "^0.454.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^7.18.1"
"react": "19.2.8",
"react-dom": "19.2.8",
"react-router": "8.3.0"
},
"devDependencies": {
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.2.0",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.49",
"postcss": "^8.5.23",
"tailwindcss": "^3.4.15",
"typescript": "^5.6.3",
"vite": "^8.0.16"
@@ -800,32 +800,24 @@
"@babel/types": "^7.28.2"
}
},
"node_modules/@types/prop-types": {
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.30",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.30.tgz",
"integrity": "sha512-3ek6mwJL5/VBewBcY4S66cqlCtK3qi4WIq37Z0m/NHw1hjhI7274Mx1qz/+ggSzyBCOEf7eHjBN6INjPAWYfYw==",
"version": "19.2.17",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
"integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.2.2"
}
},
"node_modules/@types/react-dom": {
"version": "18.3.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^18.0.0"
"@types/react": "^19.2.0"
}
},
"node_modules/@vitejs/plugin-react": {
@@ -1143,18 +1135,11 @@
"dev": true,
"license": "MIT"
},
"node_modules/cookie": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
"node_modules/cookie-es": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-3.1.1.tgz",
"integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==",
"license": "MIT"
},
"node_modules/cssesc": {
"version": "3.0.0",
@@ -1629,6 +1614,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/jsesc": {
@@ -1938,18 +1924,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -2042,9 +2016,9 @@
}
},
"node_modules/nanoid": {
"version": "3.3.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
"version": "3.3.16",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
"dev": true,
"funding": [
{
@@ -2148,9 +2122,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.15",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"version": "8.5.23",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz",
"integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==",
"dev": true,
"funding": [
{
@@ -2168,7 +2142,7 @@
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.12",
"nanoid": "^3.3.16",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -2341,28 +2315,24 @@
"license": "MIT"
},
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"version": "19.2.8",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
"integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"version": "19.2.8",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
"integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^18.3.1"
"react": "^19.2.8"
}
},
"node_modules/react-refresh": {
@@ -2376,20 +2346,19 @@
}
},
"node_modules/react-router": {
"version": "7.18.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz",
"integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==",
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-8.3.0.tgz",
"integrity": "sha512-qyPMvW83jGIct3yiieisxdk9M745anqhpIMKN5m1t6yBMfgVPpt77aHOqs5fUlEJRMCGffg9BaQLH9oPVOL7xQ==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0"
"cookie-es": "^3.1.1"
},
"engines": {
"node": ">=20.0.0"
"node": ">=22.22.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
"react": ">=19.2.7",
"react-dom": ">=19.2.7"
},
"peerDependenciesMeta": {
"react-dom": {
@@ -2397,22 +2366,6 @@
}
}
},
"node_modules/react-router-dom": {
"version": "7.18.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz",
"integrity": "sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==",
"license": "MIT",
"dependencies": {
"react-router": "7.18.1"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -2535,13 +2488,10 @@
}
},
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
}
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT"
},
"node_modules/semver": {
"version": "6.3.1",
@@ -2553,12 +2503,6 @@
"semver": "bin/semver.js"
}
},
"node_modules/set-cookie-parser": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
"license": "MIT"
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+7 -7
View File
@@ -1,7 +1,7 @@
{
"name": "clicd-frontend",
"private": true,
"version": "1.1.28",
"version": "1.1.29",
"type": "module",
"scripts": {
"dev": "vite",
@@ -14,16 +14,16 @@
"@xterm/xterm": "^6.0.0",
"axios": "^1.18.0",
"lucide-react": "^0.454.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^7.18.1"
"react": "19.2.8",
"react-dom": "19.2.8",
"react-router": "8.3.0"
},
"devDependencies": {
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.2.0",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.49",
"postcss": "^8.5.23",
"tailwindcss": "^3.4.15",
"typescript": "^5.6.3",
"vite": "^8.0.16"
+1 -1
View File
@@ -1,4 +1,4 @@
import { Routes, Route, Navigate } from 'react-router-dom'
import { Routes, Route, Navigate } from 'react-router'
import { useAuth } from './contexts/AuthContext'
import Login from './pages/Login'
import Dashboard from './pages/Dashboard'
+1 -1
View File
@@ -1,5 +1,5 @@
import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'
import { useLocation } from 'react-router'
import { useLanguage } from '../contexts/LanguageContext'
import { shouldTranslateText, translateText } from '../utils/i18n'
+1 -1
View File
@@ -1,4 +1,4 @@
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import {
Server,
Cpu,
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState, type ReactNode } from 'react'
import { ArrowLeft, ArrowRight, CalendarClock, Check, Plus, RefreshCw, Trash2, X } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { batchCreate, getIPv6Status, getEnabledImages, getHostInfo, getHostReport, getRoutingInfo, getStorageInfo, CreateContainerRequest, HostInfo, HostProbeReport, IPv6Status, PortMapping, RoutingInfo, StorageInfo, Template } from '../services/api'
import { useDialog } from './Dialog'
import { useLanguage, type Language } from '../contexts/LanguageContext'
+1 -1
View File
@@ -1,4 +1,4 @@
import { Outlet } from 'react-router-dom'
import { Outlet } from 'react-router'
import Sidebar from './Sidebar'
import { useState } from 'react'
import AutoTranslate from './AutoTranslate'
+1 -1
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import { useLocation, useNavigate } from 'react-router'
import {
ChevronLeft,
ChevronRight,
+1 -1
View File
@@ -1,5 +1,5 @@
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import api, { login as apiLogin, checkAuth, LoginResponse } from '../services/api'
interface AuthContextType {
+1 -1
View File
@@ -1,6 +1,6 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import { BrowserRouter } from 'react-router'
import App from './App'
import { AuthProvider } from './contexts/AuthContext'
import { ThemeProvider } from './contexts/ThemeContext'
+1 -1
View File
@@ -1,5 +1,5 @@
import { useState, useEffect, useCallback, useRef, type ReactNode } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { useParams, useNavigate } from 'react-router'
import {
ArrowLeft,
AlertTriangle,
+1 -1
View File
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import {
ArrowDown,
ArrowUp,
+2 -2
View File
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from 'react'
import { useCallback, useEffect, useState, type ReactNode } from 'react'
import { Cpu, HardDrive, MemoryStick, Network, Server } from 'lucide-react'
import RingStats from '../components/RingStats'
import ResourceStatsPanel, {
@@ -171,7 +171,7 @@ function SummaryCard({
value,
muted = false,
}: {
icon?: JSX.Element
icon?: ReactNode
dot?: string
title: string
value: number
+1 -1
View File
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useState, type ReactNode } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import {
Download,
Trash2,
+1 -1
View File
@@ -132,7 +132,7 @@ export default function Login() {
</form>
</div>
<p className="text-center text-xs text-gray-400 mt-6">CLICD v1.1.28</p>
<p className="text-center text-xs text-gray-400 mt-6">CLICD v1.1.29</p>
</div>
</div>
)
+1 -1
View File
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
import { Globe2, Network, Pencil, Plus, RefreshCw, Router, Save, Search, Server, Trash2, X } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { useLanguage, type Language } from '../contexts/LanguageContext'
import {
getRoutingInfo,
+1 -1
View File
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from 'react'
import { Camera, RefreshCw, Server, Trash2 } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { deleteContainerSnapshot, getSnapshots, Snapshot } from '../services/api'
import { useDialog } from '../components/Dialog'