feat: Add application credential

add application credential

Change-Id: Id07525e3bc8709aa4226dabd8084d9a09821c397
This commit is contained in:
zhuyue 2021-09-08 15:13:46 +08:00
parent 360f387aa8
commit a6f387d67e
16 changed files with 523 additions and 110 deletions

View File

@ -105,6 +105,11 @@ class KeystoneClient extends Base {
{
key: 'groups',
},
{
name: 'applicationCredentials',
key: 'application_credentials',
responseKey: 'application_credential',
},
],
extendOperations: [
{

View File

@ -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: (
<Paragraph copyable={{ text: this.openRc }} className={styles.token}>
<pre>{this.openRc}</pre>
</Paragraph>
),
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));
});
};
}

View File

@ -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 (
<>
<div>
<Link to={url}>{projectId}</Link>
{globalRootStore.hasAdminRole ? (
<Link to={url}>{projectId}</Link>
) : (
projectId
)}
</div>
<div>{value || '-'}</div>
</>
@ -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 (
<div>
<div>{idValue}</div>
<div>{nameValue}</div>
</div>
);
}
if (!url && !hasNoDetail) {
return nameValue;
}
return (

View File

@ -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: <UserOutlined />,
children: [],
hasChildren: false,
},
];
return menu;
};

View File

@ -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",

View File

@ -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网关正在使用中",

View File

@ -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;
}

View File

@ -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,
},
];
}
}

View File

@ -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);
};
}

View File

@ -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: [],
};

View File

@ -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 = (
<Row gutter={[8]} style={{ maxWidth: 200 }}>
{roles.map((i) => (
<Col span={24} key={i.id}>
{i.name}
</Col>
))}
</Row>
);
return (
<Popover content={content}>
<SearchOutlined />
</Popover>
);
},
isHideable: true,
},
];
return ret;
};
get searchFilters() {
const filters = [
{
label: t('Name'),
name: 'name',
},
];
return filters;
}
}

View File

@ -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 },
],
},

View File

@ -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;
};

View File

@ -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;

View File

@ -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;

View File

@ -151,6 +151,7 @@ export class UserStore extends Base {
data: projects,
isLoading: false,
});
return projects;
}
@action