Add resource list page.

This commit is contained in:
Yang Luo
2021-08-15 00:17:53 +08:00
parent f3c10c59cb
commit 495b64995f
16 changed files with 631 additions and 89 deletions

View File

@ -15,10 +15,8 @@
package controllers package controllers
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"strconv" "strconv"
"github.com/casbin/casdoor/object" "github.com/casbin/casdoor/object"
@ -212,67 +210,6 @@ func (c *ApiController) GetAccount() {
c.ResponseOk(user, organization) c.ResponseOk(user, organization)
} }
// UploadFile
// @Title UploadFile
// @Description upload file
// @Param owner query string true "The owner"
// @Param tag query string true "The tag"
// @Param fullFilePath query string true "The full file path"
// @Param file query string true "The file"
// @Success 200 {object} controllers.Response The Response object
// @router /upload-file [post]
func (c *ApiController) UploadFile() {
userId, ok := c.RequireSignedIn()
if !ok {
return
}
//owner := c.Input().Get("owner")
tag := c.Input().Get("tag")
parent := c.Input().Get("parent")
fullFilePath := c.Input().Get("fullFilePath")
file, _, err := c.GetFile("file")
defer file.Close()
if err != nil {
c.ResponseError(err.Error())
return
}
fileBuffer := bytes.NewBuffer(nil)
if _, err = io.Copy(fileBuffer, file); err != nil {
c.ResponseError(err.Error())
return
}
user := object.GetUser(userId)
application := object.GetApplicationByUser(user)
provider := application.GetStorageProvider()
if provider == nil {
c.ResponseError("No storage provider is found")
return
}
fileUrl, err := object.UploadFile(provider, fullFilePath, fileBuffer)
if err != nil {
c.ResponseError(err.Error())
return
}
switch tag {
case "avatar":
user.Avatar = fileUrl
object.UpdateUser(user.GetId(), user)
case "termsOfUse":
applicationId := fmt.Sprintf("admin/%s", parent)
app := object.GetApplication(applicationId)
app.TermsOfUse = fileUrl
object.UpdateApplication(applicationId, app)
}
c.ResponseOk(fileUrl)
}
// GetHumanCheck ... // GetHumanCheck ...
func (c *ApiController) GetHumanCheck() { func (c *ApiController) GetHumanCheck() {
c.Data["json"] = HumanCheck{Type: "none"} c.Data["json"] = HumanCheck{Type: "none"}

152
controllers/resource.go Normal file
View File

@ -0,0 +1,152 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"bytes"
"encoding/json"
"fmt"
"io"
"path/filepath"
"strings"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
)
func (c *ApiController) GetResources() {
owner := c.Input().Get("owner")
c.Data["json"] = object.GetResources(owner)
c.ServeJSON()
}
func (c *ApiController) GetResource() {
id := c.Input().Get("id")
c.Data["json"] = object.GetResource(id)
c.ServeJSON()
}
func (c *ApiController) UpdateResource() {
id := c.Input().Get("id")
var resource object.Resource
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.UpdateResource(id, &resource))
c.ServeJSON()
}
func (c *ApiController) AddResource() {
var resource object.Resource
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.AddResource(&resource))
c.ServeJSON()
}
func (c *ApiController) DeleteResource() {
var resource object.Resource
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.DeleteResource(&resource))
c.ServeJSON()
}
func (c *ApiController) UploadResource() {
userId, ok := c.RequireSignedIn()
if !ok {
return
}
owner := c.Input().Get("owner")
tag := c.Input().Get("tag")
parent := c.Input().Get("parent")
fullFilePath := c.Input().Get("fullFilePath")
file, header, err := c.GetFile("file")
defer file.Close()
if err != nil {
c.ResponseError(err.Error())
return
}
filename := filepath.Base(fullFilePath)
fileBuffer := bytes.NewBuffer(nil)
if _, err = io.Copy(fileBuffer, file); err != nil {
c.ResponseError(err.Error())
return
}
user := object.GetUser(userId)
application := object.GetApplicationByUser(user)
provider := application.GetStorageProvider()
if provider == nil {
c.ResponseError("No storage provider is found")
return
}
fileUrl, objectKey, err := object.UploadFile(provider, fullFilePath, fileBuffer)
if err != nil {
c.ResponseError(err.Error())
return
}
fileType := "unknown"
fileFormat := filepath.Ext(fullFilePath)
if strings.Contains(".png|.jpg|.bmp", fileFormat) {
fileType = "image"
} else if strings.Contains(".mp4|.avi", fileFormat) {
fileType = "video"
}
fileSize := int(header.Size)
resource := &object.Resource{
Owner: owner,
Name: filename,
CreatedTime: util.GetCurrentTime(),
Tag: tag,
Parent: parent,
FileType: fileType,
FileFormat: fileFormat,
FileSize: fileSize,
Url: fileUrl,
ObjectKey: objectKey,
}
object.AddOrUpdateResource(resource)
switch tag {
case "avatar":
user.Avatar = fileUrl
object.UpdateUser(user.GetId(), user)
case "termsOfUse":
applicationId := fmt.Sprintf("admin/%s", parent)
app := object.GetApplication(applicationId)
app.TermsOfUse = fileUrl
object.UpdateApplication(applicationId, app)
}
c.ResponseOk(fileUrl)
}

View File

@ -124,6 +124,11 @@ func (a *Adapter) createTable() {
panic(err) panic(err)
} }
err = a.Engine.Sync2(new(Resource))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Token)) err = a.Engine.Sync2(new(Token))
if err != nil { if err != nil {
panic(err) panic(err)

110
object/resource.go Normal file
View File

@ -0,0 +1,110 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"fmt"
"xorm.io/core"
"github.com/casbin/casdoor/util"
)
type Resource struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
Tag string `xorm:"varchar(100)" json:"tag"`
Parent string `xorm:"varchar(100)" json:"parent"`
FileType string `xorm:"varchar(100)" json:"fileType"`
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
FileSize int `json:"fileSize"`
Url string `xorm:"varchar(100)" json:"url"`
ObjectKey string `xorm:"varchar(100)" json:"objectKey"`
}
func GetResources(owner string) []*Resource {
resources := []*Resource{}
err := adapter.Engine.Desc("created_time").Find(&resources, &Resource{Owner: owner})
if err != nil {
panic(err)
}
return resources
}
func getResource(owner string, name string) *Resource {
resource := Resource{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&resource)
if err != nil {
panic(err)
}
if existed {
return &resource
} else {
return nil
}
}
func GetResource(id string) *Resource {
owner, name := util.GetOwnerAndNameFromId(id)
return getResource(owner, name)
}
func UpdateResource(id string, resource *Resource) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getResource(owner, name) == nil {
return false
}
_, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(resource)
if err != nil {
panic(err)
}
//return affected != 0
return true
}
func AddResource(resource *Resource) bool {
affected, err := adapter.Engine.Insert(resource)
if err != nil {
panic(err)
}
return affected != 0
}
func DeleteResource(resource *Resource) bool {
affected, err := adapter.Engine.ID(core.PK{resource.Owner, resource.Name}).Delete(&Resource{})
if err != nil {
panic(err)
}
return affected != 0
}
func (resource *Resource) GetId() string {
return fmt.Sprintf("%s/%s", resource.Owner, resource.Name)
}
func AddOrUpdateResource(resource *Resource) bool {
if getResource(resource.Owner, resource.Name) == nil {
return AddResource(resource)
} else {
return UpdateResource(resource.GetId(), resource)
}
}

View File

@ -23,10 +23,10 @@ import (
"github.com/casbin/casdoor/util" "github.com/casbin/casdoor/util"
) )
func UploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffer) (string, error) { func UploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffer) (string, string, error) {
storageProvider := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, provider.Endpoint) storageProvider := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, provider.Endpoint)
if storageProvider == nil { if storageProvider == nil {
return "", fmt.Errorf("the provider type: %s is not supported", provider.Type) return "", "", fmt.Errorf("the provider type: %s is not supported", provider.Type)
} }
if provider.Domain == "" { if provider.Domain == "" {
@ -34,10 +34,10 @@ func UploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffe
UpdateProvider(provider.GetId(), provider) UpdateProvider(provider.GetId(), provider)
} }
path := util.UrlJoin(util.GetUrlPath(provider.Domain), fullFilePath) objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), fullFilePath)
_, err := storageProvider.Put(path, fileBuffer) _, err := storageProvider.Put(objectKey, fileBuffer)
if err != nil { if err != nil {
return "", err return "", "", err
} }
host := "" host := ""
@ -52,6 +52,6 @@ func UploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffe
host = util.UrlJoin(provider.Domain, "/files") host = util.UrlJoin(provider.Domain, "/files")
} }
fileUrl := fmt.Sprintf("%s?time=%s", util.UrlJoin(host, path), util.GetCurrentUnixTime()) fileUrl := fmt.Sprintf("%s?time=%s", util.UrlJoin(host, objectKey), util.GetCurrentUnixTime())
return fileUrl, nil return fileUrl, objectKey, nil
} }

View File

@ -59,12 +59,13 @@ func initAPI() {
beego.Router("/api/update-user", &controllers.ApiController{}, "POST:UpdateUser") beego.Router("/api/update-user", &controllers.ApiController{}, "POST:UpdateUser")
beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser") beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser")
beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser") beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser")
beego.Router("/api/upload-file", &controllers.ApiController{}, "POST:UploadFile")
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword") beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone") beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode") beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone") beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
beego.Router("/api/get-human-check", &controllers.ApiController{}, "GET:GetHumanCheck") beego.Router("/api/get-human-check", &controllers.ApiController{}, "GET:GetHumanCheck")
beego.Router("/api/get-ldap-user", &controllers.ApiController{}, "POST:GetLdapUser") beego.Router("/api/get-ldap-user", &controllers.ApiController{}, "POST:GetLdapUser")
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "POST:GetLdaps") beego.Router("/api/get-ldaps", &controllers.ApiController{}, "POST:GetLdaps")
beego.Router("/api/get-ldap", &controllers.ApiController{}, "POST:GetLdap") beego.Router("/api/get-ldap", &controllers.ApiController{}, "POST:GetLdap")
@ -87,6 +88,13 @@ func initAPI() {
beego.Router("/api/add-application", &controllers.ApiController{}, "POST:AddApplication") beego.Router("/api/add-application", &controllers.ApiController{}, "POST:AddApplication")
beego.Router("/api/delete-application", &controllers.ApiController{}, "POST:DeleteApplication") beego.Router("/api/delete-application", &controllers.ApiController{}, "POST:DeleteApplication")
beego.Router("/api/get-resources", &controllers.ApiController{}, "GET:GetResources")
beego.Router("/api/get-resource", &controllers.ApiController{}, "GET:GetResource")
beego.Router("/api/update-resource", &controllers.ApiController{}, "POST:UpdateResource")
beego.Router("/api/add-resource", &controllers.ApiController{}, "POST:AddResource")
beego.Router("/api/delete-resource", &controllers.ApiController{}, "POST:DeleteResource")
beego.Router("/api/upload-resource", &controllers.ApiController{}, "POST:UploadResource")
beego.Router("/api/get-tokens", &controllers.ApiController{}, "GET:GetTokens") beego.Router("/api/get-tokens", &controllers.ApiController{}, "GET:GetTokens")
beego.Router("/api/get-token", &controllers.ApiController{}, "GET:GetToken") beego.Router("/api/get-token", &controllers.ApiController{}, "GET:GetToken")
beego.Router("/api/update-token", &controllers.ApiController{}, "POST:UpdateToken") beego.Router("/api/update-token", &controllers.ApiController{}, "POST:UpdateToken")

View File

@ -27,6 +27,8 @@ import ProviderListPage from "./ProviderListPage";
import ProviderEditPage from "./ProviderEditPage"; import ProviderEditPage from "./ProviderEditPage";
import ApplicationListPage from "./ApplicationListPage"; import ApplicationListPage from "./ApplicationListPage";
import ApplicationEditPage from "./ApplicationEditPage"; import ApplicationEditPage from "./ApplicationEditPage";
import ResourceListPage from "./ResourceListPage";
// import ResourceEditPage from "./ResourceEditPage";
import LdapEditPage from "./LdapEditPage"; import LdapEditPage from "./LdapEditPage";
import LdapSyncPage from "./LdapSyncPage"; import LdapSyncPage from "./LdapSyncPage";
import TokenListPage from "./TokenListPage"; import TokenListPage from "./TokenListPage";
@ -99,6 +101,8 @@ class App extends Component {
this.setState({ selectedMenuKey: '/providers' }); this.setState({ selectedMenuKey: '/providers' });
} else if (uri.includes('/applications')) { } else if (uri.includes('/applications')) {
this.setState({ selectedMenuKey: '/applications' }); this.setState({ selectedMenuKey: '/applications' });
} else if (uri.includes('/resources')) {
this.setState({ selectedMenuKey: '/resources' });
} else if (uri.includes('/tokens')) { } else if (uri.includes('/tokens')) {
this.setState({ selectedMenuKey: '/tokens' }); this.setState({ selectedMenuKey: '/tokens' });
} else if (uri.includes('/records')) { } else if (uri.includes('/records')) {
@ -317,6 +321,13 @@ class App extends Component {
</Link> </Link>
</Menu.Item> </Menu.Item>
); );
res.push(
<Menu.Item key="/resources">
<Link to="/resources">
{i18next.t("general:Resources")}
</Link>
</Menu.Item>
);
res.push( res.push(
<Menu.Item key="/tokens"> <Menu.Item key="/tokens">
<Link to="/tokens"> <Link to="/tokens">
@ -384,6 +395,8 @@ class App extends Component {
<Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)}/> <Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)}/> <Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)}/>
<Route exact path="/applications/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)}/> <Route exact path="/applications/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)}/>
{/*<Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/}
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)}/> <Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)}/> <Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)}/>
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)}/> <Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)}/>

View File

@ -19,7 +19,7 @@ import * as ApplicationBackend from "./backend/ApplicationBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as ProviderBackend from "./backend/ProviderBackend"; import * as ProviderBackend from "./backend/ProviderBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as UserBackend from "./backend/UserBackend"; import * as ResourceBackend from "./backend/ResourceBackend";
import SignupPage from "./auth/SignupPage"; import SignupPage from "./auth/SignupPage";
import LoginPage from "./auth/LoginPage"; import LoginPage from "./auth/LoginPage";
import i18next from "i18next"; import i18next from "i18next";
@ -99,7 +99,7 @@ class ApplicationEditPage extends React.Component {
} }
this.setState({uploading: true}); this.setState({uploading: true});
const fullFilePath = `termsOfUse/${this.state.application.owner}/${this.state.application.name}.html`; const fullFilePath = `termsOfUse/${this.state.application.owner}/${this.state.application.name}.html`;
UserBackend.uploadFile(this.state.application.owner, "termsOfUse", this.state.application.name, fullFilePath, info.file) ResourceBackend.uploadResource(this.state.application.owner, "termsOfUse", this.state.application.name, fullFilePath, info.file)
.then(res => { .then(res => {
if (res.status === "ok") { if (res.status === "ok") {
Setting.showMessage("success", i18next.t("application:File uploaded successfully")); Setting.showMessage("success", i18next.t("application:File uploaded successfully"));

View File

@ -18,7 +18,7 @@ import "cropperjs/dist/cropper.css";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import {Button, Row, Col, Modal} from 'antd'; import {Button, Row, Col, Modal} from 'antd';
import i18next from "i18next"; import i18next from "i18next";
import * as UserBackend from "./backend/UserBackend"; import * as ResourceBackend from "./backend/ResourceBackend";
export const CropperDiv = (props) => { export const CropperDiv = (props) => {
const [image, setImage] = useState(""); const [image, setImage] = useState("");
@ -27,6 +27,7 @@ export const CropperDiv = (props) => {
const [confirmLoading, setConfirmLoading] = React.useState(false); const [confirmLoading, setConfirmLoading] = React.useState(false);
const {title} = props; const {title} = props;
const {user} = props; const {user} = props;
const {account} = props;
const {buttonText} = props; const {buttonText} = props;
let uploadButton; let uploadButton;
@ -57,7 +58,7 @@ export const CropperDiv = (props) => {
// Setting.showMessage("success", "uploading..."); // Setting.showMessage("success", "uploading...");
const extension = image.substring(image.indexOf('/') + 1, image.indexOf(';base64')); const extension = image.substring(image.indexOf('/') + 1, image.indexOf(';base64'));
const fullFilePath = `avatar/${user.owner}/${user.name}.${extension}`; const fullFilePath = `avatar/${user.owner}/${user.name}.${extension}`;
UserBackend.uploadFile("admin", "avatar", "", fullFilePath, blob) ResourceBackend.uploadResource("admin", "avatar", account.name, fullFilePath, blob)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
window.location.href = "/account"; window.location.href = "/account";

250
web/src/ResourceListPage.js Normal file
View File

@ -0,0 +1,250 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {Button, Popconfirm, Table, Upload} from 'antd';
import {UploadOutlined} from "@ant-design/icons";
import copy from 'copy-to-clipboard';
import * as Setting from "./Setting";
import * as ResourceBackend from "./backend/ResourceBackend";
import i18next from "i18next";
import {Link} from "react-router-dom";
class ResourceListPage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
resources: null,
fileList: [],
uploading: false,
};
}
UNSAFE_componentWillMount() {
this.getResources();
}
getResources() {
ResourceBackend.getResources("admin")
.then((res) => {
this.setState({
resources: res,
});
});
}
deleteResource(i) {
ResourceBackend.deleteResource(this.state.resources[i])
.then((res) => {
Setting.showMessage("success", `Resource deleted successfully`);
this.setState({
resources: Setting.deleteRow(this.state.resources, i),
});
}
)
.catch(error => {
Setting.showMessage("error", `Resource failed to delete: ${error}`);
});
}
handleUpload(info) {
this.setState({uploading: true});
const filename = info.fileList[0].name;
const fullFilePath = `resource/${this.props.account.owner}/${this.props.account.name}/${filename}`;
ResourceBackend.uploadResource("admin", "custom", this.props.account.name, fullFilePath, info.file)
.then(res => {
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("application:File uploaded successfully"));
window.location.reload();
} else {
Setting.showMessage("error", res.msg);
}
}).finally(() => {
this.setState({uploading: false});
})
}
renderUpload() {
return (
<Upload maxCount={1} accept="image/*,video/*" showUploadList={false}
beforeUpload={file => {return false}} onChange={info => {this.handleUpload(info)}}>
<Button icon={<UploadOutlined />} loading={this.state.uploading} type="primary" size="small">
{i18next.t("resource:Upload a file...")}
</Button>
</Upload>
)
}
renderTable(resources) {
const columns = [
{
title: i18next.t("general:Name"),
dataIndex: 'name',
key: 'name',
width: '150px',
fixed: 'left',
sorter: (a, b) => a.name.localeCompare(b.name),
// render: (text, record, index) => {
// return (
// <Link to={`/resources/${text}`}>
// {text}
// </Link>
// )
// }
},
{
title: i18next.t("general:Created time"),
dataIndex: 'createdTime',
key: 'createdTime',
width: '160px',
sorter: (a, b) => a.createdTime.localeCompare(b.createdTime),
render: (text, record, index) => {
return Setting.getFormattedDate(text);
}
},
{
title: i18next.t("resource:Tag"),
dataIndex: 'tag',
key: 'tag',
width: '80px',
sorter: (a, b) => a.tag.localeCompare(b.tag),
},
{
title: i18next.t("resource:Parent"),
dataIndex: 'parent',
key: 'parent',
width: '80px',
sorter: (a, b) => a.parent.localeCompare(b.parent),
},
{
title: i18next.t("resource:File type"),
dataIndex: 'fileType',
key: 'fileType',
width: '120px',
sorter: (a, b) => a.fileType.localeCompare(b.fileType),
},
{
title: i18next.t("resource:File format"),
dataIndex: 'fileFormat',
key: 'fileFormat',
width: '130px',
sorter: (a, b) => a.fileFormat.localeCompare(b.fileFormat),
},
{
title: i18next.t("resource:File size"),
dataIndex: 'fileSize',
key: 'fileSize',
width: '120px',
sorter: (a, b) => a.fileSize - b.fileSize,
render: (text, record, index) => {
return Setting.getFriendlyFileSize(text);
}
},
{
title: i18next.t("general:Preview"),
dataIndex: 'preview',
key: 'preview',
width: '100px',
render: (text, record, index) => {
if (record.fileType === "image") {
return (
<a target="_blank" href={record.url}>
<img src={record.url} alt={record.name} width={100} />
</a>
)
} else if (record.fileType === "video") {
return (
<div>
<video width={100} controls>
<source src={text} type="video/mp4" />
</video>
</div>
)
}
}
},
{
title: i18next.t("general:URL"),
dataIndex: 'url',
key: 'url',
width: '120px',
render: (text, record, index) => {
return (
<div>
<Button type="normal" onClick={() => {
copy(record.url);
Setting.showMessage("success", i18next.t("resource:Link copied to clipboard successfully"));
}}
>
{i18next.t("resource:Copy Link")}
</Button>
</div>
)
}
},
{
title: i18next.t("general:Action"),
dataIndex: '',
key: 'op',
width: '70px',
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
return (
<div>
{/*<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/resources/${record.name}`)}>{i18next.t("general:Edit")}</Button>*/}
<Popconfirm
title={`Sure to delete resource: ${record.name} ?`}
onConfirm={() => this.deleteResource(index)}
okText={i18next.t("user:OK")}
cancelText={i18next.t("user:Cancel")}
>
<Button type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
)
}
},
];
return (
<div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={resources} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
title={() => (
<div>
{i18next.t("general:Resources")}&nbsp;&nbsp;&nbsp;&nbsp;
{/*<Button type="primary" size="small" onClick={this.addResource.bind(this)}>{i18next.t("general:Add")}</Button>*/}
{
this.renderUpload()
}
</div>
)}
loading={resources === null}
/>
</div>
);
}
render() {
return (
<div>
{
this.renderTable(this.state.resources)
}
</div>
);
}
}
export default ResourceListPage;

View File

@ -274,6 +274,18 @@ export function getShortText(s, maxLength=35) {
} }
} }
export function getFriendlyFileSize(size) {
if (size < 1024) {
return size + ' B';
}
let i = Math.floor(Math.log(size) / Math.log(1024));
let num = (size / Math.pow(1024, i));
let round = Math.round(num);
num = round < 10 ? num.toFixed(2) : round < 100 ? num.toFixed(1) : round;
return `${num} ${'KMGTPEZY'[i-1]}B`;
}
function getRandomInt(s) { function getRandomInt(s) {
let hash = 0; let hash = 0;
if (s.length !== 0) { if (s.length !== 0) {

View File

@ -179,7 +179,7 @@ class UserEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}}> <Row style={{marginTop: '20px'}}>
<CropperDiv buttonText={`${i18next.t("user:Upload a photo")}...`} title={i18next.t("user:Upload a photo")} user={this.state.user} /> <CropperDiv buttonText={`${i18next.t("user:Upload a photo")}...`} title={i18next.t("user:Upload a photo")} user={this.state.user} account={this.props.account} />
</Row> </Row>
</Col> </Col>
</Row> </Row>

View File

@ -437,7 +437,7 @@ class SignupPage extends React.Component {
this.props.history.goBack(); this.props.history.goBack();
}} }}
> >
<iframe style={{border: 0, width: "100%", height: "60vh"}} srcDoc={this.state.termsOfUseContent}/> <iframe title={"terms"} style={{border: 0, width: "100%", height: "60vh"}} srcDoc={this.state.termsOfUseContent}/>
</Modal> </Modal>
) )
} }

View File

@ -13,7 +13,6 @@
// limitations under the License. // limitations under the License.
import * as Setting from "../Setting"; import * as Setting from "../Setting";
import * as AuthBackend from "../auth/AuthBackend";
export function getApplications(owner) { export function getApplications(owner) {
return fetch(`${Setting.ServerUrl}/api/get-applications?owner=${owner}`, { return fetch(`${Setting.ServerUrl}/api/get-applications?owner=${owner}`, {

View File

@ -0,0 +1,66 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as Setting from "../Setting";
export function getResources(owner) {
return fetch(`${Setting.ServerUrl}/api/get-resources?owner=${owner}`, {
method: "GET",
credentials: "include"
}).then(res => res.json());
}
export function getResource(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-resource?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET",
credentials: "include"
}).then(res => res.json());
}
export function updateResource(owner, name, resource) {
let newResource = Setting.deepCopy(resource);
return fetch(`${Setting.ServerUrl}/api/update-resource?id=${owner}/${encodeURIComponent(name)}`, {
method: 'POST',
credentials: 'include',
body: JSON.stringify(newResource),
}).then(res => res.json());
}
export function addResource(resource) {
let newResource = Setting.deepCopy(resource);
return fetch(`${Setting.ServerUrl}/api/add-resource`, {
method: 'POST',
credentials: 'include',
body: JSON.stringify(newResource),
}).then(res => res.json());
}
export function deleteResource(resource) {
let newResource = Setting.deepCopy(resource);
return fetch(`${Setting.ServerUrl}/api/delete-resource`, {
method: 'POST',
credentials: 'include',
body: JSON.stringify(newResource),
}).then(res => res.json());
}
export function uploadResource(owner, tag, parent, fullFilePath, file) {
let formData = new FormData();
formData.append("file", file);
return fetch(`${Setting.ServerUrl}/api/upload-resource?owner=${owner}&tag=${tag}&parent=${parent}&fullFilePath=${encodeURIComponent(fullFilePath)}`, {
body: formData,
method: 'POST',
credentials: 'include',
}).then(res => res.json())
}

View File

@ -13,7 +13,6 @@
// limitations under the License. // limitations under the License.
import * as Setting from "../Setting"; import * as Setting from "../Setting";
import * as AuthBackend from "../auth/AuthBackend";
import i18next from "i18next"; import i18next from "i18next";
export function getGlobalUsers() { export function getGlobalUsers() {
@ -76,16 +75,6 @@ export function getAffiliationOptions(url, code) {
}).then(res => res.json()); }).then(res => res.json());
} }
export function uploadFile(owner, tag, parent, fullFilePath, file) {
let formData = new FormData();
formData.append("file", file);
return fetch(`${Setting.ServerUrl}/api/upload-file?owner=${owner}&tag=${tag}&parent=${parent}&fullFilePath=${encodeURIComponent(fullFilePath)}`, {
body: formData,
method: 'POST',
credentials: 'include',
}).then(res => res.json())
}
export function setPassword(userOwner, userName, oldPassword, newPassword) { export function setPassword(userOwner, userName, oldPassword, newPassword) {
let formData = new FormData(); let formData = new FormData();
formData.append("userOwner", userOwner); formData.append("userOwner", userOwner);