diff --git a/ui/src/components/header/ProjectMenu.vue b/ui/src/components/header/ProjectMenu.vue
index f55f9639714..11d72dd77ea 100644
--- a/ui/src/components/header/ProjectMenu.vue
+++ b/ui/src/components/header/ProjectMenu.vue
@@ -91,6 +91,7 @@ export default {
},
changeProject (index) {
const project = this.projects[index]
+ this.$store.dispatch('ProjectView', project.id)
this.$store.dispatch('SetProject', project)
this.$store.dispatch('ToggleTheme', project.id === undefined ? 'light' : 'dark')
this.$message.success(`Switched to "${project.displaytext}"`)
diff --git a/ui/src/components/view/DetailsTab.vue b/ui/src/components/view/DetailsTab.vue
index 12f623dc935..bdb63d907f6 100644
--- a/ui/src/components/view/DetailsTab.vue
+++ b/ui/src/components/view/DetailsTab.vue
@@ -93,6 +93,20 @@ export default {
},
$route () {
this.dedicatedSectionActive = this.dedicatedRoutes.includes(this.$route.meta.name)
+ this.fetchProjectAdmins()
+ }
+ },
+ methods: {
+ fetchProjectAdmins () {
+ if (!this.resource.owner) {
+ return false
+ }
+ var owners = this.resource.owner
+ var projectAdmins = []
+ for (var owner of owners) {
+ projectAdmins.push(Object.keys(owner).includes('user') ? owner.account + '(' + owner.user + ')' : owner.account)
+ }
+ this.resource.account = projectAdmins.join()
}
}
}
diff --git a/ui/src/components/view/InfoCard.vue b/ui/src/components/view/InfoCard.vue
index 8e9a0201454..175189eb2bc 100644
--- a/ui/src/components/view/InfoCard.vue
+++ b/ui/src/components/view/InfoCard.vue
@@ -459,6 +459,21 @@
{{ resource.zone || resource.zonename || resource.zoneid }}
+
+
{{ $t('label.owners') }}
+
+
+
+
+
+ {{ item.account + '(' + item.user + ')' }}
+ {{ item.account }}
+
+ {{ item.user ? item.account + '(' + item.user + ')' : item.account }}
+
+
+
+
{{ $t('label.account') }}
diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue
index 87aae7261e4..5ab0cf69a11 100644
--- a/ui/src/components/view/ListView.vue
+++ b/ui/src/components/view/ListView.vue
@@ -169,6 +169,17 @@
{{ text }}
+
+
+
+
+ {{ item.account + '(' + item.user + ')' }}
+ {{ item.account }}
+
+ {{ item.user ? item.account + '(' + item.user + ')' : item.account }}
+
+
+
import('@/components/view/DetailsTab.vue')
+ component: () => import('@/views/project/ProjectDetailsTab.vue')
+ },
+ {
+ name: 'accounts',
+ component: () => import('@/views/project/AccountsTab.vue'),
+ show: (record, route, user) => { return ['Admin', 'DomainAdmin'].includes(user.roletype) || record.isCurrentUserProjectAdmin }
+ },
+ {
+ name: 'project.roles',
+ component: () => import('@/views/project/iam/ProjectRoleTab.vue'),
+ show: (record, route, user) => {
+ return (['Admin', 'DomainAdmin'].includes(user.roletype) || record.isCurrentUserProjectAdmin) &&
+ 'listProjectRoles' in store.getters.apis
+ }
},
{
name: 'resources',
@@ -38,11 +52,6 @@ export default {
name: 'limits',
show: (record, route, user) => { return ['Admin'].includes(user.roletype) },
component: () => import('@/components/view/ResourceLimitTab.vue')
- },
- {
- name: 'accounts',
- show: (record, route, user) => { return record.account === user.account || ['Admin', 'DomainAdmin'].includes(user.roletype) },
- component: () => import('@/views/project/AccountsTab.vue')
}
],
actions: [
@@ -84,7 +93,7 @@ export default {
dataView: true,
args: ['displaytext'],
show: (record, store) => {
- return record.account === store.userInfo.account || ['Admin', 'DomainAdmin'].includes(store.userInfo.roletype)
+ return (['Admin', 'DomainAdmin'].includes(store.userInfo.roletype)) || record.isCurrentUserProjectAdmin
}
},
{
@@ -94,7 +103,7 @@ export default {
message: 'message.activate.project',
dataView: true,
show: (record, store) => {
- return (record.account === store.userInfo.account || ['Admin', 'DomainAdmin'].includes(store.userInfo.roletype)) && record.state === 'Suspended'
+ return ((['Admin', 'DomainAdmin'].includes(store.userInfo.roletype)) || record.isCurrentUserProjectAdmin) && record.state === 'Suspended'
}
},
{
@@ -105,7 +114,8 @@ export default {
docHelp: 'adminguide/projects.html#sending-project-membership-invitations',
dataView: true,
show: (record, store) => {
- return (record.account === store.userInfo.account || ['Admin', 'DomainAdmin'].includes(store.userInfo.roletype)) && record.state !== 'Suspended'
+ return ((['Admin', 'DomainAdmin'].includes(store.userInfo.roletype)) ||
+ record.isCurrentUserProjectAdmin) && record.state !== 'Suspended'
}
},
{
@@ -114,13 +124,11 @@ export default {
label: 'label.action.project.add.account',
docHelp: 'adminguide/projects.html#adding-project-members-from-the-ui',
dataView: true,
- args: ['projectid', 'account', 'email'],
- show: (record, store) => { return record.account === store.userInfo.account || ['Admin', 'DomainAdmin'].includes(store.userInfo.roletype) },
- mapping: {
- projectid: {
- value: (record) => { return record.id }
- }
- }
+ popup: true,
+ show: (record, store) => {
+ return (['Admin', 'DomainAdmin'].includes(store.userInfo.roletype)) || record.isCurrentUserProjectAdmin
+ },
+ component: () => import('@/views/project/AddAccountOrUserToProject.vue')
},
{
api: 'deleteProject',
@@ -129,7 +137,9 @@ export default {
message: 'message.delete.project',
docHelp: 'adminguide/projects.html#suspending-or-deleting-a-project',
dataView: true,
- show: (record, store) => { return record.account === store.userInfo.account || ['Admin', 'DomainAdmin'].includes(store.userInfo.roletype) }
+ show: (record, store) => {
+ return (['Admin', 'DomainAdmin'].includes(store.userInfo.roletype)) || record.isCurrentUserProjectAdmin
+ }
}
]
}
diff --git a/ui/src/locales/en.json b/ui/src/locales/en.json
index 4d92e475e54..d3078e5aceb 100644
--- a/ui/src/locales/en.json
+++ b/ui/src/locales/en.json
@@ -213,6 +213,7 @@
"label.action.migrate.systemvm": "Migrate System VM",
"label.action.migrate.systemvm.processing": "Migrating System VM....",
"label.action.project.add.account": "Add Account to Project",
+"label.action.project.add.user": "Add User to Project",
"label.action.reboot.instance": "Reboot Instance",
"label.action.reboot.instance.processing": "Rebooting Instance....",
"label.action.reboot.router": "Reboot Router",
@@ -339,6 +340,7 @@
"label.add.portable.ip.range": "Add Portable IP Range",
"label.add.primary.storage": "Add Primary Storage",
"label.add.private.gateway": "Add Private Gateway",
+"label.add.project.role": "Add Project Role",
"label.add.region": "Add Region",
"label.add.resources": "Add Resources",
"label.add.role": "Add Role",
@@ -608,6 +610,7 @@
"label.create.nfs.secondary.staging.storage": "Create NFS Secondary Staging Store",
"label.create.nfs.secondary.staging.store": "Create NFS secondary staging store",
"label.create.project": "Create project",
+"label.create.project.role": "Create Project Role",
"label.create.user": "Create user",
"label.create.site.vpn.connection": "Create Site-to-Site VPN Connection",
"label.create.site.vpn.gateway": "Create Site-to-Site VPN Gateway",
@@ -675,6 +678,7 @@
"label.delete.pa": "Delete Palo Alto",
"label.delete.portable.ip.range": "Delete Portable IP Range",
"label.delete.project": "Delete project",
+"label.delete.project.role": "Delete Project Role",
"label.delete.role": "Delete Role",
"label.delete.rule": "Delete rule",
"label.delete.secondary.staging.store": "Delete Secondary Staging Store",
@@ -694,6 +698,8 @@
"label.deleting.failed": "Deleting Failed",
"label.deleting.iso": "Deleting ISO",
"label.deleting.processing": "Deleting....",
+"label.demote.project.owner": "Demote account to Regular role",
+"label.demote.project.owner.user": "Demote user to Regular role",
"label.deleting.template": "Deleting template",
"label.deny": "Deny",
"label.deploymentplanner": "Deployment planner",
@@ -793,6 +799,7 @@
"label.edit.lb.rule": "Edit LB rule",
"label.edit.network.details": "Edit network details",
"label.edit.project.details": "Edit project details",
+"label.edit.project.role": "Edit Project Role",
"label.edit.region": "Edit Region",
"label.edit.role": "Edit Role",
"label.edit.rule": "Edit rule",
@@ -1239,6 +1246,7 @@
"label.macaddresschanges": "MAC Address Changes",
"label.macos": "MacOS",
"label.make.project.owner": "Make account project owner",
+"label.make.user.project.owner": "Make user project owner",
"label.makeredundant": "Make redundant",
"label.manage": "Manage",
"label.manage.resources": "Manage Resources",
@@ -1325,6 +1333,7 @@
"label.metrics.network.usage": "Network Usage",
"label.metrics.network.write": "Write",
"label.metrics.num.cpu.cores": "Cores",
+
"label.migrate.instance.to": "Migrate instance to",
"label.migrate.instance.to.host": "Migrate instance to another host",
"label.migrate.instance.to.ps": "Migrate instance to another primary storage",
@@ -1503,6 +1512,7 @@
"label.owned.public.ips": "Owned Public IP Addresses",
"label.owner.account": "Owner Account",
"label.owner.domain": "Owner Domain",
+"label.owners": "Owners",
"label.pa": "Palo Alto",
"label.page": "page",
"label.palo.alto.details": "Palo Alto details",
@@ -1593,6 +1603,10 @@
"label.project.invitation": "Project Invitations",
"label.project.invite": "Invite to project",
"label.project.name": "Project name",
+"label.project.owner": "Project Owner(s)",
+"label.project.role": "Project Role",
+"label.project.roles": "Project Roles",
+"label.project.role.permissions": "Project Role Permissions",
"label.project.view": "Project View",
"label.projectaccountname": "Project Account Name",
"label.projectid": "Project ID",
@@ -1712,6 +1726,8 @@
"label.remove.network.offering": "Remove network offering",
"label.remove.pf": "Remove port forwarding rule",
"label.remove.project.account": "Remove account from project",
+"label.remove.project.role": "Remove project role",
+"label.remove.project.user": "Remove user from project",
"label.remove.region": "Remove Region",
"label.remove.rule": "Remove rule",
"label.remove.ssh.key.pair": "Remove SSH Key Pair",
@@ -2094,6 +2110,7 @@
"label.untagged": "Untagged",
"label.update.instance.group": "Update Instance Group",
"label.update.project.resources": "Update project resources",
+"label.update.project.role": "Update project role",
"label.update.ssl": " SSL Certificate",
"label.update.ssl.cert": " SSL Certificate",
"label.update.to": "updated to",
@@ -2120,6 +2137,7 @@
"label.usehttps": "Use HTTPS",
"label.usenewdiskoffering": "Replace disk offering?",
"label.user": "User",
+"label.user.as.admin": "Make User the Project Admin",
"label.user.conflict": "Conflict",
"label.user.details": "User details",
"label.user.source": "source",
@@ -2440,6 +2458,7 @@
"message.add.tag.for.networkacl": "Add tag for NetworkACL",
"message.add.tag.processing": "Adding new tag...",
"message.add.template": "Please enter the following data to create your new template",
+"message.add.user.to.project": "This form is to enable adding specific users of an account to a project.
Furthermore, a ProjectRole may be added to the added user/account to allow/disallow API access at project level.
We can also specify the role with which the user should be added to a project - Admin/Regular; if not specified, it defaults to 'Regular'",
"message.add.volume": "Please fill in the following data to add a new volume.",
"message.add.vpn.connection.failed": "Adding VPN Connection failed",
"message.add.vpn.connection.processing": "Adding VPN Connection...",
@@ -3183,6 +3202,9 @@
"message.zone.step.3.desc": "Please enter the following info to add a new pod",
"message.zonewizard.enable.local.storage": "WARNING: If you enable local storage for this zone, you must do the following, depending on where you would like your system VMs to launch:
1. If system VMs need to be launched in shared primary storage, shared primary storage needs to be added to the zone after creation. You must also start the zone in a disabled state.
2. If system VMs need to be launched in local primary storage, system.vm.use.local.storage needs to be set to true before you enable the zone.
Would you like to continue?",
"messgae.validate.min": "Please enter a value greater than or equal to {0}.",
+"migrate.from": "Migrate From",
+"migrate.to": "Migrate To",
+"migrationPolicy": "Migration Policy",
"network.rate": "Network Rate",
"router.health.checks": "Health Check",
"side.by.side": "Side by Side",
diff --git a/ui/src/store/modules/user.js b/ui/src/store/modules/user.js
index c39e2f4dbb0..76271ff60dc 100644
--- a/ui/src/store/modules/user.js
+++ b/ui/src/store/modules/user.js
@@ -91,7 +91,6 @@ const user = {
return new Promise((resolve, reject) => {
login(userInfo).then(response => {
const result = response.loginresponse || {}
-
Cookies.set('account', result.account, { expires: 1 })
Cookies.set('domainid', result.domainid, { expires: 1 })
Cookies.set('role', result.type, { expires: 1 })
@@ -100,7 +99,6 @@ const user = {
Cookies.set('userfullname', result.firstname + ' ' + result.lastname, { expires: 1 })
Cookies.set('userid', result.userid, { expires: 1 })
Cookies.set('username', result.username, { expires: 1 })
-
Vue.ls.set(ACCESS_TOKEN, result.sessionkey, 24 * 60 * 60 * 1000)
commit('SET_TOKEN', result.sessionkey)
@@ -174,7 +172,7 @@ const user = {
})
}
- api('listUsers', { username: Cookies.get('username'), listall: true }).then(response => {
+ api('listUsers', { username: Cookies.get('username') }).then(response => {
const result = response.listusersresponse.user[0]
commit('SET_INFO', result)
commit('SET_NAME', result.firstname + ' ' + result.lastname)
@@ -248,6 +246,29 @@ const user = {
var jobsArray = Vue.ls.get(ASYNC_JOB_IDS, [])
jobsArray.push(jobJson)
commit('SET_ASYNC_JOB_IDS', jobsArray)
+ },
+ ProjectView ({ commit }, projectid) {
+ return new Promise((resolve, reject) => {
+ api('listApis', { projectid: projectid }).then(response => {
+ const apis = {}
+ const apiList = response.listapisresponse.api
+ for (var idx = 0; idx < apiList.length; idx++) {
+ const api = apiList[idx]
+ const apiName = api.name
+ apis[apiName] = {
+ params: api.params,
+ response: api.response
+ }
+ }
+ commit('SET_APIS', apis)
+ resolve(apis)
+ store.dispatch('GenerateRoutes', { apis }).then(() => {
+ router.addRoutes(store.getters.addRouters)
+ })
+ }).catch(error => {
+ reject(error)
+ })
+ })
}
}
}
diff --git a/ui/src/views/AutogenView.vue b/ui/src/views/AutogenView.vue
index c60706ce5db..9277f54bc1f 100644
--- a/ui/src/views/AutogenView.vue
+++ b/ui/src/views/AutogenView.vue
@@ -576,6 +576,18 @@ export default {
this.items = []
}
+ if (['listTemplates', 'listIsos'].includes(this.apiName) && this.items.length > 1) {
+ this.items = [...new Map(this.items.map(x => [x.id, x])).values()]
+ }
+
+ if (this.apiName === 'listProjects' && this.items.length > 0) {
+ this.columns.map(col => {
+ if (col.title === 'Account') {
+ col.title = this.$t('label.project.owner')
+ }
+ })
+ }
+
for (let idx = 0; idx < this.items.length; idx++) {
this.items[idx].key = idx
for (const key in customRender) {
@@ -818,7 +830,6 @@ export default {
execSubmit (e) {
e.preventDefault()
this.form.validateFields((err, values) => {
- console.log(values)
if (!err) {
const params = {}
if ('id' in this.resource && this.currentAction.params.map(i => { return i.name }).includes('id')) {
@@ -868,10 +879,6 @@ export default {
}
}
- console.log(this.currentAction)
- console.log(this.resource)
- console.log(params)
-
const resourceName = params.displayname || params.displaytext || params.name || params.hostname || params.username || params.ipaddress || params.virtualmachinename || this.resource.name
var hasJobId = false
diff --git a/ui/src/views/project/AccountsTab.vue b/ui/src/views/project/AccountsTab.vue
index 53b28d92a56..8c986c5f393 100644
--- a/ui/src/views/project/AccountsTab.vue
+++ b/ui/src/views/project/AccountsTab.vue
@@ -21,29 +21,47 @@
-
-
-
- {{ $t('label.make.project.owner') }}
-
+ :rowKey="record => record.userid ? record.userid : (record.accountid || record.account)">
+
+ {{ record.username }}
+
+
+ {{ getProjectRole(record) }}
+
+
+
+ @click="promoteAccount(record)" />
-
-
- {{ $t('label.remove.project.account') }}
-
+
+
+
+
{
- const listProjectAccount = json.listprojectaccountsresponse.projectaccount
- const itemCount = json.listprojectaccountsresponse.count
-
- if (!listProjectAccount || listProjectAccount.length === 0) {
- this.dataSource = []
- return
- }
-
- this.itemCount = itemCount
- this.dataSource = listProjectAccount
- }).catch(error => {
- this.$notifyError(error)
- }).finally(() => {
- this.loading = false
- })
+ this.updateProjectApi = this.$store.getters.apis.updateProject
+ this.fetchUsers()
+ this.fetchProjectAccounts(params)
+ if (this.isProjectRolesSupported()) {
+ this.fetchProjectRoles()
+ }
},
changePage (page, pageSize) {
this.page = page
@@ -166,29 +189,126 @@ export default {
this.fetchData()
},
changePageSize (currentPage, pageSize) {
- this.page = currentPage
+ this.page = 0
this.pageSize = pageSize
this.fetchData()
},
+ isLoggedInUserProjectAdmin (user) {
+ if (['Admin', 'DomainAdmin'].includes(this.$store.getters.userInfo.roletype)) {
+ return true
+ }
+ // If I'm the logged in user Or if I'm the logged in account And I'm the owner
+ if (((user.userid && user.userid === this.$store.getters.userInfo.id) ||
+ user.account === this.$store.getters.userInfo.account) &&
+ user.role === this.owner) {
+ return true
+ }
+ return false
+ },
+ isProjectRolesSupported () {
+ return ('listProjectRoles' in this.$store.getters.apis)
+ },
+ getProjectRole (record) {
+ const projectRole = this.projectRoles.filter(role => role.id === record.projectroleid)
+ return projectRole[0].name || projectRole[0].id || null
+ },
+ fetchUsers () {
+ this.loading.user = true
+ api('listUsers', { listall: true }).then(response => {
+ this.users = response.listusersresponse.user || []
+ }).catch(error => {
+ this.$notifyError(error)
+ }).finally(() => {
+ this.loading.user = false
+ })
+ },
+ fetchProjectRoles () {
+ this.loading.roles = true
+ api('listProjectRoles', { projectId: this.resource.id }).then(response => {
+ this.projectRoles = response.listprojectrolesresponse.projectrole || []
+ }).catch(error => {
+ this.$notifyError(error)
+ }).finally(() => {
+ this.loading.roles = false
+ })
+ },
+ fetchProjectAccounts (params) {
+ this.loading.projectAccount = true
+ api('listProjectAccounts', params).then(json => {
+ const listProjectAccount = json.listprojectaccountsresponse.projectaccount
+ const itemCount = json.listprojectaccountsresponse.count
+ if (!listProjectAccount || listProjectAccount.length === 0) {
+ this.dataSource = []
+ return
+ }
+ for (const projectAccount of listProjectAccount) {
+ this.imProjectAdmin = this.isLoggedInUserProjectAdmin(projectAccount)
+ if (this.imProjectAdmin) {
+ break
+ }
+ }
+
+ this.itemCount = itemCount
+ this.dataSource = listProjectAccount
+ }).catch(error => {
+ this.$notifyError(error)
+ }).finally(() => {
+ this.loading.projectAccount = false
+ })
+ },
onMakeProjectOwner (record) {
const title = this.$t('label.make.project.owner')
const loading = this.$message.loading(title + `${this.$t('label.in.progress.for')} ` + record.account, 0)
const params = {}
-
params.id = this.resource.id
params.account = record.account
+ this.updateProject(record, params, title, loading)
+ },
+ promoteAccount (record) {
+ var title = this.$t('label.make.project.owner')
+ const loading = this.$message.loading(title + `${this.$t('label.in.progress.for')} ` + record.account, 0)
+ const params = {}
+ params.id = this.resource.id
+ if (record.userid) {
+ params.userid = record.userid
+ // params.accountid = (record.user && record.user[0].accountid) || record.accountid
+ title = this.$t('label.make.user.project.owner')
+ } else {
+ params.account = record.account
+ }
+ params.roletype = this.owner
+ params.swapowner = false
+ this.updateProject(record, params, title, loading)
+ },
+ demoteAccount (record) {
+ var title = this.$t('label.demote.project.owner')
+ const loading = this.$message.loading(title + `${this.$t('label.in.progress.for')} ` + record.account, 0)
+ const params = {}
+ if (record.userid) {
+ params.userid = record.userid
+ // params.accountid = (record.user && record.user[0].accountid) || record.accountid
+ title = this.$t('label.demote.project.owner.user')
+ } else {
+ params.account = record.account
+ }
+
+ params.id = this.resource.id
+ params.roletype = 'Regular'
+ params.swapowner = false
+ this.updateProject(record, params, title, loading)
+ },
+ updateProject (record, params, title, loading) {
api('updateProject', params).then(json => {
const hasJobId = this.checkForAddAsyncJob(json, title, record.account)
-
if (hasJobId) {
this.fetchData()
}
}).catch(error => {
- // show error
this.$notifyError(error)
}).finally(() => {
setTimeout(loading, 1000)
+ this.parentFetchData()
})
},
onShowConfirmDelete (record) {
@@ -209,18 +329,22 @@ export default {
const title = this.$t('label.remove.project.account')
const loading = this.$message.loading(title + `${this.$t('label.in.progress.for')} ` + record.account, 0)
const params = {}
-
- params.account = record.account
params.projectid = this.resource.id
-
- api('deleteAccountFromProject', params).then(json => {
+ if (record.userid) {
+ params.userid = record.userid
+ this.deleteOperation('deleteUserFromProject', params, record, title, loading)
+ } else {
+ params.account = record.account
+ this.deleteOperation('deleteAccountFromProject', params, record, title, loading)
+ }
+ },
+ deleteOperation (apiName, params, record, title, loading) {
+ api(apiName, params).then(json => {
const hasJobId = this.checkForAddAsyncJob(json, title, record.account)
-
if (hasJobId) {
this.fetchData()
}
}).catch(error => {
- // show error
this.$notifyError(error)
}).finally(() => {
setTimeout(loading, 1000)
diff --git a/ui/src/views/project/AddAccountOrUserToProject.vue b/ui/src/views/project/AddAccountOrUserToProject.vue
new file mode 100644
index 00000000000..0275ed7521f
--- /dev/null
+++ b/ui/src/views/project/AddAccountOrUserToProject.vue
@@ -0,0 +1,332 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+
+
+
+
+
+
+ {{ $t('label.account') }}
+
+
+
+
+
+
+
+
+ {{ $t('label.email') }}
+
+
+
+
+
+
+
+
+ {{ $t('label.project.role') }}
+
+
+
+
+
+
+ {{ role.name }}
+
+
+
+
+
+ {{ $t('label.roletype') }}
+
+
+
+
+
+ Admin
+ Regular
+
+
+
+
+
+
+
+
+
+
+ {{ $t('label.user') }}
+
+
+
+
+
+
+
+
+ {{ $t('label.email') }}
+
+
+
+
+
+
+
+
+ {{ $t('label.project.role') }}
+
+
+
+
+
+
+ {{ role.name }}
+
+
+
+
+
+ {{ $t('label.roletype') }}
+
+
+
+
+
+ Admin
+ Regular
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/views/project/InvitationsTemplate.vue b/ui/src/views/project/InvitationsTemplate.vue
index 3b95a847568..719f4a08b13 100644
--- a/ui/src/views/project/InvitationsTemplate.vue
+++ b/ui/src/views/project/InvitationsTemplate.vue
@@ -115,6 +115,11 @@ export default {
dataIndex: 'project',
scopedSlots: { customRender: 'project' }
},
+ {
+ title: this.$t('label.account'),
+ dataIndex: 'account',
+ scopedSlots: { customRender: 'account' }
+ },
{
title: this.$t('label.domain'),
dataIndex: 'domain',
@@ -152,6 +157,18 @@ export default {
this.page = 1
this.pageSize = 10
this.itemCount = 0
+ this.apiConfig = this.$store.getters.apis.listProjectInvitations || {}
+ this.apiParams = {}
+ this.apiConfig.params.forEach(param => {
+ this.apiParams[param.name] = param
+ })
+ if (this.apiParams.userid) {
+ this.columns.splice(2, 0, {
+ title: this.$t('label.user'),
+ dataIndex: 'userid',
+ scopedSlots: { customRender: 'user' }
+ })
+ }
},
mounted () {
this.fetchData()
@@ -225,7 +242,11 @@ export default {
const params = {}
params.projectid = record.projectid
- params.account = record.account
+ if (record.userid && record.userid !== null) {
+ params.userid = record.userid
+ } else {
+ params.account = record.account
+ }
params.domainid = record.domainid
params.accept = state
diff --git a/ui/src/views/project/ProjectDetailsTab.vue b/ui/src/views/project/ProjectDetailsTab.vue
new file mode 100644
index 00000000000..ebffb598194
--- /dev/null
+++ b/ui/src/views/project/ProjectDetailsTab.vue
@@ -0,0 +1,57 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+
+
+
diff --git a/ui/src/views/project/iam/ProjectRolePermissionTab.vue b/ui/src/views/project/iam/ProjectRolePermissionTab.vue
new file mode 100644
index 00000000000..b9861085273
--- /dev/null
+++ b/ui/src/views/project/iam/ProjectRolePermissionTab.vue
@@ -0,0 +1,442 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+
+
+
+
+
+
+
+
+
+
newRule = val"
+ placeholder="Rule"
+ :class="{'rule-dropdown-error' : newRuleSelectError}" />
+
+
+
+
+
+
+ Save new Rule
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ record.rule }}
+
+
+
+
+ {{ record.description }}
+
+
+ No description entered.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/views/project/iam/ProjectRoleTab.vue b/ui/src/views/project/iam/ProjectRoleTab.vue
new file mode 100644
index 00000000000..75a3124a8c9
--- /dev/null
+++ b/ui/src/views/project/iam/ProjectRoleTab.vue
@@ -0,0 +1,308 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+
+
+ {{ $t('label.create.project.role') }}
+
+
+
+
+
+
+
+ {{ record }}
+
+ {{ record }}
+
+
+
+
+ {{ $t('label.update.project.role') }}
+
+
+
+
+
+ {{ $t('label.remove.project.role') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('label.update.project.role') }}
+
+
+
+
+
+ {{ $t('label.remove.project.role') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+