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