mirror of
https://github.com/MengMengCode/CLICD.git
synced 2026-08-05 05:36:07 +08:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 39ccbad29a | |||
| a736e156bb | |||
| 23a0541f7c | |||
| a28ae727f3 | |||
| 57d6b19c05 | |||
| bae028ade4 | |||
| 2188bddb93 | |||
| 79de3d5552 | |||
| 70679b6fbb | |||
| d739dcbaa4 | |||
| 9eaf5002df |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = `
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,7 +1,7 @@
|
||||
package version
|
||||
|
||||
var (
|
||||
Version = "1.1.28"
|
||||
Version = "1.1.29"
|
||||
Repo = "MengMengCode/CLICD"
|
||||
)
|
||||
|
||||
|
||||
Generated
+7
-7
@@ -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
@@ -12,6 +12,7 @@
|
||||
},
|
||||
"overrides": {
|
||||
"vite": "6.4.3",
|
||||
"esbuild": "0.28.1"
|
||||
"esbuild": "0.28.1",
|
||||
"postcss": "8.5.23"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+47
-103
@@ -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",
|
||||
|
||||
@@ -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,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,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,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,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,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,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,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,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,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,
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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,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,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'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user