mirror of
https://github.com/wgh136/nysoure.git
synced 2025-09-27 12:17:24 +00:00
add domain parameter to createS3Storage and update related components
This commit is contained in:
@@ -448,8 +448,14 @@ class Network {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async createS3Storage(name: string, endPoint: string, accessKeyID: string,
|
async createS3Storage(
|
||||||
secretAccessKey: string, bucketName: string, maxSizeInMB: number): Promise<Response<any>> {
|
name: string,
|
||||||
|
endPoint: string,
|
||||||
|
accessKeyID: string,
|
||||||
|
secretAccessKey: string,
|
||||||
|
bucketName: string,
|
||||||
|
maxSizeInMB: number,
|
||||||
|
domain: string): Promise<Response<any>> {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(`${this.apiBaseUrl}/storage/s3`, {
|
const response = await axios.post(`${this.apiBaseUrl}/storage/s3`, {
|
||||||
name,
|
name,
|
||||||
@@ -457,7 +463,8 @@ class Network {
|
|||||||
accessKeyID,
|
accessKeyID,
|
||||||
secretAccessKey,
|
secretAccessKey,
|
||||||
bucketName,
|
bucketName,
|
||||||
maxSizeInMB
|
maxSizeInMB,
|
||||||
|
domain
|
||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import {useEffect, useState} from "react";
|
import { useEffect, useState } from "react";
|
||||||
import {Storage} from "../network/models.ts";
|
import { Storage } from "../network/models.ts";
|
||||||
import {network} from "../network/network.ts";
|
import { network } from "../network/network.ts";
|
||||||
import showToast from "../components/toast.ts";
|
import showToast from "../components/toast.ts";
|
||||||
import Loading from "../components/loading.tsx";
|
import Loading from "../components/loading.tsx";
|
||||||
import {MdAdd, MdDelete} from "react-icons/md";
|
import { MdAdd, MdDelete } from "react-icons/md";
|
||||||
import {ErrorAlert} from "../components/alert.tsx";
|
import { ErrorAlert } from "../components/alert.tsx";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { app } from "../app.ts";
|
import { app } from "../app.ts";
|
||||||
|
|
||||||
@@ -30,15 +30,15 @@ export default function StorageView() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!app.user) {
|
if (!app.user) {
|
||||||
return <ErrorAlert className={"m-4"} message={t("You are not logged in. Please log in to access this page.")}/>
|
return <ErrorAlert className={"m-4"} message={t("You are not logged in. Please log in to access this page.")} />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!app.user?.is_admin) {
|
if (!app.user?.is_admin) {
|
||||||
return <ErrorAlert className={"m-4"} message={t("You are not authorized to access this page.")}/>
|
return <ErrorAlert className={"m-4"} message={t("You are not authorized to access this page.")} />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storages == null) {
|
if (storages == null) {
|
||||||
return <Loading/>
|
return <Loading />
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateStorages = async () => {
|
const updateStorages = async () => {
|
||||||
@@ -77,9 +77,9 @@ export default function StorageView() {
|
|||||||
return <>
|
return <>
|
||||||
<div role="alert" className={`alert alert-info alert-outline ${storages.length !== 0 && "hidden"} mx-4 mb-4`}>
|
<div role="alert" className={`alert alert-info alert-outline ${storages.length !== 0 && "hidden"} mx-4 mb-4`}>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
className="h-6 w-6 shrink-0 stroke-current">
|
className="h-6 w-6 shrink-0 stroke-current">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<span>
|
<span>
|
||||||
{t("No storage found. Please create a new storage.")}
|
{t("No storage found. Please create a new storage.")}
|
||||||
@@ -88,60 +88,60 @@ export default function StorageView() {
|
|||||||
<div className={`rounded-box border border-base-content/10 bg-base-100 mx-4 mb-4 overflow-x-auto ${storages.length === 0 ? "hidden" : ""}`}>
|
<div className={`rounded-box border border-base-content/10 bg-base-100 mx-4 mb-4 overflow-x-auto ${storages.length === 0 ? "hidden" : ""}`}>
|
||||||
<table className={"table"}>
|
<table className={"table"}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{t("Name")}</td>
|
<td>{t("Name")}</td>
|
||||||
<td>{t("Created At")}</td>
|
<td>{t("Created At")}</td>
|
||||||
<td>{t("Space")}</td>
|
<td>{t("Space")}</td>
|
||||||
<td>{t("Action")}</td>
|
<td>{t("Action")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{
|
{
|
||||||
storages.map((s) => {
|
storages.map((s) => {
|
||||||
return <tr key={s.id} className={"hover"}>
|
return <tr key={s.id} className={"hover"}>
|
||||||
<td>
|
<td>
|
||||||
{s.name}
|
{s.name}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{(new Date(s.createdAt)).toLocaleString()}
|
{(new Date(s.createdAt)).toLocaleString()}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{(s.currentSize/1024/1024).toFixed(2)} / {s.maxSize/1024/1024} MB
|
{(s.currentSize / 1024 / 1024).toFixed(2)} / {s.maxSize / 1024 / 1024} MB
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button className={"btn btn-square"} type={"button"} onClick={() => {
|
<button className={"btn btn-square"} type={"button"} onClick={() => {
|
||||||
const dialog = document.getElementById(`confirm_delete_dialog_${s.id}`) as HTMLDialogElement;
|
const dialog = document.getElementById(`confirm_delete_dialog_${s.id}`) as HTMLDialogElement;
|
||||||
dialog.showModal();
|
dialog.showModal();
|
||||||
}}>
|
}}>
|
||||||
{loadingId === s.id ? <span className={"loading loading-spinner loading-sm"}></span> : <MdDelete size={24}/>}
|
{loadingId === s.id ? <span className={"loading loading-spinner loading-sm"}></span> : <MdDelete size={24} />}
|
||||||
</button>
|
</button>
|
||||||
<dialog id={`confirm_delete_dialog_${s.id}`} className="modal">
|
<dialog id={`confirm_delete_dialog_${s.id}`} className="modal">
|
||||||
<div className="modal-box">
|
<div className="modal-box">
|
||||||
<h3 className="text-lg font-bold">{t("Delete Storage")}</h3>
|
<h3 className="text-lg font-bold">{t("Delete Storage")}</h3>
|
||||||
<p className="py-4">
|
<p className="py-4">
|
||||||
{t("Are you sure you want to delete this storage? This action cannot be undone.")}
|
{t("Are you sure you want to delete this storage? This action cannot be undone.")}
|
||||||
</p>
|
</p>
|
||||||
<div className="modal-action">
|
<div className="modal-action">
|
||||||
<form method="dialog">
|
<form method="dialog">
|
||||||
<button className="btn">{t("Cancel")}</button>
|
<button className="btn">{t("Cancel")}</button>
|
||||||
</form>
|
</form>
|
||||||
<button className="btn btn-error" onClick={() => {
|
<button className="btn btn-error" onClick={() => {
|
||||||
handleDelete(s.id);
|
handleDelete(s.id);
|
||||||
}}>
|
}}>
|
||||||
{t("Delete")}
|
{t("Delete")}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</dialog>
|
||||||
</dialog>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
})
|
||||||
})
|
}
|
||||||
}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div className={"flex flex-row-reverse px-4"}>
|
<div className={"flex flex-row-reverse px-4"}>
|
||||||
<NewStorageDialog onAdded={updateStorages}/>
|
<NewStorageDialog onAdded={updateStorages} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,7 @@ enum StorageType {
|
|||||||
s3,
|
s3,
|
||||||
}
|
}
|
||||||
|
|
||||||
function NewStorageDialog({onAdded}: { onAdded: () => void }) {
|
function NewStorageDialog({ onAdded }: { onAdded: () => void }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [storageType, setStorageType] = useState<StorageType | null>(null);
|
const [storageType, setStorageType] = useState<StorageType | null>(null);
|
||||||
|
|
||||||
@@ -163,6 +163,7 @@ function NewStorageDialog({onAdded}: { onAdded: () => void }) {
|
|||||||
secretAccessKey: "",
|
secretAccessKey: "",
|
||||||
bucketName: "",
|
bucketName: "",
|
||||||
maxSizeInMB: 0,
|
maxSizeInMB: 0,
|
||||||
|
domain: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
@@ -189,7 +190,7 @@ function NewStorageDialog({onAdded}: { onAdded: () => void }) {
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
response = await network.createS3Storage(params.name, params.endPoint, params.accessKeyID, params.secretAccessKey, params.bucketName, params.maxSizeInMB);
|
response = await network.createS3Storage(params.name, params.endPoint, params.accessKeyID, params.secretAccessKey, params.bucketName, params.maxSizeInMB, params.domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response!.success) {
|
if (response!.success) {
|
||||||
@@ -206,11 +207,11 @@ function NewStorageDialog({onAdded}: { onAdded: () => void }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<button className="btn" onClick={()=> {
|
<button className="btn" onClick={() => {
|
||||||
const dialog = document.getElementById("new_storage_dialog") as HTMLDialogElement;
|
const dialog = document.getElementById("new_storage_dialog") as HTMLDialogElement;
|
||||||
dialog.showModal();
|
dialog.showModal();
|
||||||
}}>
|
}}>
|
||||||
<MdAdd/>
|
<MdAdd />
|
||||||
{t("New Storage")}
|
{t("New Storage")}
|
||||||
</button>
|
</button>
|
||||||
<dialog id="new_storage_dialog" className="modal">
|
<dialog id="new_storage_dialog" className="modal">
|
||||||
@@ -221,13 +222,13 @@ function NewStorageDialog({onAdded}: { onAdded: () => void }) {
|
|||||||
<form className="filter mb-2">
|
<form className="filter mb-2">
|
||||||
<input className="btn btn-square" type="reset" value="×" onClick={() => {
|
<input className="btn btn-square" type="reset" value="×" onClick={() => {
|
||||||
setStorageType(null);
|
setStorageType(null);
|
||||||
}}/>
|
}} />
|
||||||
<input className="btn" type="radio" name="type" aria-label={t("Local")} onInput={() => {
|
<input className="btn" type="radio" name="type" aria-label={t("Local")} onInput={() => {
|
||||||
setStorageType(StorageType.local);
|
setStorageType(StorageType.local);
|
||||||
}}/>
|
}} />
|
||||||
<input className="btn" type="radio" name="type" aria-label={t("S3")} onInput={() => {
|
<input className="btn" type="radio" name="type" aria-label={t("S3")} onInput={() => {
|
||||||
setStorageType(StorageType.s3);
|
setStorageType(StorageType.s3);
|
||||||
}}/>
|
}} />
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -239,7 +240,7 @@ function NewStorageDialog({onAdded}: { onAdded: () => void }) {
|
|||||||
...params,
|
...params,
|
||||||
name: e.target.value,
|
name: e.target.value,
|
||||||
})
|
})
|
||||||
}}/>
|
}} />
|
||||||
</label>
|
</label>
|
||||||
<label className="input w-full my-2">
|
<label className="input w-full my-2">
|
||||||
{t("Path")}
|
{t("Path")}
|
||||||
@@ -248,7 +249,7 @@ function NewStorageDialog({onAdded}: { onAdded: () => void }) {
|
|||||||
...params,
|
...params,
|
||||||
path: e.target.value,
|
path: e.target.value,
|
||||||
})
|
})
|
||||||
}}/>
|
}} />
|
||||||
</label>
|
</label>
|
||||||
<label className="input w-full my-2">
|
<label className="input w-full my-2">
|
||||||
{t("Max Size (MB)")}
|
{t("Max Size (MB)")}
|
||||||
@@ -278,7 +279,7 @@ function NewStorageDialog({onAdded}: { onAdded: () => void }) {
|
|||||||
...params,
|
...params,
|
||||||
name: e.target.value,
|
name: e.target.value,
|
||||||
})
|
})
|
||||||
}}/>
|
}} />
|
||||||
</label>
|
</label>
|
||||||
<label className="input w-full my-2">
|
<label className="input w-full my-2">
|
||||||
{t("Endpoint")}
|
{t("Endpoint")}
|
||||||
@@ -287,7 +288,7 @@ function NewStorageDialog({onAdded}: { onAdded: () => void }) {
|
|||||||
...params,
|
...params,
|
||||||
endPoint: e.target.value,
|
endPoint: e.target.value,
|
||||||
})
|
})
|
||||||
}}/>
|
}} />
|
||||||
</label>
|
</label>
|
||||||
<label className="input w-full my-2">
|
<label className="input w-full my-2">
|
||||||
{t("Access Key ID")}
|
{t("Access Key ID")}
|
||||||
@@ -296,7 +297,7 @@ function NewStorageDialog({onAdded}: { onAdded: () => void }) {
|
|||||||
...params,
|
...params,
|
||||||
accessKeyID: e.target.value,
|
accessKeyID: e.target.value,
|
||||||
})
|
})
|
||||||
}}/>
|
}} />
|
||||||
</label>
|
</label>
|
||||||
<label className="input w-full my-2">
|
<label className="input w-full my-2">
|
||||||
{t("Secret Access Key")}
|
{t("Secret Access Key")}
|
||||||
@@ -305,7 +306,7 @@ function NewStorageDialog({onAdded}: { onAdded: () => void }) {
|
|||||||
...params,
|
...params,
|
||||||
secretAccessKey: e.target.value,
|
secretAccessKey: e.target.value,
|
||||||
})
|
})
|
||||||
}}/>
|
}} />
|
||||||
</label>
|
</label>
|
||||||
<label className="input w-full my-2">
|
<label className="input w-full my-2">
|
||||||
{t("Bucket Name")}
|
{t("Bucket Name")}
|
||||||
@@ -314,7 +315,16 @@ function NewStorageDialog({onAdded}: { onAdded: () => void }) {
|
|||||||
...params,
|
...params,
|
||||||
bucketName: e.target.value,
|
bucketName: e.target.value,
|
||||||
})
|
})
|
||||||
}}/>
|
}} />
|
||||||
|
</label>
|
||||||
|
<label className="input w-full my-2">
|
||||||
|
{t("Domain")}
|
||||||
|
<input type="text" placeholder={t("Optional")} className="w-full" value={params.domain} onChange={(e) => {
|
||||||
|
setParams({
|
||||||
|
...params,
|
||||||
|
domain: e.target.value,
|
||||||
|
})
|
||||||
|
}} />
|
||||||
</label>
|
</label>
|
||||||
<label className="input w-full my-2">
|
<label className="input w-full my-2">
|
||||||
{t("Max Size (MB)")}
|
{t("Max Size (MB)")}
|
||||||
@@ -335,7 +345,7 @@ function NewStorageDialog({onAdded}: { onAdded: () => void }) {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
{error !== "" && <ErrorAlert message={error} className={"my-2"}/>}
|
{error !== "" && <ErrorAlert message={error} className={"my-2"} />}
|
||||||
|
|
||||||
<div className="modal-action">
|
<div className="modal-action">
|
||||||
<form method="dialog">
|
<form method="dialog">
|
||||||
|
@@ -14,6 +14,7 @@ type CreateS3StorageParams struct {
|
|||||||
EndPoint string `json:"endPoint"`
|
EndPoint string `json:"endPoint"`
|
||||||
AccessKeyID string `json:"accessKeyID"`
|
AccessKeyID string `json:"accessKeyID"`
|
||||||
SecretAccessKey string `json:"secretAccessKey"`
|
SecretAccessKey string `json:"secretAccessKey"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
BucketName string `json:"bucketName"`
|
BucketName string `json:"bucketName"`
|
||||||
MaxSizeInMB uint `json:"maxSizeInMB"`
|
MaxSizeInMB uint `json:"maxSizeInMB"`
|
||||||
}
|
}
|
||||||
@@ -32,6 +33,7 @@ func CreateS3Storage(uid uint, params CreateS3StorageParams) error {
|
|||||||
AccessKeyID: params.AccessKeyID,
|
AccessKeyID: params.AccessKeyID,
|
||||||
SecretAccessKey: params.SecretAccessKey,
|
SecretAccessKey: params.SecretAccessKey,
|
||||||
BucketName: params.BucketName,
|
BucketName: params.BucketName,
|
||||||
|
Domain: params.Domain,
|
||||||
}
|
}
|
||||||
s := model.Storage{
|
s := model.Storage{
|
||||||
Name: params.Name,
|
Name: params.Name,
|
||||||
|
@@ -5,12 +5,13 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3/log"
|
"github.com/gofiber/fiber/v3/log"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type S3Storage struct {
|
type S3Storage struct {
|
||||||
@@ -18,6 +19,7 @@ type S3Storage struct {
|
|||||||
AccessKeyID string
|
AccessKeyID string
|
||||||
SecretAccessKey string
|
SecretAccessKey string
|
||||||
BucketName string
|
BucketName string
|
||||||
|
Domain string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *S3Storage) Upload(filePath string, fileName string) (string, error) {
|
func (s *S3Storage) Upload(filePath string, fileName string) (string, error) {
|
||||||
@@ -43,6 +45,10 @@ func (s *S3Storage) Upload(filePath string, fileName string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *S3Storage) Download(storageKey string, fileName string) (string, error) {
|
func (s *S3Storage) Download(storageKey string, fileName string) (string, error) {
|
||||||
|
if s.Domain != "" {
|
||||||
|
return s.Domain + "/" + storageKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
minioClient, err := minio.New(s.EndPoint, &minio.Options{
|
minioClient, err := minio.New(s.EndPoint, &minio.Options{
|
||||||
Creds: credentials.NewStaticV4(s.AccessKeyID, s.SecretAccessKey, ""),
|
Creds: credentials.NewStaticV4(s.AccessKeyID, s.SecretAccessKey, ""),
|
||||||
Secure: true,
|
Secure: true,
|
||||||
@@ -80,6 +86,7 @@ func (s *S3Storage) FromString(config string) error {
|
|||||||
s.AccessKeyID = s3Config.AccessKeyID
|
s.AccessKeyID = s3Config.AccessKeyID
|
||||||
s.SecretAccessKey = s3Config.SecretAccessKey
|
s.SecretAccessKey = s3Config.SecretAccessKey
|
||||||
s.BucketName = s3Config.BucketName
|
s.BucketName = s3Config.BucketName
|
||||||
|
s.Domain = s3Config.Domain
|
||||||
if s.EndPoint == "" || s.AccessKeyID == "" || s.SecretAccessKey == "" || s.BucketName == "" {
|
if s.EndPoint == "" || s.AccessKeyID == "" || s.SecretAccessKey == "" || s.BucketName == "" {
|
||||||
return errors.New("invalid S3 configuration")
|
return errors.New("invalid S3 configuration")
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user