feat: manager applications in organization scope (#1290)

* feat: manager applications in organization scope(front end)

* fix: application can use own organization and admin provider

* fix: improve methed to get provider

* fix: modify provider methods by convention
This commit is contained in:
Yaodong Yu
2022-11-21 01:17:55 +08:00
committed by GitHub
parent 19ba37e0c2
commit 7e756b8ee2
20 changed files with 146 additions and 68 deletions

View File

@ -420,6 +420,8 @@ class App extends Component {
</Link>
</Menu.Item>
);
}
if (Setting.isLocalAdminUser(this.state.account)) {
res.push(
<Menu.Item key="/applications">
<Link to="/applications">
@ -427,9 +429,6 @@ class App extends Component {
</Link>
</Menu.Item>
);
}
if (Setting.isLocalAdminUser(this.state.account)) {
res.push(
<Menu.Item key="/providers">
<Link to="/providers">
@ -565,10 +564,9 @@ class App extends Component {
<Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} />
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
<Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
<Route exact path="/providers/:organizationName/: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/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
<Route exact path="/applications/:organizationName/: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} />)} />

View File

@ -91,7 +91,7 @@ class ApplicationEditPage extends React.Component {
super(props);
this.state = {
classes: props,
owner: props.account.owner,
owner: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
applicationName: props.match.params.applicationName,
application: null,
organizations: [],
@ -142,21 +142,11 @@ class ApplicationEditPage extends React.Component {
}
getProviders() {
if (Setting.isAdminUser(this.props.account)) {
ProviderBackend.getGlobalProviders()
.then((res) => {
this.setState({
providers: res,
});
});
} else {
ProviderBackend.getProviders(this.state.owner)
.then((res) => {
this.setState({
providers: res,
});
});
}
ProviderBackend.getProviders(this.state.owner).then((res => {
this.setState({
providers: res,
});
}));
}
getSamlMetadata() {
@ -287,7 +277,7 @@ class ApplicationEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.application.organization} onChange={(value => {this.updateApplicationField("organization", value);})}>
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.application.organization} onChange={(value => {this.updateApplicationField("organization", value);})}>
{
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
}
@ -791,7 +781,7 @@ class ApplicationEditPage extends React.Component {
submitApplicationEdit(willExist) {
const application = Setting.deepCopy(this.state.application);
ApplicationBackend.updateApplication(this.state.application.owner, this.state.applicationName, application)
ApplicationBackend.updateApplication("admin", this.state.applicationName, application)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
@ -802,7 +792,7 @@ class ApplicationEditPage extends React.Component {
if (willExist) {
this.props.history.push("/applications");
} else {
this.props.history.push(`/applications/${this.state.application.name}`);
this.props.history.push(`/applications/${this.state.application.organization}/${this.state.application.name}`);
}
} else {
Setting.showMessage("error", res.msg);

View File

@ -23,11 +23,28 @@ import i18next from "i18next";
import BaseListPage from "./BaseListPage";
class ApplicationListPage extends BaseListPage {
constructor(props) {
super(props);
this.state = {
classes: props,
organizationName: props.account.owner,
data: [],
pagination: {
current: 1,
pageSize: 10,
},
loading: false,
searchText: "",
searchedColumn: "",
};
}
newApplication() {
const randomName = Setting.getRandomName();
return {
owner: "admin", // this.props.account.applicationname,
owner: "admin", // this.props.account.applicationName,
name: `application_${randomName}`,
organization: this.state.organizationName,
createdTime: moment().format(),
displayName: `New Application - ${randomName}`,
logo: `${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`,
@ -61,7 +78,7 @@ class ApplicationListPage extends BaseListPage {
const newApplication = this.newApplication();
ApplicationBackend.addApplication(newApplication)
.then((res) => {
this.props.history.push({pathname: `/applications/${newApplication.name}`, mode: "add"});
this.props.history.push({pathname: `/applications/${newApplication.organization}/${newApplication.name}`, mode: "add"});
}
)
.catch(error => {
@ -96,7 +113,7 @@ class ApplicationListPage extends BaseListPage {
...this.getColumnSearchProps("name"),
render: (text, record, index) => {
return (
<Link to={`/applications/${text}`}>
<Link to={`/applications/${record.organization}/${text}`}>
{text}
</Link>
);
@ -213,7 +230,7 @@ class ApplicationListPage extends BaseListPage {
render: (text, record, index) => {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/applications/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/applications/${record.organization}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
title={`Sure to delete application: ${record.name} ?`}
onConfirm={() => this.deleteApplication(index)}
@ -254,7 +271,8 @@ class ApplicationListPage extends BaseListPage {
const field = params.searchedColumn, value = params.searchText;
const sortField = params.sortField, sortOrder = params.sortOrder;
this.setState({loading: true});
ApplicationBackend.getApplications("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
(Setting.isAdminUser(this.props.account) ? ApplicationBackend.getApplications("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) :
ApplicationBackend.getApplicationsByOrganization("admin", this.state.organizationName, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@ -22,6 +22,7 @@ import {authConfig} from "./auth/Auth";
import * as ProviderEditTestEmail from "./TestEmailWidget";
import copy from "copy-to-clipboard";
import {CaptchaPreview} from "./common/CaptchaPreview";
import * as OrganizationBackend from "./backend/OrganizationBackend";
const {Option} = Select;
const {TextArea} = Input;
@ -34,11 +35,13 @@ class ProviderEditPage extends React.Component {
providerName: props.match.params.providerName,
owner: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
provider: null,
organizations: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit",
};
}
UNSAFE_componentWillMount() {
this.getOrganizations();
this.getProvider();
}
@ -51,6 +54,17 @@ class ProviderEditPage extends React.Component {
});
}
getOrganizations() {
if (Setting.isAdminUser(this.props.account)) {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: res.msg === undefined ? res : [],
});
});
}
}
parseProviderField(key, value) {
if (["port"].includes(key)) {
value = Setting.myParseInt(value);
@ -191,6 +205,19 @@ class ProviderEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.provider.owner} onChange={(value => {this.updateProviderField("owner", value);})}>
{Setting.isAdminUser(this.props.account) ? <Option key={"admin"} value={"admin"}>{i18next.t("provider:admin (share)")}</Option> : null}
{
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Category"), i18next.t("provider:Category - Tooltip"))} :
@ -760,18 +787,19 @@ class ProviderEditPage extends React.Component {
submitProviderEdit(willExist) {
const provider = Setting.deepCopy(this.state.provider);
ProviderBackend.updateProvider(this.state.provider.owner, this.state.providerName, provider)
ProviderBackend.updateProvider(this.state.owner, this.state.providerName, provider)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
this.setState({
owner: this.state.provider.owner,
providerName: this.state.provider.name,
});
if (willExist) {
this.props.history.push("/providers");
} else {
this.props.history.push(`/providers/${this.state.provider.name}`);
this.props.history.push(`/providers/${this.state.provider.owner}/${this.state.provider.name}`);
}
} else {
Setting.showMessage("error", res.msg);

View File

@ -27,7 +27,7 @@ class ProviderListPage extends BaseListPage {
super(props);
this.state = {
classes: props,
owner: Setting.isAdminUser(props.account) ? "admin" : props.account.organization.name,
owner: Setting.isAdminUser(props.account) ? "admin" : props.account.owner,
data: [],
pagination: {
current: 1,
@ -96,12 +96,20 @@ class ProviderListPage extends BaseListPage {
...this.getColumnSearchProps("name"),
render: (text, record, index) => {
return (
<Link to={`/providers/${text}`}>
<Link to={`/providers/${record.owner}/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("general:Organization"),
dataIndex: "owner",
key: "owner",
width: "150px",
sorter: true,
...this.getColumnSearchProps("owner"),
},
{
title: i18next.t("general:Created time"),
dataIndex: "createdTime",
@ -192,12 +200,12 @@ class ProviderListPage extends BaseListPage {
render: (text, record, index) => {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/providers/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Button disabled={!Setting.isAdminUser(this.props.account) && (record.owner !== this.props.account.owner)} style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/providers/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
title={`Sure to delete provider: ${record.name} ?`}
onConfirm={() => this.deleteProvider(index)}
>
<Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
<Button disabled={!Setting.isAdminUser(this.props.account) && (record.owner !== this.props.account.owner)} style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -95,7 +95,7 @@ class ResourceListPage extends BaseListPage {
...this.getColumnSearchProps("provider"),
render: (text, record, index) => {
return (
<Link to={`/providers/${text}`}>
<Link to={`/providers/${record.owner}/${text}`}>
{text}
</Link>
);

View File

@ -102,7 +102,7 @@ class TokenListPage extends BaseListPage {
...this.getColumnSearchProps("application"),
render: (text, record, index) => {
return (
<Link to={`/applications/${text}`}>
<Link to={`/applications/${record.organization}/${text}`}>
{text}
</Link>
);

View File

@ -167,7 +167,7 @@ class UserListPage extends BaseListPage {
...this.getColumnSearchProps("signupApplication"),
render: (text, record, index) => {
return (
<Link to={`/applications/${text}`}>
<Link to={`/applications/${record.owner}/${text}`}>
{text}
</Link>
);

View File

@ -24,8 +24,8 @@ export function getApplications(owner, page = "", pageSize = "", field = "", val
}).then(res => res.json());
}
export function getApplicationsByOrganization(owner, organization) {
return fetch(`${Setting.ServerUrl}/api/get-organization-applications?owner=${owner}&organization=${organization}`, {
export function getApplicationsByOrganization(owner, organization, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
return fetch(`${Setting.ServerUrl}/api/get-organization-applications?owner=${owner}&organization=${organization}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
method: "GET",
credentials: "include",
headers: {
@ -68,7 +68,6 @@ export function updateApplication(owner, name, application) {
export function addApplication(application) {
const newApplication = Setting.deepCopy(application);
newApplication.organization = "built-in";
return fetch(`${Setting.ServerUrl}/api/add-application`, {
method: "POST",
credentials: "include",