diff --git a/src/client/keystone/index.js b/src/client/keystone/index.js
index 6578223c..ed199783 100644
--- a/src/client/keystone/index.js
+++ b/src/client/keystone/index.js
@@ -105,6 +105,11 @@ class KeystoneClient extends Base {
{
key: 'groups',
},
+ {
+ name: 'applicationCredentials',
+ key: 'application_credentials',
+ responseKey: 'application_credential',
+ },
],
extendOperations: [
{
diff --git a/src/components/Layout/GlobalHeader/OpenRc.jsx b/src/components/Layout/GlobalHeader/OpenRc.jsx
index b16c7fd7..8678a0bd 100644
--- a/src/components/Layout/GlobalHeader/OpenRc.jsx
+++ b/src/components/Layout/GlobalHeader/OpenRc.jsx
@@ -12,19 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-/* eslint-disable no-useless-escape */
-import React from 'react';
import { inject, observer } from 'mobx-react';
-import { Typography } from 'antd';
import { ModalAction } from 'containers/Action';
import { allCanReadPolicy } from 'resources/policy';
import globalAuthCatalogStore from 'stores/keystone/catalog';
-import { getOpenRc } from 'resources/openstack-rc';
+import { getCredentialOpenRc, getPwdOpenRc } from 'resources/openstack-rc';
import FileSaver from 'file-saver';
-import styles from './index.less';
-// import { DownSquareOutlined } from '@ant-design/icons';
-
-const { Paragraph } = Typography;
@inject('rootStore')
@observer
@@ -37,67 +30,20 @@ export default class OpenRc extends ModalAction {
static title = t('Get OpenRC file');
- componentDidMount() {
- this.getData();
- }
-
- async getData() {
- await this.store.fetchList({});
- this.exportRcFile();
- }
-
get name() {
return t('Get OpenRC file');
}
- getModalSize() {
- return 'large';
- }
-
- get token() {
- const key = 'keystone_token';
- const item = localStorage.getItem(key);
- try {
- return JSON.parse(item) || {};
- } catch (e) {
- return {};
- }
- }
-
get showNotice() {
return false;
}
- get tokenValue() {
- return this.token.value || '';
- }
-
- get tokenExpiry() {
- const { expires } = this.token;
- return expires || 0;
- }
-
get user() {
const { user } = this.props.rootStore;
return user;
}
- get project() {
- const {
- project: {
- id: projectId = '',
- name: projectName = '',
- domain: { name: projectDomainName } = {},
- } = {},
- } = this.user || {};
- return {
- projectId,
- projectName,
- projectDomainName,
- };
- }
-
- get openRc() {
+ getOpenRC(type) {
const {
project: {
id: projectId = '',
@@ -114,20 +60,29 @@ export default class OpenRc extends ModalAction {
const authUrl = endpoints.filter(
(endpoint) => endpoint.interface === 'public'
)[0].url;
- const openstackRc = getOpenRc({
- authUrl,
- projectId,
- projectName,
- projectDomain,
- userDomain,
- userName,
- region,
- });
+ let openstackRc = '';
+ if (type === 'password') {
+ openstackRc = getPwdOpenRc({
+ authUrl,
+ projectId,
+ projectName,
+ projectDomain,
+ userDomain,
+ userName,
+ region,
+ });
+ } else {
+ openstackRc = getCredentialOpenRc({
+ authUrl,
+ region,
+ });
+ }
+
return openstackRc;
}
- exportRcFile = () => {
- const blob = new Blob([this.openRc], {
+ exportRcFile = (data) => {
+ const blob = new Blob([data], {
type: 'text/plain;charset=utf-8',
});
FileSaver.saveAs(blob, 'openrc.sh');
@@ -135,7 +90,7 @@ export default class OpenRc extends ModalAction {
get defaultValue() {
const value = {
- token: this.tokenValue,
+ type: 'password',
};
return value;
}
@@ -144,34 +99,30 @@ export default class OpenRc extends ModalAction {
static allowed = () => Promise.resolve(true);
- get labelCol() {
- return {
- xs: { span: 0 },
- sm: { span: 0 },
- };
- }
-
- get wrapperCol() {
- return {
- xs: { span: 24 },
- sm: { span: 24 },
- };
- }
-
get formItems() {
return [
{
- name: 'token',
- label: '',
- type: 'label',
- component: (
-
- {this.openRc}
-
- ),
+ name: 'type',
+ label: t('Type'),
+ type: 'select',
+ options: [
+ {
+ label: t('Password Type'),
+ value: 'password',
+ },
+ {
+ label: t('Credential Type'),
+ value: 'credential',
+ },
+ ],
},
];
}
- onSubmit = () => Promise.resolve();
+ onSubmit = (values) => {
+ const { type } = values;
+ return this.store.fetchList().then(() => {
+ return this.exportRcFile(this.getOpenRC(type));
+ });
+ };
}
diff --git a/src/components/Tables/Base/index.jsx b/src/components/Tables/Base/index.jsx
index 33e33ff1..e8c50d04 100644
--- a/src/components/Tables/Base/index.jsx
+++ b/src/components/Tables/Base/index.jsx
@@ -44,6 +44,7 @@ import { getNoValue } from 'utils/index';
import { columnRender } from 'utils/render';
import { getLocalStorageItem, setLocalStorageItem } from 'utils/local-storage';
import { inject } from 'mobx-react';
+import globalRootStore from 'stores/root';
import CustomColumns from './CustomColumns';
import ItemActionButtons from './ItemActionButtons';
import PrimaryActionButtons from './PrimaryActionButtons';
@@ -331,7 +332,11 @@ export default class BaseTable extends React.Component {
return (
<>
- {projectId}
+ {globalRootStore.hasAdminRole ? (
+ {projectId}
+ ) : (
+ projectId
+ )}
{value || '-'}
>
@@ -360,7 +365,14 @@ export default class BaseTable extends React.Component {
if (render) {
return render;
}
- const { linkPrefix, dataIndex, idKey, linkPrefixFunc, linkFunc } = column;
+ const {
+ linkPrefix,
+ dataIndex,
+ idKey,
+ linkPrefixFunc,
+ linkFunc,
+ hasNoDetail = false,
+ } = column;
const { rowKey } = this.props;
return (value, record) => {
const idValue = get(record, idKey || rowKey);
@@ -374,7 +386,15 @@ export default class BaseTable extends React.Component {
url = this.getLinkUrl(linkValue, idValue);
}
const nameValue = value || get(record, dataIndex) || '-';
- if (!url) {
+ if (hasNoDetail) {
+ return (
+
+
{idValue}
+
{nameValue}
+
+ );
+ }
+ if (!url && !hasNoDetail) {
return nameValue;
}
return (
diff --git a/src/layouts/user-menu.jsx b/src/layouts/user-menu.jsx
index a3b9d9c9..a804d09a 100644
--- a/src/layouts/user-menu.jsx
+++ b/src/layouts/user-menu.jsx
@@ -13,7 +13,7 @@
// limitations under the License.
import React from 'react';
-import { HomeOutlined } from '@ant-design/icons';
+import { HomeOutlined, UserOutlined } from '@ant-design/icons';
const renderMenu = (t) => {
if (!t) {
@@ -29,6 +29,15 @@ const renderMenu = (t) => {
hasBreadcrumb: false,
hasChildren: false,
},
+ {
+ path: '/user/application-credentials',
+ name: t('Application Credentials'),
+ key: '/user/application-credentials',
+ level: 0,
+ icon: ,
+ children: [],
+ hasChildren: false,
+ },
];
return menu;
};
diff --git a/src/locales/en.json b/src/locales/en.json
index f5a4e522..8a646659 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -94,6 +94,7 @@
"Anti-affinity (not mandatory):": "Anti-affinity (not mandatory):",
"Any": "Any",
"Any(Random)": "Any(Random)",
+ "Application Credentials": "Application Credentials",
"Application Template": "Application Template",
"Apply for extended quota": "Apply for extended quota",
"Applying": "Applying",
@@ -299,6 +300,7 @@
"Create": "Create",
"Create ": "Create ",
"Create Allowed Address Pair": "Create Allowed Address Pair",
+ "Create Application Credentials": "Create Application Credentials",
"Create Backup": "Create Backup",
"Create Bandwidth Limit Rule": "Create Bandwidth Limit Rule",
"Create Bare Metal Node": "Create Bare Metal Node",
@@ -367,6 +369,7 @@
"Created Time": "Created Time",
"Creating": "Creating",
"Creation Timeout (Minutes)": "Creation Timeout (Minutes)",
+ "Credential Type": "Credential Type",
"Current Availability Zones": "Current Availability Zones",
"Current Capacity (GB)": "Current Capacity (GB)",
"Current Compute Host": "Current Compute Host",
@@ -416,6 +419,7 @@
"Delay Interval(s)": "Delay Interval(s)",
"Delete": "Delete",
"Delete Allowed Address Pair": "Delete Allowed Address Pair",
+ "Delete Application Credential": "Delete Application Credential",
"Delete Backup": "Delete Backup",
"Delete Bandwidth Egress Rules": "Delete Bandwidth Egress Rules",
"Delete Bandwidth Ingress Rules": "Delete Bandwidth Ingress Rules",
@@ -612,6 +616,7 @@
"Execution Result": "Execution Result",
"Expand Advanced Options": "Expand Advanced Options",
"Expired Time": "Expired Time",
+ "Expires At": "Expires At",
"Extend Root Volume": "Extend Root Volume",
"Extend Volume": "Extend Volume",
"Extend root volume": "Extend root volume",
@@ -1109,6 +1114,7 @@
"Parameter": "Parameter",
"Params Setting": "Params Setting",
"Password": "Password",
+ "Password Type": "Password Type",
"Password changed successfully, please log in again.": "Password changed successfully, please log in again.",
"Password must be the same with confirm password.": "Password must be the same with confirm password.",
"Pause": "Pause",
@@ -1140,7 +1146,6 @@
"Placement service:": "Placement service:",
"Platform Info": "Platform Info",
"Please confirm your password!": "Please confirm your password!",
- "Please do not request repeat quickly": "Please do not request repeat quickly",
"Please enter a valid Email Address!": "Please enter a valid Email Address!",
"Please enter a valid Phone Number": "Please enter a valid Phone Number",
"Please enter complete key value!": "Please enter complete key value!",
@@ -1521,15 +1526,13 @@
"Switch Info": "Switch Info",
"Switch Language": "Switch Language",
"Switch Project": "Switch Project",
- "Syetem Admin": "Syetem Admin",
- "Syetem Reader": "Syetem Reader",
"System": "System",
+ "System Admin": "System Admin",
"System Config": "System Config",
"System Disk": "System Disk",
"System Info": "System Info",
+ "System Reader": "System Reader",
"System Scope": "System Scope",
- "System error": "System error",
- "System is busy, please try again later": "System is busy, please try again later",
"System is error, please try again later.": "System is error, please try again later.",
"TCP": "TCP",
"Tags": "Tags",
@@ -1788,6 +1791,7 @@
"add router": "add router",
"all": "all",
"an optional string field to be used to store any vendor-specific information": "an optional string field to be used to store any vendor-specific information",
+ "application credential": "application credential",
"associate floating ip": "associate floating ip",
"attach interface": "attach interface",
"availability zones": "availability zones",
@@ -1835,6 +1839,7 @@
"delete DNAT rule": "delete DNAT rule",
"delete SNAT rule": "delete SNAT rule",
"delete allowed address pair": "delete allowed address pair",
+ "delete application credential": "delete application credential",
"delete backup": "delete backup",
"delete bandwidth egress rules": "delete bandwidth egress rules",
"delete bandwidth ingress rules": "delete bandwidth ingress rules",
diff --git a/src/locales/zh.json b/src/locales/zh.json
index 04ca0496..824efb9e 100644
--- a/src/locales/zh.json
+++ b/src/locales/zh.json
@@ -94,6 +94,7 @@
"Anti-affinity (not mandatory):": "反亲和 (非强制):",
"Any": "",
"Any(Random)": "任意(随机)",
+ "Application Credentials": "应用凭证",
"Application Template": "应用模板",
"Apply for extended quota": "申请扩展配额",
"Applying": "使用中",
@@ -299,6 +300,7 @@
"Create": "创建",
"Create ": "创建",
"Create Allowed Address Pair": "创建可用地址对",
+ "Create Application Credentials": "创建应用凭证",
"Create Backup": "创建备份",
"Create Bandwidth Limit Rule": "创建带宽限制规则",
"Create Bare Metal Node": "创建裸机节点",
@@ -367,6 +369,7 @@
"Created Time": "创建时间",
"Creating": "创建中",
"Creation Timeout (Minutes)": "创建超时(分钟)",
+ "Credential Type": "凭证类型",
"Current Availability Zones": "当前可用域",
"Current Capacity (GB)": "当前容量(GB)",
"Current Compute Host": "当前计算节点",
@@ -416,6 +419,7 @@
"Delay Interval(s)": "检查间隔(秒)",
"Delete": "删除",
"Delete Allowed Address Pair": "删除可用地址对",
+ "Delete Application Credential": "删除应用凭证",
"Delete Backup": "删除备份",
"Delete Bandwidth Egress Rules": "删除带宽出方向限制",
"Delete Bandwidth Ingress Rules": "删除带宽入方向限制",
@@ -612,6 +616,7 @@
"Execution Result": "执行结果",
"Expand Advanced Options": "展开高级选项",
"Expired Time": "到期时间",
+ "Expires At": "到期时间",
"Extend Root Volume": "扩容根硬盘",
"Extend Volume": "扩容云硬盘",
"Extend root volume": "扩容根硬盘",
@@ -1108,6 +1113,7 @@
"Parameter": "参数",
"Params Setting": "参数设置",
"Password": "密码",
+ "Password Type": "密码类型",
"Password changed successfully, please log in again.": "密码修改成功,请重新登录。",
"Password must be the same with confirm password.": "密码和确认密码必须一致。",
"Pause": "暂停",
@@ -1139,7 +1145,6 @@
"Placement service:": "放置服务(placement):",
"Platform Info": "平台概况",
"Please confirm your password!": "请确认您的密码",
- "Please do not request repeat quickly": "",
"Please enter a valid Email Address!": "请输入一个有效的邮箱地址",
"Please enter a valid Phone Number": "请输入一个有效的手机号",
"Please enter complete key value!": "请输入完整的键值!",
@@ -1520,15 +1525,13 @@
"Switch Info": "交换机信息",
"Switch Language": "切换语言",
"Switch Project": "切换项目",
- "Syetem Admin": "系统管理权限",
- "Syetem Reader": "系统只读权限",
"System": "系统",
+ "System Admin": "系统管理权限",
"System Config": "系统配置",
"System Disk": "系统盘",
"System Info": "系统信息",
+ "System Reader": "系统只读权限",
"System Scope": "绑定系统范围",
- "System error": "",
- "System is busy, please try again later": "",
"System is error, please try again later.": "系统出错,请稍后再试。",
"TCP": "",
"Tags": "标签",
@@ -1787,6 +1790,7 @@
"add router": "添加路由器",
"all": "所有",
"an optional string field to be used to store any vendor-specific information": "选填选型,用于储存供应商的特定信息",
+ "application credential": "应用凭证",
"associate floating ip": "绑定浮动IP",
"attach interface": "挂载网卡",
"availability zones": "可用域",
@@ -1834,6 +1838,7 @@
"delete DNAT rule": "删除DNAT规则",
"delete SNAT rule": "删除SNAT规则",
"delete allowed address pair": "删除可用地址对",
+ "delete application credential": "删除应用凭证",
"delete backup": "删除备份",
"delete bandwidth egress rules": "删除出方向带宽限制规则",
"delete bandwidth ingress rules": "删除入方向带宽限制规则",
@@ -1978,7 +1983,6 @@
"subnet": "子网",
"subnets": "子网",
"suspend instance": "挂起云主机",
- "the instance only has one virtual adapter": "云主机只有一个网卡",
"the policy is in use": "策略正在使用中",
"the router has connected subnet": "路由器有连接的子网",
"the vpn gateway is in use": "VPN网关正在使用中",
diff --git a/src/pages/identity/containers/User/Detail/index.jsx b/src/pages/identity/containers/User/Detail/index.jsx
index 5d7d574e..b850cc2e 100644
--- a/src/pages/identity/containers/User/Detail/index.jsx
+++ b/src/pages/identity/containers/User/Detail/index.jsx
@@ -18,6 +18,7 @@ import { Badge } from 'antd';
import { UserStore } from 'stores/keystone/user';
import globalDomainStore from 'stores/keystone/domain';
import Base from 'containers/TabDetail';
+import Credentials from 'src/pages/user-center/containers/Credentials';
import UserGroup from '../../UserGroup';
import Project from '../../Project';
import actionConfigs from '../actions';
@@ -128,6 +129,11 @@ export class UserDetail extends Base {
key: 'userGroup',
component: UserGroup,
},
+ {
+ title: t('Application Credentials'),
+ key: 'applicationCredentials',
+ component: Credentials,
+ },
];
return tabs;
}
diff --git a/src/pages/user-center/containers/Credentials/actions/Create.jsx b/src/pages/user-center/containers/Credentials/actions/Create.jsx
new file mode 100644
index 00000000..ae596f0f
--- /dev/null
+++ b/src/pages/user-center/containers/Credentials/actions/Create.jsx
@@ -0,0 +1,98 @@
+// Copyright 2021 99cloud
+//
+// 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 { inject, observer } from 'mobx-react';
+import { ModalAction } from 'containers/Action';
+import globalCredentialStore from 'stores/keystone/credential';
+import moment from 'moment';
+import globalRootStore from 'stores/root';
+import { toJS } from 'mobx';
+import FileSaver from 'file-saver';
+
+@inject('rootStore')
+@observer
+export default class Create extends ModalAction {
+ static id = 'create-application_credentials';
+
+ static title = t('Create Application Credentials');
+
+ get name() {
+ return t('Create Application Credentials');
+ }
+
+ onSubmit = (values) => {
+ if (values.expires_at) {
+ values.expires_at = values.expires_at.clone().endOf('day');
+ }
+ if (values.roles) {
+ values.roles = Object.keys(values.roles)
+ .filter((key) => values.roles[key])
+ .map((i) => ({ id: i }));
+ }
+ return globalCredentialStore.create(values).then((res) => {
+ const { links, roles, system, unrestricted, user_id, name, ...rest } =
+ res.application_credential;
+ const filename = `${name}.json`;
+ const blob = new Blob([JSON.stringify(rest, null, 2)], {
+ type: 'text/plain;charset=utf-8',
+ });
+ FileSaver.saveAs(blob, filename);
+ });
+ };
+
+ static allowed() {
+ return Promise.resolve(true);
+ }
+
+ static policy = 'identity:create_application_credential';
+
+ get roleOptions() {
+ // const baseRoles = toJS(globalRootStore.baseRoles);
+ const roles = toJS(globalRootStore.roles);
+
+ return roles.map((i) => ({ label: i.name, value: i.id }));
+ }
+
+ get formItems() {
+ return [
+ {
+ name: 'name',
+ label: t('Name'),
+ type: 'input-name',
+ required: true,
+ },
+ {
+ name: 'expires_at',
+ label: t('Expires At'),
+ type: 'date-picker',
+ showToday: false,
+ disabledDate: (current) =>
+ current && current < moment().subtract(1, 'days').endOf('d'),
+ required: false,
+ },
+ {
+ name: 'roles',
+ label: t('Roles'),
+ type: 'check-group',
+ options: this.roleOptions,
+ },
+ {
+ name: 'description',
+ label: t('Description'),
+ type: 'textarea',
+ required: false,
+ },
+ ];
+ }
+}
diff --git a/src/pages/user-center/containers/Credentials/actions/Delete.jsx b/src/pages/user-center/containers/Credentials/actions/Delete.jsx
new file mode 100644
index 00000000..a06be2df
--- /dev/null
+++ b/src/pages/user-center/containers/Credentials/actions/Delete.jsx
@@ -0,0 +1,45 @@
+// Copyright 2021 99cloud
+//
+// 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 { ConfirmAction } from 'containers/Action';
+import globalCredentialStore from 'stores/keystone/credential';
+
+export default class DeleteAction extends ConfirmAction {
+ get id() {
+ return 'delete';
+ }
+
+ get title() {
+ return t('Delete Application Credential');
+ }
+
+ get buttonType() {
+ return 'danger';
+ }
+
+ get buttonText() {
+ return t('Delete');
+ }
+
+ get actionName() {
+ return t('delete application credential');
+ }
+
+ policy = 'identity:delete_application_credential';
+
+ onSubmit = (data) => {
+ const { user_id, id } = data;
+ return globalCredentialStore.client.delete(user_id, id);
+ };
+}
diff --git a/src/pages/user-center/containers/Credentials/actions/index.jsx b/src/pages/user-center/containers/Credentials/actions/index.jsx
new file mode 100644
index 00000000..664bf698
--- /dev/null
+++ b/src/pages/user-center/containers/Credentials/actions/index.jsx
@@ -0,0 +1,32 @@
+// Copyright 2021 99cloud
+//
+// 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 Create from './Create';
+import Delete from './Delete';
+
+export const actionConfigs = {
+ rowActions: {
+ firstAction: Delete,
+ },
+ batchActions: [Delete],
+ primaryActions: [Create],
+};
+
+export const detailConfigs = {
+ rowActions: {
+ firstAction: Delete,
+ },
+ batchActions: [Delete],
+ primaryActions: [],
+};
diff --git a/src/pages/user-center/containers/Credentials/index.jsx b/src/pages/user-center/containers/Credentials/index.jsx
new file mode 100644
index 00000000..45ab83b9
--- /dev/null
+++ b/src/pages/user-center/containers/Credentials/index.jsx
@@ -0,0 +1,125 @@
+// Copyright 2021 99cloud
+//
+// 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 { observer, inject } from 'mobx-react';
+import { Popover, Row, Col } from 'antd';
+import { SearchOutlined } from '@ant-design/icons';
+import Base from 'containers/List';
+import { CredentialStore } from 'stores/keystone/credential';
+import globalRootStore from 'stores/root';
+import { actionConfigs, detailConfigs } from './actions';
+
+@inject('rootStore')
+@observer
+export default class Credentials extends Base {
+ init() {
+ this.store = new CredentialStore();
+ this.downloadStore = new CredentialStore();
+ }
+
+ get isUserDetail() {
+ const {
+ match: { path },
+ } = this.props;
+ return path.indexOf('user-admin/detail') >= 0;
+ }
+
+ updateFetchParamsByPage = (params) => {
+ if (!this.isUserDetail) {
+ params.id = globalRootStore.user.user.id;
+ }
+ return params;
+ };
+
+ get isFilterByBackend() {
+ return true;
+ }
+
+ get isSortByBackend() {
+ return true;
+ }
+
+ get policy() {
+ return 'identity:get_application_credential';
+ }
+
+ get name() {
+ return t('application credential');
+ }
+
+ get actionConfigs() {
+ if (this.isUserDetail) {
+ return detailConfigs;
+ }
+ return actionConfigs;
+ }
+
+ getColumns = () => {
+ const ret = [
+ {
+ title: t('ID/Name'),
+ dataIndex: 'name',
+ hasNoDetail: true,
+ },
+ {
+ title: t('Project ID/Name'),
+ dataIndex: 'project_name',
+ },
+ {
+ title: t('Description'),
+ dataIndex: 'description',
+ isHideable: true,
+ },
+ {
+ title: t('Expires At'),
+ dataIndex: 'expires_at',
+ valueRender: 'toLocalTime',
+ isHideable: true,
+ },
+ {
+ title: t('Roles'),
+ dataIndex: 'roles',
+ render: (roles) => {
+ const content = (
+
+ {roles.map((i) => (
+
+ {i.name}
+
+ ))}
+
+ );
+ return (
+
+
+
+ );
+ },
+ isHideable: true,
+ },
+ ];
+ return ret;
+ };
+
+ get searchFilters() {
+ const filters = [
+ {
+ label: t('Name'),
+ name: 'name',
+ },
+ ];
+ return filters;
+ }
+}
diff --git a/src/pages/user-center/routes/index.js b/src/pages/user-center/routes/index.js
index ca8ae664..d6b7aa3c 100644
--- a/src/pages/user-center/routes/index.js
+++ b/src/pages/user-center/routes/index.js
@@ -15,6 +15,7 @@
import BaseLayout from 'layouts/Basic';
import E404 from 'pages/base/containers/404';
+import Credentials from '../containers/Credentials';
import UserCenter from '../containers/UserCenter';
const PATH = '/user';
@@ -28,6 +29,11 @@ export default [
component: UserCenter,
exact: true,
},
+ {
+ path: `${PATH}/application-credentials`,
+ component: Credentials,
+ exact: true,
+ },
{ path: '*', component: E404 },
],
},
diff --git a/src/resources/openstack-rc.js b/src/resources/openstack-rc.js
index 83c6874a..ca23c39d 100644
--- a/src/resources/openstack-rc.js
+++ b/src/resources/openstack-rc.js
@@ -13,7 +13,7 @@
// limitations under the License.
// eslint-disable-next-line import/prefer-default-export
-export const getOpenRc = (data) => {
+export const getPwdOpenRc = (data) => {
const {
authUrl,
projectId,
@@ -36,7 +36,7 @@ export const getOpenRc = (data) => {
'# OpenStack API is version 3. For example, your cloud provider may implement\n' +
'# Image API v1.1, Block Storage API v2, and Compute API v2.0. OS_AUTH_URL is\n' +
'# only for the Identity API served through keystone.\n' +
- `export OS_AUTH_URL=${authUrl}\n` +
+ `export OS_AUTH_URL=${authUrl}/v3/\n` +
'\n' +
'# With the addition of Keystone we have standardized on the term **project**\n' +
'# as the entity that owns the resources.\n' +
@@ -70,3 +70,43 @@ export const getOpenRc = (data) => {
return openstackRc;
};
+
+export const getCredentialOpenRc = (data) => {
+ const { authUrl, region } = data;
+ const openstackRc =
+ '#!/usr/bin/env bash\n' +
+ '# To use an OpenStack cloud you need to authenticate against the Identity\n' +
+ '# service named keystone, which returns a **Token** and **Service Catalog**.\n' +
+ '# The catalog contains the endpoints for all services the user/tenant has\n' +
+ '# access to - such as Compute, Image Service, Identity, Object Storage, Block\n' +
+ '# Storage, and Networking (code-named nova, glance, keystone, swift,\n' +
+ '# cinder, and neutron).\n' +
+ '#\n' +
+ '# *NOTE*: Using the 3 *Identity API* does not necessarily mean any other\n' +
+ '# OpenStack API is version 3. For example, your cloud provider may implement\n' +
+ '# Image API v1.1, Block Storage API v2, and Compute API v2.0. OS_AUTH_URL is\n' +
+ '# only for the Identity API served through keystone.\n' +
+ `export OS_AUTH_URL=${authUrl}/v3/\n` +
+ '\n' +
+ '# With Keystone you pass the keystone password.\n' +
+ 'echo "Please enter your OpenStack Credential ID as OS_APPLICATION_CREDENTIAL_ID: "\n' +
+ 'read -sr OS_APPLICATION_CREDENTIAL_ID\n' +
+ 'export OS_APPLICATION_CREDENTIAL_ID=$OS_APPLICATION_CREDENTIAL_ID\n' +
+ 'echo "Please enter your OpenStack Credential Secret as OS_APPLICATION_CREDENTIAL_SECRET: "\n' +
+ 'read -sr OS_APPLICATION_CREDENTIAL_SECRET\n' +
+ 'export OS_APPLICATION_CREDENTIAL_SECRET=$OS_APPLICATION_CREDENTIAL_SECRET\n' +
+ '\n' +
+ "# Don't leave a blank variable, unset it if it was empty\n" +
+ 'if [ -z "$OS_REGION_NAME" ]; then unset OS_REGION_NAME; fi\n' +
+ 'export OS_INTERFACE=public\n' +
+ 'export OS_IDENTITY_API_VERSION=3\n' +
+ 'export OS_AUTH_TYPE=v3applicationcredential\n' +
+ '# If your configuration has multiple regions, we set that information here.\n' +
+ '# OS_REGION_NAME is optional and only valid in certain environments.\n' +
+ `export OS_REGION_NAME=${region}\n` +
+ '\n' +
+ '# If OS_AUTH_URL use private SSL, Please add CACERT file path \n' +
+ '# export OS_CACERT={crtPath}';
+
+ return openstackRc;
+};
diff --git a/src/resources/role.js b/src/resources/role.js
index 0fb867b0..e47a0a5e 100644
--- a/src/resources/role.js
+++ b/src/resources/role.js
@@ -16,8 +16,8 @@ const rolePermission = {
project_admin: t('Project Admin'),
project_reader: t('Project Reader'),
project_member: t('Project Member'),
- system_admin: t('Syetem Admin'),
- system_reader: t('Syetem Reader'),
+ system_admin: t('System Admin'),
+ system_reader: t('System Reader'),
};
export default rolePermission;
diff --git a/src/stores/keystone/credential.js b/src/stores/keystone/credential.js
new file mode 100644
index 00000000..378d3beb
--- /dev/null
+++ b/src/stores/keystone/credential.js
@@ -0,0 +1,66 @@
+// Copyright 2021 99cloud
+//
+// 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 { action } from 'mobx';
+import client from 'client';
+import globalRootStore from 'stores/root';
+import globalUserStore from 'stores/keystone/user';
+import Base from '../base';
+
+export class CredentialStore extends Base {
+ get isSubResource() {
+ return true;
+ }
+
+ get client() {
+ return client.keystone.users.applicationCredentials;
+ }
+
+ get paramsFuncPage() {
+ return (params) => {
+ const { current, withPrice, id, ...rest } = params;
+ return rest;
+ };
+ }
+
+ @action
+ create(data) {
+ const body = {};
+ body[this.responseKey] = data;
+ return this.submitting(
+ this.client.create(globalRootStore.user.user.id, body)
+ );
+ }
+
+ async listDidFetch(items, allProjects) {
+ if (!allProjects) {
+ try {
+ const results = await globalUserStore.getUserProjects();
+ const projectNameMap = new Map();
+ results.forEach((p) => {
+ projectNameMap.set(p.id, p.name);
+ });
+ items.forEach((item) => {
+ item.project_name = projectNameMap.get(item.project_id) || '-';
+ });
+ } catch (e) {
+ return items;
+ }
+ }
+ return items;
+ }
+}
+
+const globalCredentialStore = new CredentialStore();
+export default globalCredentialStore;
diff --git a/src/stores/keystone/user.js b/src/stores/keystone/user.js
index d2584b60..a79a51a3 100644
--- a/src/stores/keystone/user.js
+++ b/src/stores/keystone/user.js
@@ -151,6 +151,7 @@ export class UserStore extends Base {
data: projects,
isLoading: false,
});
+ return projects;
}
@action