mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-23 14:33:28 +08:00
Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
6464bd10dc | |||
db878a890e | |||
12d6d8e6ce | |||
8ed6e4f934 | |||
ed9732caf9 | |||
0de4e7da38 | |||
a330fbc11f | |||
ed158d4981 |
@ -37,7 +37,7 @@ COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/conf/app.conf ./conf/app.co
|
||||
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/version_info.txt ./go/src/casdoor/version_info.txt
|
||||
COPY --from=FRONT --chown=$USER:$USER /web/build ./web/build
|
||||
|
||||
ENTRYPOINT ["/server"]
|
||||
ENTRYPOINT ["sudo","/server"]
|
||||
|
||||
|
||||
FROM debian:latest AS db
|
||||
|
@ -13,7 +13,7 @@
|
||||
<a href="https://github.com/casdoor/casdoor/releases/latest">
|
||||
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casdoor/casdoor.svg">
|
||||
</a>
|
||||
<a href="https://hub.docker.com/repository/docker/casbin/casdoor">
|
||||
<a href="https://hub.docker.com/r/casbin/casdoor">
|
||||
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
|
||||
</a>
|
||||
</p>
|
||||
|
@ -65,7 +65,7 @@ func (c *ApiController) GetOrganizations() {
|
||||
c.ResponseOk(organizations)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetOrganizationCount(owner, field, value)
|
||||
count, err := object.GetOrganizationCount(owner, organizationName, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -138,7 +138,7 @@ func (c *ApiController) AddOrganization() {
|
||||
return
|
||||
}
|
||||
|
||||
count, err := object.GetOrganizationCount("", "", "")
|
||||
count, err := object.GetOrganizationCount("", "", "", "")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
1
main.go
1
main.go
@ -71,6 +71,7 @@ func main() {
|
||||
beego.BConfig.WebConfig.Session.SessionProviderConfig = conf.GetConfigString("redisEndpoint")
|
||||
}
|
||||
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
|
||||
beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600 * 24 * 30
|
||||
// beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
|
||||
|
||||
err := logs.SetLogger(logs.AdapterFile, conf.GetConfigString("logConfig"))
|
||||
|
@ -31,15 +31,17 @@ type SigninMethod struct {
|
||||
}
|
||||
|
||||
type SignupItem struct {
|
||||
Name string `json:"name"`
|
||||
Visible bool `json:"visible"`
|
||||
Required bool `json:"required"`
|
||||
Prompted bool `json:"prompted"`
|
||||
CustomCss string `json:"customCss"`
|
||||
Label string `json:"label"`
|
||||
Placeholder string `json:"placeholder"`
|
||||
Regex string `json:"regex"`
|
||||
Rule string `json:"rule"`
|
||||
Name string `json:"name"`
|
||||
Visible bool `json:"visible"`
|
||||
Required bool `json:"required"`
|
||||
Prompted bool `json:"prompted"`
|
||||
Type string `json:"type"`
|
||||
CustomCss string `json:"customCss"`
|
||||
Label string `json:"label"`
|
||||
Placeholder string `json:"placeholder"`
|
||||
Options []string `json:"options"`
|
||||
Regex string `json:"regex"`
|
||||
Rule string `json:"rule"`
|
||||
}
|
||||
|
||||
type SigninItem struct {
|
||||
|
@ -79,9 +79,9 @@ type Organization struct {
|
||||
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
|
||||
}
|
||||
|
||||
func GetOrganizationCount(owner, field, value string) (int64, error) {
|
||||
func GetOrganizationCount(owner, name, field, value string) (int64, error) {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
return session.Count(&Organization{})
|
||||
return session.Count(&Organization{Name: name})
|
||||
}
|
||||
|
||||
func GetOrganizations(owner string, name ...string) ([]*Organization, error) {
|
||||
|
@ -43,6 +43,10 @@ func getWebBuildFolder() string {
|
||||
return path
|
||||
}
|
||||
|
||||
if util.FileExist(filepath.Join(frontendBaseDir, "index.html")) {
|
||||
return frontendBaseDir
|
||||
}
|
||||
|
||||
path = filepath.Join(frontendBaseDir, "web/build")
|
||||
return path
|
||||
}
|
||||
|
97
web/src/CasbinEditor.js
Normal file
97
web/src/CasbinEditor.js
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright 2024 The Casdoor 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, {useCallback, useEffect, useRef, useState} from "react";
|
||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import "codemirror/mode/properties/properties";
|
||||
import * as Setting from "./Setting";
|
||||
import IframeEditor from "./IframeEditor";
|
||||
import {Tabs} from "antd";
|
||||
|
||||
const {TabPane} = Tabs;
|
||||
|
||||
const CasbinEditor = ({model, onModelTextChange}) => {
|
||||
const [activeKey, setActiveKey] = useState("advanced");
|
||||
const iframeRef = useRef(null);
|
||||
const [localModelText, setLocalModelText] = useState(model.modelText);
|
||||
|
||||
const handleModelTextChange = useCallback((newModelText) => {
|
||||
if (!Setting.builtInObject(model)) {
|
||||
setLocalModelText(newModelText);
|
||||
onModelTextChange(newModelText);
|
||||
}
|
||||
}, [model, onModelTextChange]);
|
||||
|
||||
const syncModelText = useCallback(() => {
|
||||
return new Promise((resolve) => {
|
||||
if (activeKey === "advanced" && iframeRef.current) {
|
||||
const handleSyncMessage = (event) => {
|
||||
if (event.data.type === "modelUpdate") {
|
||||
window.removeEventListener("message", handleSyncMessage);
|
||||
handleModelTextChange(event.data.modelText);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
window.addEventListener("message", handleSyncMessage);
|
||||
iframeRef.current.getModelText();
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}, [activeKey, handleModelTextChange]);
|
||||
|
||||
const handleTabChange = (key) => {
|
||||
syncModelText().then(() => {
|
||||
setActiveKey(key);
|
||||
if (key === "advanced" && iframeRef.current) {
|
||||
iframeRef.current.updateModelText(localModelText);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLocalModelText(model.modelText);
|
||||
}, [model.modelText]);
|
||||
|
||||
return (
|
||||
<div style={{height: "100%", width: "100%", display: "flex", flexDirection: "column"}}>
|
||||
<Tabs activeKey={activeKey} onChange={handleTabChange} style={{flex: "0 0 auto", marginTop: "-10px"}}>
|
||||
<TabPane tab="Basic Editor" key="basic" />
|
||||
<TabPane tab="Advanced Editor" key="advanced" />
|
||||
</Tabs>
|
||||
<div style={{flex: "1 1 auto", overflow: "hidden"}}>
|
||||
{activeKey === "advanced" ? (
|
||||
<IframeEditor
|
||||
ref={iframeRef}
|
||||
initialModelText={localModelText}
|
||||
onModelTextChange={handleModelTextChange}
|
||||
style={{width: "100%", height: "100%"}}
|
||||
/>
|
||||
) : (
|
||||
<CodeMirror
|
||||
value={localModelText}
|
||||
className="full-height-editor no-horizontal-scroll-editor"
|
||||
options={{mode: "properties", theme: "default"}}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
handleModelTextChange(value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CasbinEditor;
|
66
web/src/IframeEditor.js
Normal file
66
web/src/IframeEditor.js
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright 2024 The Casdoor 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, {forwardRef, useEffect, useImperativeHandle, useRef, useState} from "react";
|
||||
|
||||
const IframeEditor = forwardRef(({initialModelText, onModelTextChange}, ref) => {
|
||||
const iframeRef = useRef(null);
|
||||
const [iframeReady, setIframeReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleMessage = (event) => {
|
||||
if (event.origin !== "https://editor.casbin.org") {return;}
|
||||
|
||||
if (event.data.type === "modelUpdate") {
|
||||
onModelTextChange(event.data.modelText);
|
||||
} else if (event.data.type === "iframeReady") {
|
||||
setIframeReady(true);
|
||||
iframeRef.current?.contentWindow.postMessage({
|
||||
type: "initializeModel",
|
||||
modelText: initialModelText,
|
||||
}, "*");
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", handleMessage);
|
||||
return () => window.removeEventListener("message", handleMessage);
|
||||
}, [onModelTextChange, initialModelText]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getModelText: () => {
|
||||
iframeRef.current?.contentWindow.postMessage({type: "getModelText"}, "*");
|
||||
},
|
||||
updateModelText: (newModelText) => {
|
||||
if (iframeReady) {
|
||||
iframeRef.current?.contentWindow.postMessage({
|
||||
type: "updateModelText",
|
||||
modelText: newModelText,
|
||||
}, "*");
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
src="https://editor.casbin.org/model-editor"
|
||||
frameBorder="0"
|
||||
width="100%"
|
||||
height="500px"
|
||||
title="Casbin Model Editor"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default IframeEditor;
|
@ -18,11 +18,7 @@ import * as ModelBackend from "./backend/ModelBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
|
||||
require("codemirror/mode/properties/properties");
|
||||
import ModelEditor from "./CasbinEditor";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
@ -147,16 +143,10 @@ class ModelEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("model:Model text"), i18next.t("model:Model text - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
<div style={{width: "100%"}} >
|
||||
<CodeMirror
|
||||
value={this.state.model.modelText}
|
||||
options={{mode: "properties", theme: "default"}}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
if (Setting.builtInObject(this.state.model)) {
|
||||
return;
|
||||
}
|
||||
this.updateModelField("modelText", value);
|
||||
}}
|
||||
<div style={{position: "relative", height: "500px"}} >
|
||||
<ModelEditor
|
||||
model={this.state.model}
|
||||
onModelTextChange={(value) => this.updateModelField("modelText", value)}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
|
@ -938,7 +938,7 @@ class LoginPage extends React.Component {
|
||||
signinItem.label ? Setting.renderSignupLink(application, signinItem.label) :
|
||||
(
|
||||
<React.Fragment>
|
||||
{i18next.t("login:No account?")}
|
||||
{i18next.t("login:No account?")}
|
||||
{
|
||||
Setting.renderSignupLink(application, i18next.t("login:sign up now"))
|
||||
}
|
||||
|
@ -51,3 +51,19 @@ code {
|
||||
.custom-link:hover {
|
||||
color: rgb(64 64 64) !important;
|
||||
}
|
||||
|
||||
.full-height-editor {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.full-height-editor [class*="CodeMirror"] {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.no-horizontal-scroll-editor [class*="CodeMirror-hscrollbar"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.no-horizontal-scroll-editor [class*="CodeMirror-scroll"] {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ class SignupTable extends React.Component {
|
||||
}
|
||||
|
||||
addRow(table) {
|
||||
const row = {name: Setting.getNewRowNameForTable(table, "Please select a signup item"), visible: true, required: true, rule: "None", customCss: ""};
|
||||
const row = {name: Setting.getNewRowNameForTable(table, "Please select a signup item"), visible: true, required: true, options: [], rule: "None", customCss: ""};
|
||||
if (table === undefined) {
|
||||
table = [];
|
||||
}
|
||||
@ -201,6 +201,25 @@ class SignupTable extends React.Component {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("provider:Type"),
|
||||
dataIndex: "type",
|
||||
key: "type",
|
||||
width: "160px",
|
||||
render: (text, record, index) => {
|
||||
const options = [
|
||||
{id: "Input", name: i18next.t("application:Input")},
|
||||
{id: "Single Choice", name: i18next.t("application:Single Choice")},
|
||||
{id: "Multiple Choices", name: i18next.t("application:Multiple Choices")},
|
||||
];
|
||||
|
||||
return (
|
||||
<Select virtual={false} style={{width: "100%"}} value={text} onChange={(value => {
|
||||
this.updateField(table, index, "type", value);
|
||||
})} options={options.map(item => Setting.getOption(item.name, item.id))} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("signup:Label"),
|
||||
dataIndex: "label",
|
||||
@ -261,7 +280,7 @@ class SignupTable extends React.Component {
|
||||
title: i18next.t("signup:Placeholder"),
|
||||
dataIndex: "placeholder",
|
||||
key: "placeholder",
|
||||
width: "200px",
|
||||
width: "110px",
|
||||
render: (text, record, index) => {
|
||||
if (record.name.startsWith("Text ")) {
|
||||
return null;
|
||||
@ -274,6 +293,26 @@ class SignupTable extends React.Component {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("signup:Options"),
|
||||
dataIndex: "options",
|
||||
key: "options",
|
||||
width: "180px",
|
||||
render: (text, record, index) => {
|
||||
if (record.type !== "Single Choice" && record.type !== "Multiple Choices") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Select virtual={false} mode="tags" style={{width: "100%"}} value={text}
|
||||
onChange={(value => {
|
||||
this.updateField(table, index, "options", value);
|
||||
})}
|
||||
options={text?.map((option) => Setting.getOption(option, option))}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("signup:Regex"),
|
||||
dataIndex: "regex",
|
||||
|
Reference in New Issue
Block a user