mirror of
https://github.com/khairul169/garage-webui.git
synced 2026-07-28 11:00:19 +07:00
Merge c6d4315c52ed8faceb8e2f901d736ab571a2be57 into ee420fbf2946e9f79977615cee5e29192d7da478
This commit is contained in:
commit
6bd93b110c
@ -1,7 +1,7 @@
|
|||||||
FROM node:20-slim AS frontend
|
FROM node:20-slim AS frontend
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN npm install -g corepack@latest && corepack use pnpm@latest
|
RUN npm install -g pnpm@9.15.9
|
||||||
|
|
||||||
COPY package.json pnpm-lock.yaml ./
|
COPY package.json pnpm-lock.yaml ./
|
||||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||||
|
|||||||
@ -55,6 +55,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
API_BASE_URL: "http://garage:3903"
|
API_BASE_URL: "http://garage:3903"
|
||||||
S3_ENDPOINT_URL: "http://garage:3900"
|
S3_ENDPOINT_URL: "http://garage:3900"
|
||||||
|
S3_WEB_ENDPOINT_URL: "http://garage:3902"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Without Docker
|
### Without Docker
|
||||||
@ -148,6 +149,7 @@ Configurable envs:
|
|||||||
- `API_ADMIN_KEY`: Admin API key.
|
- `API_ADMIN_KEY`: Admin API key.
|
||||||
- `S3_REGION`: S3 Region.
|
- `S3_REGION`: S3 Region.
|
||||||
- `S3_ENDPOINT_URL`: S3 Endpoint url.
|
- `S3_ENDPOINT_URL`: S3 Endpoint url.
|
||||||
|
- `S3_WEB_ENDPOINT_URL`: Garage web endpoint used for anonymous reads of objects in buckets with website access enabled.
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|
||||||
|
|||||||
@ -3,4 +3,5 @@ BASE_PATH=""
|
|||||||
AUTH_USER_PASS='username:$2y$10$DSTi9o0uQPEHSNlf66xMEOgm9KgVNBP3vHxA3SK0Xha2EVMb3mTXm'
|
AUTH_USER_PASS='username:$2y$10$DSTi9o0uQPEHSNlf66xMEOgm9KgVNBP3vHxA3SK0Xha2EVMb3mTXm'
|
||||||
API_BASE_URL="http://garage:3903"
|
API_BASE_URL="http://garage:3903"
|
||||||
S3_ENDPOINT_URL="http://garage:3900"
|
S3_ENDPOINT_URL="http://garage:3900"
|
||||||
|
S3_WEB_ENDPOINT_URL="http://garage:3902"
|
||||||
API_ADMIN_KEY=""
|
API_ADMIN_KEY=""
|
||||||
|
|||||||
@ -7,17 +7,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func AuthMiddleware(next http.Handler) http.Handler {
|
func AuthMiddleware(next http.Handler) http.Handler {
|
||||||
authData := utils.GetEnv("AUTH_USER_PASS", "")
|
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
auth := utils.Session.Get(r, "authenticated")
|
if !IsAuthenticated(r) {
|
||||||
|
|
||||||
if authData == "" {
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if auth == nil || !auth.(bool) {
|
|
||||||
utils.ResponseErrorStatus(w, errors.New("unauthorized"), http.StatusUnauthorized)
|
utils.ResponseErrorStatus(w, errors.New("unauthorized"), http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -25,3 +16,12 @@ func AuthMiddleware(next http.Handler) http.Handler {
|
|||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsAuthenticated(r *http.Request) bool {
|
||||||
|
if utils.GetEnv("AUTH_USER_PASS", "") == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
auth, ok := utils.Session.Get(r, "authenticated").(bool)
|
||||||
|
return ok && auth
|
||||||
|
}
|
||||||
|
|||||||
@ -6,9 +6,12 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"khairul169/garage-webui/middleware"
|
||||||
"khairul169/garage-webui/schema"
|
"khairul169/garage-webui/schema"
|
||||||
"khairul169/garage-webui/utils"
|
"khairul169/garage-webui/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -81,6 +84,11 @@ func (b *Browse) GetObjects(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Browse) GetOneObject(w http.ResponseWriter, r *http.Request) {
|
func (b *Browse) GetOneObject(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !middleware.IsAuthenticated(r) {
|
||||||
|
b.getPublicObject(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
bucket := r.PathValue("bucket")
|
bucket := r.PathValue("bucket")
|
||||||
key := r.PathValue("key")
|
key := r.PathValue("key")
|
||||||
queryParams := r.URL.Query()
|
queryParams := r.URL.Query()
|
||||||
@ -169,6 +177,43 @@ func (b *Browse) GetOneObject(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Browse) getPublicObject(w http.ResponseWriter, r *http.Request) {
|
||||||
|
target, err := url.Parse(utils.Garage.GetS3WebEndpoint())
|
||||||
|
if err != nil || target.Scheme == "" || target.Host == "" {
|
||||||
|
utils.ResponseErrorStatus(w, errors.New("invalid Garage web endpoint"), http.StatusBadGateway)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket := r.PathValue("bucket")
|
||||||
|
key := r.PathValue("key")
|
||||||
|
download := r.URL.Query().Get("dl") == "1"
|
||||||
|
|
||||||
|
proxy := &httputil.ReverseProxy{
|
||||||
|
Rewrite: func(proxyRequest *httputil.ProxyRequest) {
|
||||||
|
proxyRequest.Out.URL.Scheme = target.Scheme
|
||||||
|
proxyRequest.Out.URL.Host = target.Host
|
||||||
|
proxyRequest.Out.URL.Path = strings.TrimSuffix(target.Path, "/") + "/" + strings.TrimPrefix(key, "/")
|
||||||
|
proxyRequest.Out.URL.RawPath = ""
|
||||||
|
proxyRequest.Out.URL.RawQuery = ""
|
||||||
|
proxyRequest.Out.Host = bucket
|
||||||
|
proxyRequest.Out.Header.Del("Authorization")
|
||||||
|
proxyRequest.Out.Header.Del("Cookie")
|
||||||
|
},
|
||||||
|
ModifyResponse: func(response *http.Response) error {
|
||||||
|
if download && response.StatusCode >= 200 && response.StatusCode < 300 {
|
||||||
|
parts := strings.Split(key, "/")
|
||||||
|
response.Header.Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", parts[len(parts)-1]))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
ErrorHandler: func(w http.ResponseWriter, _ *http.Request, err error) {
|
||||||
|
utils.ResponseErrorStatus(w, fmt.Errorf("cannot fetch public object: %w", err), http.StatusBadGateway)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Browse) PutObject(w http.ResponseWriter, r *http.Request) {
|
func (b *Browse) PutObject(w http.ResponseWriter, r *http.Request) {
|
||||||
bucket := r.PathValue("bucket")
|
bucket := r.PathValue("bucket")
|
||||||
key := r.PathValue("key")
|
key := r.PathValue("key")
|
||||||
|
|||||||
65
backend/router/browse_test.go
Normal file
65
backend/router/browse_test.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"khairul169/garage-webui/utils"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAnonymousObjectUsesGarageWebEndpoint(t *testing.T) {
|
||||||
|
webEndpoint := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Host != "public-bucket" {
|
||||||
|
t.Errorf("expected bucket host, got %q", r.Host)
|
||||||
|
}
|
||||||
|
if r.URL.Path != "/folder/file name.txt" {
|
||||||
|
t.Errorf("expected object path, got %q", r.URL.Path)
|
||||||
|
}
|
||||||
|
if r.URL.RawQuery != "" {
|
||||||
|
t.Errorf("expected UI query parameters to be removed, got %q", r.URL.RawQuery)
|
||||||
|
}
|
||||||
|
if r.Header.Get("Authorization") != "" {
|
||||||
|
t.Error("authorization header was forwarded to the public endpoint")
|
||||||
|
}
|
||||||
|
if r.Header.Get("Cookie") != "" {
|
||||||
|
t.Error("cookie header was forwarded to the public endpoint")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
_, _ = io.WriteString(w, "public content")
|
||||||
|
}))
|
||||||
|
defer webEndpoint.Close()
|
||||||
|
|
||||||
|
t.Setenv("AUTH_USER_PASS", "user:hash")
|
||||||
|
t.Setenv("S3_WEB_ENDPOINT_URL", webEndpoint.URL)
|
||||||
|
sessionManager := utils.InitSessionManager()
|
||||||
|
handler := sessionManager.LoadAndSave(HandleApiRouter())
|
||||||
|
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/browse/public-bucket/folder/file%20name.txt?view=1", nil)
|
||||||
|
request.Header.Set("Authorization", "Bearer private-token")
|
||||||
|
request.Header.Set("Cookie", "dashboard=private-session")
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(response, request)
|
||||||
|
|
||||||
|
if response.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected status 200, got %d: %s", response.Code, response.Body.String())
|
||||||
|
}
|
||||||
|
if response.Body.String() != "public content" {
|
||||||
|
t.Errorf("expected proxied object body, got %q", response.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnonymousBucketListingStillRequiresAuthentication(t *testing.T) {
|
||||||
|
t.Setenv("AUTH_USER_PASS", "user:hash")
|
||||||
|
sessionManager := utils.InitSessionManager()
|
||||||
|
handler := sessionManager.LoadAndSave(HandleApiRouter())
|
||||||
|
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/browse/private-bucket", nil)
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(response, request)
|
||||||
|
|
||||||
|
if response.Code != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("expected status 401, got %d: %s", response.Code, response.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,6 +11,10 @@ func HandleApiRouter() *http.ServeMux {
|
|||||||
auth := &Auth{}
|
auth := &Auth{}
|
||||||
mux.HandleFunc("POST /auth/login", auth.Login)
|
mux.HandleFunc("POST /auth/login", auth.Login)
|
||||||
|
|
||||||
|
browse := &Browse{}
|
||||||
|
mux.Handle("GET /browse/{bucket}", middleware.AuthMiddleware(http.HandlerFunc(browse.GetObjects)))
|
||||||
|
mux.HandleFunc("GET /browse/{bucket}/{key...}", browse.GetOneObject)
|
||||||
|
|
||||||
router := http.NewServeMux()
|
router := http.NewServeMux()
|
||||||
router.HandleFunc("POST /auth/logout", auth.Logout)
|
router.HandleFunc("POST /auth/logout", auth.Logout)
|
||||||
router.HandleFunc("GET /auth/status", auth.GetStatus)
|
router.HandleFunc("GET /auth/status", auth.GetStatus)
|
||||||
@ -21,9 +25,6 @@ func HandleApiRouter() *http.ServeMux {
|
|||||||
buckets := &Buckets{}
|
buckets := &Buckets{}
|
||||||
router.HandleFunc("GET /buckets", buckets.GetAll)
|
router.HandleFunc("GET /buckets", buckets.GetAll)
|
||||||
|
|
||||||
browse := &Browse{}
|
|
||||||
router.HandleFunc("GET /browse/{bucket}", browse.GetObjects)
|
|
||||||
router.HandleFunc("GET /browse/{bucket}/{key...}", browse.GetOneObject)
|
|
||||||
router.HandleFunc("PUT /browse/{bucket}/{key...}", browse.PutObject)
|
router.HandleFunc("PUT /browse/{bucket}/{key...}", browse.PutObject)
|
||||||
router.HandleFunc("DELETE /browse/{bucket}/{key...}", browse.DeleteObject)
|
router.HandleFunc("DELETE /browse/{bucket}/{key...}", browse.DeleteObject)
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"khairul169/garage-webui/schema"
|
"khairul169/garage-webui/schema"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -74,6 +76,64 @@ func (g *garage) GetS3Endpoint() string {
|
|||||||
return endpoint
|
return endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *garage) GetS3WebEndpoint() string {
|
||||||
|
endpoint := os.Getenv("S3_WEB_ENDPOINT_URL")
|
||||||
|
if len(endpoint) > 0 {
|
||||||
|
return endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
port := endpointPort(g.Config.S3Web.BindAddr, "3902")
|
||||||
|
for _, candidate := range []string{
|
||||||
|
os.Getenv("S3_ENDPOINT_URL"),
|
||||||
|
os.Getenv("API_BASE_URL"),
|
||||||
|
g.Config.RPCPublicAddr,
|
||||||
|
} {
|
||||||
|
host, scheme := endpointHost(candidate)
|
||||||
|
if host == "" || isTemplateValue(host) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s://%s", scheme, net.JoinHostPort(host, port))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func endpointHost(endpoint string) (string, string) {
|
||||||
|
if endpoint == "" {
|
||||||
|
return "", "http"
|
||||||
|
}
|
||||||
|
|
||||||
|
scheme := "http"
|
||||||
|
value := endpoint
|
||||||
|
if !strings.Contains(value, "://") {
|
||||||
|
value = "//" + value
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := url.Parse(value)
|
||||||
|
if err != nil {
|
||||||
|
return "", scheme
|
||||||
|
}
|
||||||
|
if parsed.Scheme != "" {
|
||||||
|
scheme = parsed.Scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed.Hostname(), scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
func endpointPort(bindAddr string, defaultPort string) string {
|
||||||
|
_, port, err := net.SplitHostPort(bindAddr)
|
||||||
|
if err == nil && port != "" {
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultPort
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTemplateValue(value string) bool {
|
||||||
|
return strings.HasPrefix(value, "__") && strings.HasSuffix(value, "__")
|
||||||
|
}
|
||||||
|
|
||||||
func (g *garage) GetS3Region() string {
|
func (g *garage) GetS3Region() string {
|
||||||
endpoint := os.Getenv("S3_REGION")
|
endpoint := os.Getenv("S3_REGION")
|
||||||
if len(endpoint) > 0 {
|
if len(endpoint) > 0 {
|
||||||
|
|||||||
33
backend/utils/garage_test.go
Normal file
33
backend/utils/garage_test.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"khairul169/garage-webui/schema"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetS3WebEndpointUsesS3ServiceHost(t *testing.T) {
|
||||||
|
t.Setenv("S3_WEB_ENDPOINT_URL", "")
|
||||||
|
t.Setenv("S3_ENDPOINT_URL", "http://garage:3900")
|
||||||
|
t.Setenv("API_BASE_URL", "http://garage-admin:3903")
|
||||||
|
|
||||||
|
garage := &garage{Config: schema.Config{
|
||||||
|
RPCPublicAddr: "__RPC_PUBLIC_ADDR__:3901",
|
||||||
|
S3Web: schema.S3Web{
|
||||||
|
BindAddr: "[::]:3902",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
if endpoint := garage.GetS3WebEndpoint(); endpoint != "http://garage:3902" {
|
||||||
|
t.Fatalf("expected S3 service host, got %q", endpoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetS3WebEndpointPrefersExplicitEndpoint(t *testing.T) {
|
||||||
|
t.Setenv("S3_WEB_ENDPOINT_URL", "http://garage-web.storage:8080")
|
||||||
|
t.Setenv("S3_ENDPOINT_URL", "http://garage:3900")
|
||||||
|
|
||||||
|
garage := &garage{}
|
||||||
|
if endpoint := garage.GetS3WebEndpoint(); endpoint != "http://garage-web.storage:8080" {
|
||||||
|
t.Fatalf("expected explicit web endpoint, got %q", endpoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,7 +10,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 3900:3900
|
- 3900:3900
|
||||||
- 3901:3901
|
- 3901:3901
|
||||||
- 3902:3903
|
- 3902:3902
|
||||||
- 3903:3903
|
- 3903:3903
|
||||||
|
|
||||||
webui:
|
webui:
|
||||||
@ -24,3 +24,4 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
API_BASE_URL: "http://garage:3903"
|
API_BASE_URL: "http://garage:3903"
|
||||||
S3_ENDPOINT_URL: "http://garage:3900"
|
S3_ENDPOINT_URL: "http://garage:3900"
|
||||||
|
S3_WEB_ENDPOINT_URL: "http://garage:3902"
|
||||||
|
|||||||
@ -42,7 +42,7 @@ const ShareDialog = () => {
|
|||||||
{!bucket.websiteAccess && (
|
{!bucket.websiteAccess && (
|
||||||
<Alert className="mb-4 items-start text-sm">
|
<Alert className="mb-4 items-start text-sm">
|
||||||
<FileWarningIcon className="mt-1" />
|
<FileWarningIcon className="mt-1" />
|
||||||
Sharing is only available for buckets with enabled website access.
|
Sharing is only available when HTTP Access is set to Anonymous HTTP.
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-row overflow-x-auto pb-2">
|
<div className="flex flex-row overflow-x-auto pb-2">
|
||||||
|
|||||||
@ -8,8 +8,14 @@ import { useConfig } from "@/hooks/useConfig";
|
|||||||
import { Info, LinkIcon } from "lucide-react";
|
import { Info, LinkIcon } from "lucide-react";
|
||||||
import Button from "@/components/ui/button";
|
import Button from "@/components/ui/button";
|
||||||
import { InputField } from "@/components/ui/input";
|
import { InputField } from "@/components/ui/input";
|
||||||
import { ToggleField } from "@/components/ui/toggle";
|
|
||||||
import { useBucketContext } from "../context";
|
import { useBucketContext } from "../context";
|
||||||
|
import FormControl from "@/components/ui/form-control";
|
||||||
|
import Select from "@/components/ui/select";
|
||||||
|
|
||||||
|
const accessOptions = [
|
||||||
|
{ label: "Private (login required)", value: "private" },
|
||||||
|
{ label: "Anonymous HTTP", value: "anonymous" },
|
||||||
|
] as const;
|
||||||
|
|
||||||
const WebsiteAccessSection = () => {
|
const WebsiteAccessSection = () => {
|
||||||
const { bucket: data, bucketName } = useBucketContext();
|
const { bucket: data, bucketName } = useBucketContext();
|
||||||
@ -17,7 +23,8 @@ const WebsiteAccessSection = () => {
|
|||||||
const form = useForm<WebsiteConfigSchema>({
|
const form = useForm<WebsiteConfigSchema>({
|
||||||
resolver: zodResolver(websiteConfigSchema),
|
resolver: zodResolver(websiteConfigSchema),
|
||||||
});
|
});
|
||||||
const isEnabled = useWatch({ control: form.control, name: "websiteAccess" });
|
const accessMode = useWatch({ control: form.control, name: "accessMode" });
|
||||||
|
const isAnonymous = accessMode === "anonymous";
|
||||||
|
|
||||||
const websitePort = config?.s3_web?.bind_addr?.split(":").pop() || "80";
|
const websitePort = config?.s3_web?.bind_addr?.split(":").pop() || "80";
|
||||||
const rootDomain = config?.s3_web?.root_domain;
|
const rootDomain = config?.s3_web?.root_domain;
|
||||||
@ -25,24 +32,24 @@ const WebsiteAccessSection = () => {
|
|||||||
const updateMutation = useUpdateBucket(data?.id);
|
const updateMutation = useUpdateBucket(data?.id);
|
||||||
|
|
||||||
const onChange = useDebounce((values: DeepPartial<WebsiteConfigSchema>) => {
|
const onChange = useDebounce((values: DeepPartial<WebsiteConfigSchema>) => {
|
||||||
const data = {
|
const websiteAccess = {
|
||||||
enabled: values.websiteAccess,
|
enabled: values.accessMode === "anonymous",
|
||||||
indexDocument: values.websiteAccess
|
indexDocument: values.accessMode === "anonymous"
|
||||||
? values.websiteConfig?.indexDocument
|
? values.websiteConfig?.indexDocument
|
||||||
: undefined,
|
: undefined,
|
||||||
errorDocument: values.websiteAccess
|
errorDocument: values.accessMode === "anonymous"
|
||||||
? values.websiteConfig?.errorDocument
|
? values.websiteConfig?.errorDocument
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
updateMutation.mutate({
|
updateMutation.mutate({
|
||||||
websiteAccess: data,
|
websiteAccess,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.reset({
|
form.reset({
|
||||||
websiteAccess: data?.websiteAccess,
|
accessMode: data?.websiteAccess ? "anonymous" : "private",
|
||||||
websiteConfig: {
|
websiteConfig: {
|
||||||
indexDocument: data?.websiteConfig?.indexDocument || "index.html",
|
indexDocument: data?.websiteConfig?.indexDocument || "index.html",
|
||||||
errorDocument: data?.websiteConfig?.errorDocument || "error/400.html",
|
errorDocument: data?.websiteConfig?.errorDocument || "error/400.html",
|
||||||
@ -56,7 +63,7 @@ const WebsiteAccessSection = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<div className="flex flex-row gap-2">
|
<div className="flex flex-row gap-2">
|
||||||
<p className="label label-text py-0 grow-0">Website Access</p>
|
<p className="label label-text py-0 grow-0">HTTP Access</p>
|
||||||
<Button
|
<Button
|
||||||
href="https://garagehq.deuxfleurs.fr/documentation/cookbook/exposing-websites"
|
href="https://garagehq.deuxfleurs.fr/documentation/cookbook/exposing-websites"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -68,9 +75,25 @@ const WebsiteAccessSection = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ToggleField form={form} name="websiteAccess" label="Enabled" />
|
<FormControl
|
||||||
|
form={form}
|
||||||
|
name="accessMode"
|
||||||
|
className="mt-2"
|
||||||
|
render={(field) => (
|
||||||
|
<Select
|
||||||
|
{...field}
|
||||||
|
value={accessOptions.find((option) => option.value === field.value)}
|
||||||
|
options={accessOptions}
|
||||||
|
onChange={(option) =>
|
||||||
|
field.onChange(
|
||||||
|
(option as { value?: "private" | "anonymous" } | null)?.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
{isEnabled && (
|
{isAnonymous && (
|
||||||
<>
|
<>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<InputField
|
<InputField
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export const addAliasSchema = z.object({
|
|||||||
export type AddAliasSchema = z.infer<typeof addAliasSchema>;
|
export type AddAliasSchema = z.infer<typeof addAliasSchema>;
|
||||||
|
|
||||||
export const websiteConfigSchema = z.object({
|
export const websiteConfigSchema = z.object({
|
||||||
websiteAccess: z.boolean(),
|
accessMode: z.enum(["private", "anonymous"]),
|
||||||
websiteConfig: z
|
websiteConfig: z
|
||||||
.object({ indexDocument: z.string(), errorDocument: z.string() })
|
.object({ indexDocument: z.string(), errorDocument: z.string() })
|
||||||
.nullish(),
|
.nullish(),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user