mirror of
				https://github.com/khairul169/garage-webui.git
				synced 2025-11-04 17:01:06 +07:00 
			
		
		
		
	Compare commits
	
		
			No commits in common. "main" and "1.0.8" have entirely different histories.
		
	
	
		
	
		
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -26,7 +26,3 @@ dist-ssr
 | 
			
		||||
.env*
 | 
			
		||||
!.env.example
 | 
			
		||||
docker-compose.*.yml
 | 
			
		||||
 | 
			
		||||
data/
 | 
			
		||||
meta/
 | 
			
		||||
garage.toml
 | 
			
		||||
 | 
			
		||||
@ -21,12 +21,6 @@ RUN make
 | 
			
		||||
 | 
			
		||||
FROM scratch
 | 
			
		||||
 | 
			
		||||
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
 | 
			
		||||
COPY --from=ghcr.io/tarampampam/curl:8.6.0 /bin/curl /bin/curl
 | 
			
		||||
COPY --from=backend /app/main /bin/main
 | 
			
		||||
 | 
			
		||||
HEALTHCHECK --interval=5m --timeout=2s --retries=3 --start-period=15s CMD [ \
 | 
			
		||||
    "curl", "--fail", "http://127.0.0.1:3909" \
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
ENTRYPOINT [ "main" ]
 | 
			
		||||
CMD [ "/bin/main" ]
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										12
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								README.md
									
									
									
									
									
								
							@ -31,7 +31,7 @@ If you install Garage using Docker, you can install this web UI alongside Garage
 | 
			
		||||
```yml
 | 
			
		||||
services:
 | 
			
		||||
  garage:
 | 
			
		||||
    image: dxflrs/garage:v2.0.0
 | 
			
		||||
    image: dxflrs/garage:v1.0.1
 | 
			
		||||
    container_name: garage
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./garage.toml:/etc/garage.toml
 | 
			
		||||
@ -41,7 +41,7 @@ services:
 | 
			
		||||
    ports:
 | 
			
		||||
      - 3900:3900
 | 
			
		||||
      - 3901:3901
 | 
			
		||||
      - 3902:3902
 | 
			
		||||
      - 3902:3903
 | 
			
		||||
      - 3903:3903
 | 
			
		||||
 | 
			
		||||
  webui:
 | 
			
		||||
@ -62,7 +62,7 @@ services:
 | 
			
		||||
Get the latest binary from the [release page](https://github.com/khairul169/garage-webui/releases/latest) according to your OS architecture. For example:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
$ wget -O garage-webui https://github.com/khairul169/garage-webui/releases/download/1.1.0/garage-webui-v1.1.0-linux-amd64
 | 
			
		||||
$ wget -O garage-webui https://github.com/khairul169/garage-webui/releases/download/1.0.8/garage-webui-v1.0.8-linux-amd64
 | 
			
		||||
$ chmod +x garage-webui
 | 
			
		||||
$ sudo cp garage-webui /usr/local/bin
 | 
			
		||||
```
 | 
			
		||||
@ -136,11 +136,7 @@ admin_token = "YOUR_ADMIN_TOKEN_HERE"
 | 
			
		||||
metrics_token = "YOUR_METRICS_TOKEN_HERE"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
However, if it fails to load, you can set `API_BASE_URL` & `API_ADMIN_KEY` environment variables instead.
 | 
			
		||||
 | 
			
		||||
### Environment Variables
 | 
			
		||||
 | 
			
		||||
Configurable envs:
 | 
			
		||||
However, if it fails to load, you can set these environment variables instead:
 | 
			
		||||
 | 
			
		||||
- `CONFIG_PATH`: Path to the Garage `config.toml` file. Defaults to `/etc/garage.toml`.
 | 
			
		||||
- `BASE_PATH`: Base path or prefix for Web UI.
 | 
			
		||||
 | 
			
		||||
@ -146,20 +146,11 @@ func (b *Browse) GetOneObject(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	w.Header().Set("Content-Type", *object.ContentType)
 | 
			
		||||
	w.Header().Set("Content-Length", strconv.FormatInt(*object.ContentLength, 10))
 | 
			
		||||
	w.Header().Set("Cache-Control", "max-age=86400")
 | 
			
		||||
	w.Header().Set("Last-Modified", object.LastModified.Format(time.RFC1123))
 | 
			
		||||
 | 
			
		||||
	if object.ContentType != nil {
 | 
			
		||||
		w.Header().Set("Content-Type", *object.ContentType)
 | 
			
		||||
	} else {
 | 
			
		||||
		w.Header().Set("Content-Type", "application/octet-stream")
 | 
			
		||||
	}
 | 
			
		||||
	if object.ContentLength != nil {
 | 
			
		||||
		w.Header().Set("Content-Length", strconv.FormatInt(*object.ContentLength, 10))
 | 
			
		||||
	}
 | 
			
		||||
	if object.ETag != nil {
 | 
			
		||||
	w.Header().Set("Etag", *object.ETag)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = io.Copy(w, object.Body)
 | 
			
		||||
 | 
			
		||||
@ -292,7 +283,7 @@ func getBucketCredentials(bucket string) (aws.CredentialsProvider, error) {
 | 
			
		||||
		return cacheData.(aws.CredentialsProvider), nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	body, err := utils.Garage.Fetch("/v2/GetBucketInfo?globalAlias="+bucket, &utils.FetchOptions{})
 | 
			
		||||
	body, err := utils.Garage.Fetch("/v1/bucket?globalAlias="+bucket, &utils.FetchOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
@ -309,7 +300,7 @@ func getBucketCredentials(bucket string) (aws.CredentialsProvider, error) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		body, err := utils.Garage.Fetch(fmt.Sprintf("/v2/GetKeyInfo?id=%s&showSecretKey=true", k.AccessKeyID), &utils.FetchOptions{})
 | 
			
		||||
		body, err := utils.Garage.Fetch(fmt.Sprintf("/v1/key?id=%s&showSecretKey=true", k.AccessKeyID), &utils.FetchOptions{})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
@ -331,26 +322,15 @@ func getS3Client(bucket string) (*s3.Client, error) {
 | 
			
		||||
		return nil, fmt.Errorf("cannot get credentials for bucket %s: %w", bucket, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Determine endpoint and whether to disable HTTPS
 | 
			
		||||
	endpoint := utils.Garage.GetS3Endpoint()
 | 
			
		||||
	disableHTTPS := !strings.HasPrefix(endpoint, "https://")
 | 
			
		||||
 | 
			
		||||
	// AWS config without BaseEndpoint
 | 
			
		||||
	awsConfig := aws.Config{
 | 
			
		||||
		Credentials:  creds,
 | 
			
		||||
		Region:       utils.Garage.GetS3Region(),
 | 
			
		||||
		BaseEndpoint: aws.String(utils.Garage.GetS3Endpoint()),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Build S3 client with custom endpoint resolver for proper signing
 | 
			
		||||
	client := s3.NewFromConfig(awsConfig, func(o *s3.Options) {
 | 
			
		||||
		o.UsePathStyle = true
 | 
			
		||||
		o.EndpointOptions.DisableHTTPS = disableHTTPS
 | 
			
		||||
		o.EndpointResolver = s3.EndpointResolverFunc(func(region string, opts s3.EndpointResolverOptions) (aws.Endpoint, error) {
 | 
			
		||||
			return aws.Endpoint{
 | 
			
		||||
				URL:           endpoint,
 | 
			
		||||
				SigningRegion: utils.Garage.GetS3Region(),
 | 
			
		||||
			}, nil
 | 
			
		||||
		})
 | 
			
		||||
		o.EndpointOptions.DisableHTTPS = true
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return client, nil
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ import (
 | 
			
		||||
type Buckets struct{}
 | 
			
		||||
 | 
			
		||||
func (b *Buckets) GetAll(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	body, err := utils.Garage.Fetch("/v2/ListBuckets", &utils.FetchOptions{})
 | 
			
		||||
	body, err := utils.Garage.Fetch("/v1/bucket?list", &utils.FetchOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utils.ResponseError(w, err)
 | 
			
		||||
		return
 | 
			
		||||
@ -27,7 +27,7 @@ func (b *Buckets) GetAll(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
 | 
			
		||||
	for _, bucket := range buckets {
 | 
			
		||||
		go func() {
 | 
			
		||||
			body, err := utils.Garage.Fetch(fmt.Sprintf("/v2/GetBucketInfo?id=%s", bucket.ID), &utils.FetchOptions{})
 | 
			
		||||
			body, err := utils.Garage.Fetch(fmt.Sprintf("/v1/bucket?id=%s", bucket.ID), &utils.FetchOptions{})
 | 
			
		||||
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ch <- schema.Bucket{ID: bucket.ID, GlobalAliases: bucket.GlobalAliases}
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,6 @@ type GetBucketsRes struct {
 | 
			
		||||
	ID            string       `json:"id"`
 | 
			
		||||
	GlobalAliases []string     `json:"globalAliases"`
 | 
			
		||||
	LocalAliases  []LocalAlias `json:"localAliases"`
 | 
			
		||||
	Created       string       `json:"created"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Bucket struct {
 | 
			
		||||
@ -21,7 +20,6 @@ type Bucket struct {
 | 
			
		||||
	UnfinishedMultipartUploadParts int64         `json:"unfinishedMultipartUploadParts"`
 | 
			
		||||
	UnfinishedMultipartUploadBytes int64         `json:"unfinishedMultipartUploadBytes"`
 | 
			
		||||
	Quotas                         Quotas        `json:"quotas"`
 | 
			
		||||
	Created                        string        `json:"created"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LocalAlias struct {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
services:
 | 
			
		||||
  garage:
 | 
			
		||||
    image: dxflrs/garage:v2.0.0
 | 
			
		||||
    image: dxflrs/garage:v1.0.1
 | 
			
		||||
    container_name: garage
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./garage.toml:/etc/garage.toml
 | 
			
		||||
@ -24,3 +24,4 @@ services:
 | 
			
		||||
    environment:
 | 
			
		||||
      API_BASE_URL: "http://garage:3903"
 | 
			
		||||
      S3_ENDPOINT_URL: "http://garage:3900"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "garage-webui",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "version": "1.1.0",
 | 
			
		||||
  "version": "1.0.8",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev:client": "vite",
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,7 @@ export const useCreateBucket = (
 | 
			
		||||
  options?: UseMutationOptions<any, Error, CreateBucketSchema>
 | 
			
		||||
) => {
 | 
			
		||||
  return useMutation({
 | 
			
		||||
    mutationFn: (body) => api.post("/v2/CreateBucket", { body }),
 | 
			
		||||
    mutationFn: (body) => api.post("/v1/bucket", { body }),
 | 
			
		||||
    ...options,
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ import { Bucket, Permissions } from "../types";
 | 
			
		||||
export const useBucket = (id?: string | null) => {
 | 
			
		||||
  return useQuery({
 | 
			
		||||
    queryKey: ["bucket", id],
 | 
			
		||||
    queryFn: () => api.get<Bucket>("/v2/GetBucketInfo", { params: { id } }),
 | 
			
		||||
    queryFn: () => api.get<Bucket>("/v1/bucket", { params: { id } }),
 | 
			
		||||
    enabled: !!id,
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
@ -18,10 +18,7 @@ export const useBucket = (id?: string | null) => {
 | 
			
		||||
export const useUpdateBucket = (id?: string | null) => {
 | 
			
		||||
  return useMutation({
 | 
			
		||||
    mutationFn: (values: any) => {
 | 
			
		||||
      return api.post<any>("/v2/UpdateBucket", {
 | 
			
		||||
        params: { id },
 | 
			
		||||
        body: values,
 | 
			
		||||
      });
 | 
			
		||||
      return api.put<any>("/v1/bucket", { params: { id }, body: values });
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
@ -32,8 +29,8 @@ export const useAddAlias = (
 | 
			
		||||
) => {
 | 
			
		||||
  return useMutation({
 | 
			
		||||
    mutationFn: (alias: string) => {
 | 
			
		||||
      return api.post("/v2/AddBucketAlias", {
 | 
			
		||||
        body: { bucketId, globalAlias: alias },
 | 
			
		||||
      return api.put("/v1/bucket/alias/global", {
 | 
			
		||||
        params: { id: bucketId, alias },
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    ...options,
 | 
			
		||||
@ -46,8 +43,8 @@ export const useRemoveAlias = (
 | 
			
		||||
) => {
 | 
			
		||||
  return useMutation({
 | 
			
		||||
    mutationFn: (alias: string) => {
 | 
			
		||||
      return api.post("/v2/RemoveBucketAlias", {
 | 
			
		||||
        body: { bucketId, globalAlias: alias },
 | 
			
		||||
      return api.delete("/v1/bucket/alias/global", {
 | 
			
		||||
        params: { id: bucketId, alias },
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    ...options,
 | 
			
		||||
@ -65,7 +62,8 @@ export const useAllowKey = (
 | 
			
		||||
  return useMutation({
 | 
			
		||||
    mutationFn: async (payload) => {
 | 
			
		||||
      const promises = payload.map(async (key) => {
 | 
			
		||||
        return api.post("/v2/AllowBucketKey", {
 | 
			
		||||
        console.log("test", key);
 | 
			
		||||
        return api.post("/v1/bucket/allow", {
 | 
			
		||||
          body: {
 | 
			
		||||
            bucketId,
 | 
			
		||||
            accessKeyId: key.keyId,
 | 
			
		||||
@ -90,7 +88,7 @@ export const useDenyKey = (
 | 
			
		||||
) => {
 | 
			
		||||
  return useMutation({
 | 
			
		||||
    mutationFn: (payload) => {
 | 
			
		||||
      return api.post("/v2/DenyBucketKey", {
 | 
			
		||||
      return api.post("/v1/bucket/deny", {
 | 
			
		||||
        body: {
 | 
			
		||||
          bucketId,
 | 
			
		||||
          accessKeyId: payload.keyId,
 | 
			
		||||
@ -106,7 +104,7 @@ export const useRemoveBucket = (
 | 
			
		||||
  options?: MutationOptions<any, Error, string>
 | 
			
		||||
) => {
 | 
			
		||||
  return useMutation({
 | 
			
		||||
    mutationFn: (id) => api.post("/v2/DeleteBucket", { params: { id } }),
 | 
			
		||||
    mutationFn: (id) => api.delete("/v1/bucket", { params: { id } }),
 | 
			
		||||
    ...options,
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -108,7 +108,7 @@ const NodesList = ({ nodes }: NodeListProps) => {
 | 
			
		||||
 | 
			
		||||
  const onRevert = () => {
 | 
			
		||||
    if (
 | 
			
		||||
      window.confirm("Are you sure you want to revert any changes made?") &&
 | 
			
		||||
      window.confirm("Are you sure you want to revert layout changes?") &&
 | 
			
		||||
      data?.version != null
 | 
			
		||||
    ) {
 | 
			
		||||
      revertChanges.mutate(data?.version + 1);
 | 
			
		||||
@ -117,7 +117,7 @@ const NodesList = ({ nodes }: NodeListProps) => {
 | 
			
		||||
 | 
			
		||||
  const onApply = () => {
 | 
			
		||||
    if (
 | 
			
		||||
      window.confirm("Are you sure you want to apply your layout changes?") &&
 | 
			
		||||
      window.confirm("Are you sure you want to revert layout changes?") &&
 | 
			
		||||
      data?.version != null
 | 
			
		||||
    ) {
 | 
			
		||||
      applyChanges.mutate(data?.version + 1);
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@ import {
 | 
			
		||||
  ApplyLayoutResult,
 | 
			
		||||
  AssignNodeBody,
 | 
			
		||||
  GetClusterLayoutResult,
 | 
			
		||||
  GetNodeInfoResult,
 | 
			
		||||
  GetStatusResult,
 | 
			
		||||
} from "./types";
 | 
			
		||||
import {
 | 
			
		||||
@ -12,37 +11,24 @@ import {
 | 
			
		||||
  useQuery,
 | 
			
		||||
} from "@tanstack/react-query";
 | 
			
		||||
 | 
			
		||||
export const useNodeInfo = () => {
 | 
			
		||||
  return useQuery({
 | 
			
		||||
    queryKey: ["node-info"],
 | 
			
		||||
    queryFn: () =>
 | 
			
		||||
      api.get<GetNodeInfoResult>("/v2/GetNodeInfo", {
 | 
			
		||||
        params: { node: "self" },
 | 
			
		||||
      }),
 | 
			
		||||
    select: (data) => Object.values(data?.success || {})?.[0],
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useClusterStatus = () => {
 | 
			
		||||
  return useQuery({
 | 
			
		||||
    queryKey: ["status"],
 | 
			
		||||
    queryFn: () => api.get<GetStatusResult>("/v2/GetClusterStatus"),
 | 
			
		||||
    queryFn: () => api.get<GetStatusResult>("/v1/status"),
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useClusterLayout = () => {
 | 
			
		||||
  return useQuery({
 | 
			
		||||
    queryKey: ["layout"],
 | 
			
		||||
    queryFn: () => api.get<GetClusterLayoutResult>("/v2/GetClusterLayout"),
 | 
			
		||||
    queryFn: () => api.get<GetClusterLayoutResult>("/v1/layout"),
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useConnectNode = (options?: Partial<UseMutationOptions>) => {
 | 
			
		||||
  return useMutation<any, Error, string>({
 | 
			
		||||
    mutationFn: async (nodeId) => {
 | 
			
		||||
      const [res] = await api.post("/v2/ConnectClusterNodes", {
 | 
			
		||||
        body: [nodeId],
 | 
			
		||||
      });
 | 
			
		||||
      const [res] = await api.post("/v1/connect", { body: [nodeId] });
 | 
			
		||||
      if (!res.success) {
 | 
			
		||||
        throw new Error(res.error || "Unknown error");
 | 
			
		||||
      }
 | 
			
		||||
@ -54,10 +40,7 @@ export const useConnectNode = (options?: Partial<UseMutationOptions>) => {
 | 
			
		||||
 | 
			
		||||
export const useAssignNode = (options?: Partial<UseMutationOptions>) => {
 | 
			
		||||
  return useMutation<any, Error, AssignNodeBody>({
 | 
			
		||||
    mutationFn: (data) =>
 | 
			
		||||
      api.post("/v2/UpdateClusterLayout", {
 | 
			
		||||
        body: { parameters: null, roles: [data] },
 | 
			
		||||
      }),
 | 
			
		||||
    mutationFn: (data) => api.post("/v1/layout", { body: [data] }),
 | 
			
		||||
    ...(options as any),
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
@ -65,16 +48,15 @@ export const useAssignNode = (options?: Partial<UseMutationOptions>) => {
 | 
			
		||||
export const useUnassignNode = (options?: Partial<UseMutationOptions>) => {
 | 
			
		||||
  return useMutation<any, Error, string>({
 | 
			
		||||
    mutationFn: (nodeId) =>
 | 
			
		||||
      api.post("/v2/UpdateClusterLayout", {
 | 
			
		||||
        body: { parameters: null, roles: [{ id: nodeId, remove: true }] },
 | 
			
		||||
      }),
 | 
			
		||||
      api.post("/v1/layout", { body: [{ id: nodeId, remove: true }] }),
 | 
			
		||||
    ...(options as any),
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useRevertChanges = (options?: Partial<UseMutationOptions>) => {
 | 
			
		||||
  return useMutation<any, Error, number>({
 | 
			
		||||
    mutationFn: () => api.post("/v2/RevertClusterLayout"),
 | 
			
		||||
    mutationFn: (version) =>
 | 
			
		||||
      api.post("/v1/layout/revert", { body: { version } }),
 | 
			
		||||
    ...(options as any),
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
@ -82,7 +64,7 @@ export const useRevertChanges = (options?: Partial<UseMutationOptions>) => {
 | 
			
		||||
export const useApplyChanges = (options?: Partial<UseMutationOptions>) => {
 | 
			
		||||
  return useMutation<ApplyLayoutResult, Error, number>({
 | 
			
		||||
    mutationFn: (version) =>
 | 
			
		||||
      api.post("/v2/ApplyClusterLayout", { body: { version } }),
 | 
			
		||||
      api.post("/v1/layout/apply", { body: { version } }),
 | 
			
		||||
    ...(options as any),
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,11 @@
 | 
			
		||||
import Page from "@/context/page-context";
 | 
			
		||||
import { useClusterStatus, useNodeInfo } from "./hooks";
 | 
			
		||||
import { useClusterStatus } from "./hooks";
 | 
			
		||||
import { Card } from "react-daisyui";
 | 
			
		||||
import NodesList from "./components/nodes-list";
 | 
			
		||||
import { useMemo } from "react";
 | 
			
		||||
 | 
			
		||||
const ClusterPage = () => {
 | 
			
		||||
  const { data } = useClusterStatus();
 | 
			
		||||
  const { data: node } = useNodeInfo();
 | 
			
		||||
 | 
			
		||||
  const nodes = useMemo(() => {
 | 
			
		||||
    if (!data) return [];
 | 
			
		||||
 | 
			
		||||
@ -29,13 +27,13 @@ const ClusterPage = () => {
 | 
			
		||||
        <Card.Body className="gap-1">
 | 
			
		||||
          <Card.Title className="mb-2">Details</Card.Title>
 | 
			
		||||
 | 
			
		||||
          {/* <DetailItem title="Node ID" value={node?.nodeId} /> */}
 | 
			
		||||
          <DetailItem title="Garage Version" value={node?.garageVersion} />
 | 
			
		||||
          <DetailItem title="Node ID" value={data?.node} />
 | 
			
		||||
          <DetailItem title="Version" value={data?.garageVersion} />
 | 
			
		||||
          {/* <DetailItem title="Rust version" value={data?.rustVersion} /> */}
 | 
			
		||||
          <DetailItem title="DB engine" value={node?.dbEngine} />
 | 
			
		||||
          <DetailItem title="DB engine" value={data?.dbEngine} />
 | 
			
		||||
          <DetailItem
 | 
			
		||||
            title="Layout version"
 | 
			
		||||
            value={data?.layoutVersion || data?.layout?.version || "-"}
 | 
			
		||||
            value={data?.layoutVersion || data?.layout?.version}
 | 
			
		||||
          />
 | 
			
		||||
        </Card.Body>
 | 
			
		||||
      </Card>
 | 
			
		||||
 | 
			
		||||
@ -1,19 +1,5 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
export type GetNodeInfoResult = {
 | 
			
		||||
  success: {
 | 
			
		||||
    [key: string]: {
 | 
			
		||||
      nodeId:         string;
 | 
			
		||||
      garageVersion:  string;
 | 
			
		||||
      garageFeatures: string[];
 | 
			
		||||
      rustVersion:    string;
 | 
			
		||||
      dbEngine:       string;
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
  error:   Error;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export type GetStatusResult = {
 | 
			
		||||
  node: string;
 | 
			
		||||
  garageVersion: string;
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,6 @@ import { useQuery } from "@tanstack/react-query";
 | 
			
		||||
export const useNodesHealth = () => {
 | 
			
		||||
  return useQuery({
 | 
			
		||||
    queryKey: ["health"],
 | 
			
		||||
    queryFn: () => api.get<GetHealthResult>("/v2/GetClusterHealth"),
 | 
			
		||||
    queryFn: () => api.get<GetHealthResult>("/v1/health"),
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -56,7 +56,7 @@ const HomePage = () => {
 | 
			
		||||
        <StatsCard
 | 
			
		||||
          title="Active Storage Nodes"
 | 
			
		||||
          icon={DatabaseZap}
 | 
			
		||||
          value={health?.storageNodesUp}
 | 
			
		||||
          value={health?.storageNodesOk}
 | 
			
		||||
        />
 | 
			
		||||
        <StatsCard
 | 
			
		||||
          title="Partitions"
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ export type GetHealthResult = {
 | 
			
		||||
  knownNodes: number;
 | 
			
		||||
  connectedNodes: number;
 | 
			
		||||
  storageNodes: number;
 | 
			
		||||
  storageNodesUp: number;
 | 
			
		||||
  storageNodesOk: number;
 | 
			
		||||
  partitions: number;
 | 
			
		||||
  partitionsQuorum: number;
 | 
			
		||||
  partitionsAllOk: number;
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ import { CreateKeySchema } from "./schema";
 | 
			
		||||
export const useKeys = () => {
 | 
			
		||||
  return useQuery({
 | 
			
		||||
    queryKey: ["keys"],
 | 
			
		||||
    queryFn: () => api.get<Key[]>("/v2/ListKeys"),
 | 
			
		||||
    queryFn: () => api.get<Key[]>("/v1/key?list"),
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -20,9 +20,9 @@ export const useCreateKey = (
 | 
			
		||||
  return useMutation({
 | 
			
		||||
    mutationFn: async (body) => {
 | 
			
		||||
      if (body.isImport) {
 | 
			
		||||
        return api.post("/v2/ImportKey", { body });
 | 
			
		||||
        return api.post("/v1/key/import", { body });
 | 
			
		||||
      }
 | 
			
		||||
      return api.post("/v2/CreateKey", { body });
 | 
			
		||||
      return api.post("/v1/key", { body });
 | 
			
		||||
    },
 | 
			
		||||
    ...options,
 | 
			
		||||
  });
 | 
			
		||||
@ -32,7 +32,7 @@ export const useRemoveKey = (
 | 
			
		||||
  options?: UseMutationOptions<any, Error, string>
 | 
			
		||||
) => {
 | 
			
		||||
  return useMutation({
 | 
			
		||||
    mutationFn: (id) => api.post("/v2/DeleteKey", { params: { id } }),
 | 
			
		||||
    mutationFn: (id) => api.delete("/v1/key", { params: { id } }),
 | 
			
		||||
    ...options,
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@ const KeysPage = () => {
 | 
			
		||||
 | 
			
		||||
  const fetchSecretKey = useCallback(async (id: string) => {
 | 
			
		||||
    try {
 | 
			
		||||
      const result = await api.get("/v2/GetKeyInfo", {
 | 
			
		||||
      const result = await api.get("/v1/key", {
 | 
			
		||||
        params: { id, showSecretKey: "true" },
 | 
			
		||||
      });
 | 
			
		||||
      if (!result?.secretAccessKey) {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user