feat: support refreshing hash
This commit is contained in:
parent
f8bb187c89
commit
ec1bb36c21
11 changed files with 99 additions and 36 deletions
|
@ -731,3 +731,11 @@ div.hr {
|
||||||
color: #e74c3c;
|
color: #e74c3c;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
margin: 0 1rem 1rem 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border-top: dashed 1px #7f8c8d;
|
||||||
|
}
|
||||||
|
|
|
@ -205,3 +205,12 @@ func (cl *FilesClient) ListSharings() (*http.Response, *fileshdr.SharingResp, []
|
||||||
}
|
}
|
||||||
return resp, shResp, nil
|
return resp, shResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cl *FilesClient) GenerateHash(filepath string) (*http.Response, string, []error) {
|
||||||
|
return cl.r.Post(cl.url("/v1/fs/hashes/sha1")).
|
||||||
|
AddCookie(cl.token).
|
||||||
|
Send(fileshdr.GenerateHashReq{
|
||||||
|
FilePath: filepath,
|
||||||
|
}).
|
||||||
|
End()
|
||||||
|
}
|
||||||
|
|
|
@ -198,4 +198,14 @@ export class FilesClient extends BaseClient {
|
||||||
url: `${this.url}/v1/fs/sharings`,
|
url: `${this.url}/v1/fs/sharings`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
generateHash = (filePath: string): Promise<Response> => {
|
||||||
|
return this.do({
|
||||||
|
method: "post",
|
||||||
|
url: `${this.url}/v1/fs/hashes/sha1`,
|
||||||
|
data: {
|
||||||
|
filePath: filePath,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ export interface FilesClientResps {
|
||||||
deleteSharingMockResp?: Response;
|
deleteSharingMockResp?: Response;
|
||||||
listSharingsMockResp?: Response<ListSharingsResp>;
|
listSharingsMockResp?: Response<ListSharingsResp>;
|
||||||
isSharingMockResp?: Response;
|
isSharingMockResp?: Response;
|
||||||
|
generateHashMockResp?: Response;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const resps = {
|
export const resps = {
|
||||||
|
@ -130,7 +131,9 @@ export const resps = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
isSharingMockResp: { status: 200, statusText: "", data: {} },
|
isSharingMockResp: { status: 200, statusText: "", data: {} },
|
||||||
|
generateHashMockResp: { status: 200, statusText: "", data: {} },
|
||||||
};
|
};
|
||||||
|
|
||||||
export class MockFilesClient {
|
export class MockFilesClient {
|
||||||
private url: string;
|
private url: string;
|
||||||
private resps: FilesClientResps;
|
private resps: FilesClientResps;
|
||||||
|
@ -213,4 +216,8 @@ export class MockFilesClient {
|
||||||
isSharing = (dirPath: string): Promise<Response> => {
|
isSharing = (dirPath: string): Promise<Response> => {
|
||||||
return this.wrapPromise(this.resps.isSharingMockResp);
|
return this.wrapPromise(this.resps.isSharingMockResp);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
generateHash = (filePath: string): Promise<Response> => {
|
||||||
|
return this.wrapPromise(this.resps.generateHashMockResp);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,6 +107,7 @@ export interface IFilesClient {
|
||||||
deleteSharing: (dirPath: string) => Promise<Response>;
|
deleteSharing: (dirPath: string) => Promise<Response>;
|
||||||
isSharing: (dirPath: string) => Promise<Response>;
|
isSharing: (dirPath: string) => Promise<Response>;
|
||||||
listSharings: () => Promise<Response<ListSharingsResp>>;
|
listSharings: () => Promise<Response<ListSharingsResp>>;
|
||||||
|
generateHash: (filePath: string) => Promise<Response>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Response<T = any> {
|
export interface Response<T = any> {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import * as React from "react";
|
||||||
import * as ReactDOM from "react-dom";
|
import * as ReactDOM from "react-dom";
|
||||||
import { List, Map, Set } from "immutable";
|
import { List, Map, Set } from "immutable";
|
||||||
import FileSize from "filesize";
|
import FileSize from "filesize";
|
||||||
|
|
||||||
import { RiFolder2Fill } from "@react-icons/all-files/ri/RiFolder2Fill";
|
import { RiFolder2Fill } from "@react-icons/all-files/ri/RiFolder2Fill";
|
||||||
import { RiHomeSmileFill } from "@react-icons/all-files/ri/RiHomeSmileFill";
|
import { RiHomeSmileFill } from "@react-icons/all-files/ri/RiHomeSmileFill";
|
||||||
import { RiFile2Fill } from "@react-icons/all-files/ri/RiFile2Fill";
|
import { RiFile2Fill } from "@react-icons/all-files/ri/RiFile2Fill";
|
||||||
|
@ -318,10 +319,17 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleDetail = (name: string) => {
|
toggleDetail = (name: string) => {
|
||||||
const showDetail = this.state.showDetail.has(name) ? this.state.showDetail.delete(name) : this.state.showDetail.add(name);
|
const showDetail = this.state.showDetail.has(name)
|
||||||
|
? this.state.showDetail.delete(name)
|
||||||
|
: this.state.showDetail.add(name);
|
||||||
this.setState({ showDetail });
|
this.setState({ showDetail });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
generateHash = async (filePath: string): Promise<boolean> => {
|
||||||
|
alertMsg(this.props.msg.pkg.get("refresh-hint"));
|
||||||
|
return updater().generateHash(filePath);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const showOp = this.props.login.userRole === roleVisitor ? "hidden" : "";
|
const showOp = this.props.login.userRole === roleVisitor ? "hidden" : "";
|
||||||
|
|
||||||
|
@ -341,7 +349,8 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const nameCellClass = `item-name item-name-${this.props.browser.isVertical ? "vertical" : "horizontal"
|
const nameCellClass = `item-name item-name-${
|
||||||
|
this.props.browser.isVertical ? "vertical" : "horizontal"
|
||||||
} pointer`;
|
} pointer`;
|
||||||
const sizeCellClass = this.props.browser.isVertical
|
const sizeCellClass = this.props.browser.isVertical
|
||||||
? `hidden margin-s`
|
? `hidden margin-s`
|
||||||
|
@ -434,7 +443,8 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
<span className={`padding-m ${showOp}`}>
|
<span className={`padding-m ${showOp}`}>
|
||||||
<button
|
<button
|
||||||
onClick={() => this.select(item.name)}
|
onClick={() => this.select(item.name)}
|
||||||
className={`${isSelected ? "blue0-bg white-font" : "grey2-bg grey3-font"
|
className={`${
|
||||||
|
isSelected ? "blue0-bg white-font" : "grey2-bg grey3-font"
|
||||||
}`}
|
}`}
|
||||||
style={{ width: "8rem", display: "inline-block" }}
|
style={{ width: "8rem", display: "inline-block" }}
|
||||||
>
|
>
|
||||||
|
@ -442,26 +452,21 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
? this.props.msg.pkg.get("browser.deselect")
|
? this.props.msg.pkg.get("browser.deselect")
|
||||||
: this.props.msg.pkg.get("browser.select")}
|
: this.props.msg.pkg.get("browser.select")}
|
||||||
</button>
|
</button>
|
||||||
{/* <button
|
|
||||||
onClick={() => this.toggleDetail(item.name)}
|
|
||||||
className="grey2-bg grey3-font"
|
|
||||||
style={{ width: "8rem", display: "inline-block" }}
|
|
||||||
>
|
|
||||||
{this.props.msg.pkg.get("detail")}
|
|
||||||
</button> */}
|
|
||||||
</span>,
|
</span>,
|
||||||
])}
|
])}
|
||||||
childrenStyles={List([{}, { justifyContent: "flex-end" }])}
|
childrenStyles={List([{}, { justifyContent: "flex-end" }])}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div key={item.name}>
|
<div key={item.name}>
|
||||||
|
|
||||||
<Flexbox
|
<Flexbox
|
||||||
children={List([
|
children={List([
|
||||||
<span className="padding-m">
|
<span className="padding-m">
|
||||||
<Flexbox
|
<Flexbox
|
||||||
children={List([
|
children={List([
|
||||||
<RiFile2Fill size="3rem" className="cyan0-font margin-r-m" />,
|
<RiFile2Fill
|
||||||
|
size="3rem"
|
||||||
|
className="cyan0-font margin-r-m"
|
||||||
|
/>,
|
||||||
|
|
||||||
<span className={`${nameCellClass}`}>
|
<span className={`${nameCellClass}`}>
|
||||||
<a
|
<a
|
||||||
|
@ -486,7 +491,7 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
<span className={`item-op padding-m ${showOp}`}>
|
<span className={`item-op padding-m ${showOp}`}>
|
||||||
<button
|
<button
|
||||||
onClick={() => this.toggleDetail(item.name)}
|
onClick={() => this.toggleDetail(item.name)}
|
||||||
className="grey2-bg grey3-font"
|
className="grey2-bg grey3-font margin-r-m"
|
||||||
style={{ width: "8rem", display: "inline-block" }}
|
style={{ width: "8rem", display: "inline-block" }}
|
||||||
>
|
>
|
||||||
{this.props.msg.pkg.get("detail")}
|
{this.props.msg.pkg.get("detail")}
|
||||||
|
@ -495,7 +500,8 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => this.select(item.name)}
|
onClick={() => this.select(item.name)}
|
||||||
className={`${isSelected ? "blue0-bg white-font" : "grey2-bg grey3-font"
|
className={`${
|
||||||
|
isSelected ? "blue0-bg white-font" : "grey2-bg grey3-font"
|
||||||
}`}
|
}`}
|
||||||
style={{ width: "8rem", display: "inline-block" }}
|
style={{ width: "8rem", display: "inline-block" }}
|
||||||
>
|
>
|
||||||
|
@ -508,9 +514,27 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
childrenStyles={List([{}, { justifyContent: "flex-end" }])}
|
childrenStyles={List([{}, { justifyContent: "flex-end" }])}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={`grey2-bg grey3-font margin-l ${this.state.showDetail.has(item.name) ? "" : "hidden"}`}
|
className={`${
|
||||||
|
this.state.showDetail.has(item.name) ? "" : "hidden"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<span className="margin-l">{`SHA1: ${item.sha1}`}</span>
|
<Flexbox
|
||||||
|
children={List([
|
||||||
|
<span>
|
||||||
|
<b>SHA1:</b>
|
||||||
|
{` ${item.sha1}`}
|
||||||
|
</span>,
|
||||||
|
<button
|
||||||
|
onClick={() => this.generateHash(itemPath)}
|
||||||
|
className="black-bg white-font margin-l-m"
|
||||||
|
style={{ display: "inline-block" }}
|
||||||
|
>
|
||||||
|
{this.props.msg.pkg.get("refresh")}
|
||||||
|
</button>,
|
||||||
|
])}
|
||||||
|
className={`grey2-bg grey3-font detail margin-r-m`}
|
||||||
|
childrenStyles={List([{}, { justifyContent: "flex-end" }])}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -534,7 +558,7 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="container" style={{ paddingBottom: "1rem" }}>
|
<div className="container" style={{ paddingBottom: "1rem" }}>
|
||||||
<div className={`${showOp}`} >
|
<div className={`${showOp}`}>
|
||||||
<Flexbox
|
<Flexbox
|
||||||
children={List([
|
children={List([
|
||||||
<span>
|
<span>
|
||||||
|
@ -672,9 +696,7 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
/>
|
/>
|
||||||
{uploading.err.trim() === "" ? null : (
|
{uploading.err.trim() === "" ? null : (
|
||||||
<div className="alert-red margin-s">
|
<div className="alert-red margin-s">
|
||||||
<span className="padding-m">
|
<span className="padding-m">{uploading.err.trim()}</span>
|
||||||
{uploading.err.trim()}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -761,7 +783,8 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
type="text"
|
type="text"
|
||||||
readOnly
|
readOnly
|
||||||
className="margin-r-m"
|
className="margin-r-m"
|
||||||
value={`${document.location.href.split("?")[0]
|
value={`${
|
||||||
|
document.location.href.split("?")[0]
|
||||||
}?dir=${encodeURIComponent(dirPath)}`}
|
}?dir=${encodeURIComponent(dirPath)}`}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
|
@ -902,12 +925,8 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>{sharingListPane}</div>
|
||||||
{sharingListPane}
|
<div>{uploadingListPane}</div>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{uploadingListPane}
|
|
||||||
</div>
|
|
||||||
{itemListPane}
|
{itemListPane}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -548,6 +548,11 @@ export class Updater {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
generateHash = async (filePath: string): Promise<boolean> => {
|
||||||
|
const resp = await this.filesClient.generateHash(filePath);
|
||||||
|
return resp.status === 200;
|
||||||
|
};
|
||||||
|
|
||||||
updateBrowser = (prevState: ICoreState): ICoreState => {
|
updateBrowser = (prevState: ICoreState): ICoreState => {
|
||||||
return {
|
return {
|
||||||
...prevState,
|
...prevState,
|
||||||
|
|
|
@ -75,4 +75,6 @@ export const msgs: Map<string, string> = Map({
|
||||||
"upload.404.title": "No uploading is in the progress",
|
"upload.404.title": "No uploading is in the progress",
|
||||||
"upload.404.desc": "You can upload a file in the items tab",
|
"upload.404.desc": "You can upload a file in the items tab",
|
||||||
"detail": "Detail",
|
"detail": "Detail",
|
||||||
|
"refresh": "Refresh",
|
||||||
|
"refresh-hint": "Please refresh later to see the result",
|
||||||
});
|
});
|
||||||
|
|
|
@ -73,4 +73,6 @@ export const msgs: Map<string, string> = Map({
|
||||||
"upload.404.title": "没有正在上传的文件",
|
"upload.404.title": "没有正在上传的文件",
|
||||||
"upload.404.desc": "在列表面板可以上传文件",
|
"upload.404.desc": "在列表面板可以上传文件",
|
||||||
"detail": "详细",
|
"detail": "详细",
|
||||||
|
"refresh": "刷新",
|
||||||
|
"refresh-hint": "请稍后刷新查看结果",
|
||||||
});
|
});
|
||||||
|
|
|
@ -903,12 +903,12 @@ func (h *FileHandlers) ListSharings(c *gin.Context) {
|
||||||
c.JSON(200, &SharingResp{SharingDirs: dirs})
|
c.JSON(200, &SharingResp{SharingDirs: dirs})
|
||||||
}
|
}
|
||||||
|
|
||||||
type HashBody struct {
|
type GenerateHashReq struct {
|
||||||
FilePath string `json:"filePath"`
|
FilePath string `json:"filePath"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *FileHandlers) GenerateHash(c *gin.Context) {
|
func (h *FileHandlers) GenerateHash(c *gin.Context) {
|
||||||
req := &HashBody{}
|
req := &GenerateHashReq{}
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(q.ErrResp(c, 400, err))
|
c.JSON(q.ErrResp(c, 400, err))
|
||||||
return
|
return
|
||||||
|
|
|
@ -69,7 +69,7 @@ func NewMultiUsersSvc(cfg gocfg.ICfg, deps *depidx.Deps) (*MultiUsersSvc, error)
|
||||||
apiRuleCname(userstore.AdminRole, "DELETE", "/v1/fs/sharings"): true,
|
apiRuleCname(userstore.AdminRole, "DELETE", "/v1/fs/sharings"): true,
|
||||||
apiRuleCname(userstore.AdminRole, "GET", "/v1/fs/sharings"): true,
|
apiRuleCname(userstore.AdminRole, "GET", "/v1/fs/sharings"): true,
|
||||||
apiRuleCname(userstore.AdminRole, "GET", "/v1/fs/sharings/exist"): true,
|
apiRuleCname(userstore.AdminRole, "GET", "/v1/fs/sharings/exist"): true,
|
||||||
apiRuleCname(userstore.AdminRole, "GET", "/hashes/sha1"): true,
|
apiRuleCname(userstore.AdminRole, "POST", "/v1/fs/hashes/sha1"): true,
|
||||||
|
|
||||||
// user rules
|
// user rules
|
||||||
apiRuleCname(userstore.UserRole, "GET", "/"): true,
|
apiRuleCname(userstore.UserRole, "GET", "/"): true,
|
||||||
|
@ -99,7 +99,7 @@ func NewMultiUsersSvc(cfg gocfg.ICfg, deps *depidx.Deps) (*MultiUsersSvc, error)
|
||||||
apiRuleCname(userstore.UserRole, "DELETE", "/v1/fs/sharings"): true,
|
apiRuleCname(userstore.UserRole, "DELETE", "/v1/fs/sharings"): true,
|
||||||
apiRuleCname(userstore.UserRole, "GET", "/v1/fs/sharings"): true,
|
apiRuleCname(userstore.UserRole, "GET", "/v1/fs/sharings"): true,
|
||||||
apiRuleCname(userstore.UserRole, "GET", "/v1/fs/sharings/exist"): true,
|
apiRuleCname(userstore.UserRole, "GET", "/v1/fs/sharings/exist"): true,
|
||||||
apiRuleCname(userstore.AdminRole, "GET", "/hashes/sha1"): true,
|
apiRuleCname(userstore.AdminRole, "POST", "/v1/fs/hashes/sha1"): true,
|
||||||
// visitor rules
|
// visitor rules
|
||||||
apiRuleCname(userstore.VisitorRole, "GET", "/"): true,
|
apiRuleCname(userstore.VisitorRole, "GET", "/"): true,
|
||||||
apiRuleCname(userstore.VisitorRole, "GET", publicPath): true,
|
apiRuleCname(userstore.VisitorRole, "GET", publicPath): true,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue