mirror of https://github.com/rancher/dashboard.git
Migrating cluster membership from ember to vue (clusterRoleTemplateBindings)
rancher/dashboard#2501
This commit is contained in:
parent
1b6da12755
commit
bbfc4070aa
|
|
@ -1686,6 +1686,33 @@ login:
|
|||
loginWithLocal: Log in with Local User
|
||||
useProvider: Use a {provider} user
|
||||
|
||||
members:
|
||||
clusterMembers: Cluster Members
|
||||
createActionLabel: Add
|
||||
clusterPermissions:
|
||||
noDescription: User created - no description
|
||||
label: Cluster Permissions
|
||||
description: Controls what access users have to the Cluster
|
||||
createProjects: Create Projects
|
||||
manageClusterBackups: Manage Cluster Backups
|
||||
manageClusterCatalogs: Manage Cluster Catalogs
|
||||
manageClusterMembers: Manage Cluster Members
|
||||
manageNodes: Manage Nodes
|
||||
manageStorage: Manage Storage
|
||||
viewAllProjects: View All Projects
|
||||
viewClusterCatalogs: View Cluster Catalogs
|
||||
viewClusterMembers: View Cluster Members
|
||||
viewNodes: View Nodes
|
||||
owner:
|
||||
label: Owner
|
||||
description: Owners have full control over the Cluster and all resources inside it.
|
||||
member:
|
||||
label: Member
|
||||
description: Members can manage the resources inside the Cluster but not change the Cluster itself.
|
||||
custom:
|
||||
label: Custom
|
||||
description: Choose individual roles for this user.
|
||||
|
||||
monitoring:
|
||||
accessModes:
|
||||
many: ReadWriteMany
|
||||
|
|
@ -2500,6 +2527,8 @@ rbac:
|
|||
fleetworkspace-admin: Admin
|
||||
fleetworkspace-member: Member
|
||||
fleetworkspace-readonly: Read-Only
|
||||
members:
|
||||
label: Members
|
||||
roletemplate:
|
||||
label: Roles
|
||||
newUserDefault:
|
||||
|
|
@ -4129,6 +4158,11 @@ typeLabel:
|
|||
one { Cluster Group }
|
||||
other {Cluster Groups }
|
||||
}
|
||||
management.cattle.io.clusterroletemplatebinding: |-
|
||||
{count, plural,
|
||||
one { Cluster Member }
|
||||
other { Cluster Members }
|
||||
}
|
||||
fleet.cattle.io.gitrepo: |-
|
||||
{count, plural,
|
||||
one { Git Repo }
|
||||
|
|
@ -4215,6 +4249,11 @@ typeLabel:
|
|||
one { User }
|
||||
other { Users }
|
||||
}
|
||||
namespace: |-
|
||||
{count, plural,
|
||||
one { Namespace }
|
||||
other { Namespaces }
|
||||
}
|
||||
group.principal: |-
|
||||
{count, plural,
|
||||
one { Group }
|
||||
|
|
|
|||
|
|
@ -317,7 +317,8 @@ export default {
|
|||
{{ parent.displayName }}:
|
||||
</nuxt-link>
|
||||
<span v-else>{{ parent.displayName }}:</span>
|
||||
<t :k="'resourceDetail.header.' + realMode" :subtype="resourceSubtype" :name="value.nameDisplay" />
|
||||
<span v-if="value.detailPageHeaderActionOverride && value.detailPageHeaderActionOverride(realMode)">{{ value.detailPageHeaderActionOverride(realMode) }}</span>
|
||||
<t v-else :k="'resourceDetail.header.' + realMode" :subtype="resourceSubtype" :name="value.nameDisplay" />
|
||||
<BadgeState v-if="!isCreate && parent.showState" class="masthead-state" :value="value" />
|
||||
</h1>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -34,7 +34,17 @@ export default {
|
|||
mode: {
|
||||
type: String,
|
||||
default: 'edit',
|
||||
}
|
||||
},
|
||||
|
||||
descriptionKey: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
|
||||
description: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
@ -102,17 +112,27 @@ export default {
|
|||
:aria-checked="isChecked"
|
||||
role="radio"
|
||||
/>
|
||||
<label
|
||||
v-if="label"
|
||||
:class="[ muteLabel ? 'text-muted' : '', 'radio-label']"
|
||||
v-html="label"
|
||||
>
|
||||
<slot name="label">{{ label }}</slot>
|
||||
</label>
|
||||
<div class="labeling">
|
||||
<label
|
||||
v-if="label"
|
||||
:class="[ muteLabel ? 'text-muted' : '', 'radio-label', 'm-0']"
|
||||
v-html="label"
|
||||
>
|
||||
<slot name="label">{{ label }}</slot>
|
||||
</label>
|
||||
<div v-if="descriptionKey || description" class="radio-button-outer-container-description">
|
||||
<t v-if="descriptionKey" :k="descriptionKey" />
|
||||
<template v-else-if="description">
|
||||
{{ description }}
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<style lang='scss'>
|
||||
$fontColor: var(--input-label);
|
||||
|
||||
.radio-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -131,7 +151,7 @@ export default {
|
|||
.radio-container {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
|
@ -142,10 +162,6 @@ export default {
|
|||
cursor: not-allowed
|
||||
}
|
||||
|
||||
.radio-label {
|
||||
margin: 3px 10px 0px 5px;
|
||||
}
|
||||
|
||||
.radio-custom {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
|
|
@ -155,6 +171,7 @@ export default {
|
|||
border-radius: 50%;
|
||||
transition: all 0.3s ease-out;
|
||||
border: 1.5px solid var(--border);
|
||||
margin-top: 5px;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
|
@ -186,6 +203,19 @@ export default {
|
|||
background-color: var(--disabled-bg);
|
||||
opacity: .25;
|
||||
}
|
||||
|
||||
.radio-button-outer-container-description {
|
||||
color: $fontColor;
|
||||
font-size: 11px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.labeling {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
|
||||
margin: 3px 10px 0px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@ export default {
|
|||
:name="name"
|
||||
:value="value"
|
||||
:label="option.label"
|
||||
:description="option.description"
|
||||
:val="option.value"
|
||||
:disabled="isDisabled"
|
||||
:mode="mode"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
WORKLOAD, WORKLOAD_TYPES, SERVICE, HPA, NETWORK_POLICY, PV, PVC, STORAGE_CLASS, POD,
|
||||
RBAC,
|
||||
MANAGEMENT,
|
||||
NAMESPACE,
|
||||
NORMAN,
|
||||
} from '@/config/types';
|
||||
|
||||
|
|
@ -27,6 +28,7 @@ export function init(store) {
|
|||
basicType,
|
||||
ignoreType,
|
||||
mapGroup,
|
||||
mapType,
|
||||
weightGroup,
|
||||
weightType,
|
||||
headers,
|
||||
|
|
@ -40,7 +42,7 @@ export function init(store) {
|
|||
weight: 3,
|
||||
showNamespaceFilter: true,
|
||||
icon: 'compass',
|
||||
typeStoreMap: { [MANAGEMENT.PROJECT]: 'management' }
|
||||
typeStoreMap: { [MANAGEMENT.PROJECT]: 'management', [MANAGEMENT.CLUSTER_ROLE_TEMPLATE_BINDING]: 'management' }
|
||||
});
|
||||
|
||||
basicType(['cluster-dashboard', 'cluster-tools']);
|
||||
|
|
@ -77,6 +79,7 @@ export function init(store) {
|
|||
RBAC.CLUSTER_ROLE,
|
||||
RBAC.ROLE_BINDING,
|
||||
RBAC.CLUSTER_ROLE_BINDING,
|
||||
'cluster-members'
|
||||
], 'rbac');
|
||||
|
||||
weightGroup('cluster', 99, true);
|
||||
|
|
@ -124,9 +127,12 @@ export function init(store) {
|
|||
mapGroup(/^(.*\.)?cluster\.x-k8s\.io$/, 'Cluster Provisioning');
|
||||
mapGroup(/^(aks|eks|gke|rke|rke-machine-config|provisioning)\.cattle\.io$/, 'Cluster Provisioning');
|
||||
|
||||
mapType(MANAGEMENT.CLUSTER_ROLE_TEMPLATE_BINDING, store.getters['i18n/t'](`typeLabel.${ MANAGEMENT.CLUSTER_ROLE_TEMPLATE_BINDING }`, { count: 2 }));
|
||||
|
||||
configureType(NODE, { isCreatable: false, isEditable: false });
|
||||
configureType(WORKLOAD_TYPES.JOB, { isEditable: false, match: WORKLOAD_TYPES.JOB });
|
||||
configureType(PVC, { isEditable: false });
|
||||
configureType(MANAGEMENT.CLUSTER_ROLE_TEMPLATE_BINDING, { isEditable: false });
|
||||
|
||||
configureType('workload', {
|
||||
displayName: 'Workload',
|
||||
|
|
@ -200,7 +206,7 @@ export function init(store) {
|
|||
]);
|
||||
|
||||
virtualType({
|
||||
label: 'Cluster Dashboard',
|
||||
label: store.getters['i18n/t']('clusterIndexPage.header'),
|
||||
group: 'Root',
|
||||
namespaced: false,
|
||||
name: 'cluster-dashboard',
|
||||
|
|
@ -211,7 +217,18 @@ export function init(store) {
|
|||
});
|
||||
|
||||
virtualType({
|
||||
label: 'Overview',
|
||||
label: store.getters['i18n/t']('members.clusterMembers'),
|
||||
group: 'rbac',
|
||||
namespaced: false,
|
||||
name: 'cluster-members',
|
||||
icon: 'globe',
|
||||
weight: 100,
|
||||
route: { name: 'c-cluster-explorer-members' },
|
||||
exact: true,
|
||||
});
|
||||
|
||||
virtualType({
|
||||
label: store.getters['i18n/t']('generic.overview'),
|
||||
group: 'Workload',
|
||||
namespaced: true,
|
||||
name: 'workload',
|
||||
|
|
@ -226,7 +243,7 @@ export function init(store) {
|
|||
});
|
||||
|
||||
virtualType({
|
||||
label: 'Projects/Namespaces',
|
||||
label: store.getters['i18n/t']('projectNamespaces.label'),
|
||||
group: 'cluster',
|
||||
icon: 'globe',
|
||||
namespaced: false,
|
||||
|
|
@ -238,7 +255,7 @@ export function init(store) {
|
|||
});
|
||||
|
||||
virtualType({
|
||||
label: 'Namespaces',
|
||||
label: store.getters['i18n/t'](`typeLabel.${ NAMESPACE }`, { count: 2 }),
|
||||
group: 'cluster',
|
||||
icon: 'globe',
|
||||
namespaced: false,
|
||||
|
|
|
|||
|
|
@ -177,6 +177,14 @@ export const RAM = {
|
|||
width: 120,
|
||||
};
|
||||
|
||||
export const PRINCIPAL = {
|
||||
name: 'principal',
|
||||
labelKey: 'tableHeaders.name',
|
||||
sort: 'principal.loginName',
|
||||
value: 'userPrincipalName',
|
||||
formatter: 'Principal',
|
||||
};
|
||||
|
||||
export const PODS = {
|
||||
name: 'pods',
|
||||
labelKey: 'tableHeaders.pods',
|
||||
|
|
@ -789,6 +797,12 @@ export const RESTART = {
|
|||
width: 75,
|
||||
};
|
||||
|
||||
export const ROLE = {
|
||||
name: 'role',
|
||||
value: 'roleDisplay',
|
||||
labelKey: 'tableHeaders.role',
|
||||
};
|
||||
|
||||
export const FEATURE_DESCRIPTION = {
|
||||
name: 'description',
|
||||
labelKey: 'tableHeaders.description',
|
||||
|
|
|
|||
|
|
@ -12,14 +12,15 @@ export const STEVE = {
|
|||
// Auth (via Norman)
|
||||
// Base: /v3
|
||||
export const NORMAN = {
|
||||
AUTH_CONFIG: 'authconfig',
|
||||
ETCD_BACKUP: 'etcdbackup',
|
||||
CLUSTER_TOKEN: 'clusterregistrationtoken',
|
||||
GROUP: 'group',
|
||||
PRINCIPAL: 'principal',
|
||||
SPOOFED: { GROUP_PRINCIPAL: 'group.principal' },
|
||||
TOKEN: 'token',
|
||||
USER: 'user',
|
||||
AUTH_CONFIG: 'authconfig',
|
||||
ETCD_BACKUP: 'etcdbackup',
|
||||
CLUSTER_TOKEN: 'clusterregistrationtoken',
|
||||
CLUSTER_ROLE_TEMPLATE_BINDING: 'clusterRoleTemplateBinding',
|
||||
GROUP: 'group',
|
||||
PRINCIPAL: 'principal',
|
||||
SPOOFED: { GROUP_PRINCIPAL: 'group.principal' },
|
||||
TOKEN: 'token',
|
||||
USER: 'user',
|
||||
};
|
||||
|
||||
// Public (via Norman)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,222 @@
|
|||
<script>
|
||||
import CreateEditView from '@/mixins/create-edit-view';
|
||||
import CruResource from '@/components/CruResource';
|
||||
import SelectPrincipal from '@/components/auth/SelectPrincipal';
|
||||
import { MANAGEMENT, NORMAN } from '@/config/types';
|
||||
import RadioGroup from '@/components/form/RadioGroup';
|
||||
import Card from '@/components/Card';
|
||||
import Loading from '@/components/Loading';
|
||||
import Checkbox from '@/components/form/Checkbox';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Card,
|
||||
Checkbox,
|
||||
CruResource,
|
||||
Loading,
|
||||
RadioGroup,
|
||||
SelectPrincipal
|
||||
},
|
||||
|
||||
mixins: [CreateEditView],
|
||||
async fetch() {
|
||||
await this.$store.dispatch('management/findAll', { type: MANAGEMENT.USER });
|
||||
this.roleTemplates = await this.$store.dispatch('management/findAll', { type: MANAGEMENT.ROLE_TEMPLATE });
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
customPermissions: [
|
||||
{
|
||||
label: this.t('members.clusterPermissions.createProjects'),
|
||||
key: 'projects-create',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
label: this.t('members.clusterPermissions.manageClusterBackups'),
|
||||
key: 'backups-manage',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
label: this.t('members.clusterPermissions.manageClusterCatalogs'),
|
||||
key: 'clustercatalogs-manage',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
label: this.t('members.clusterPermissions.manageClusterMembers'),
|
||||
key: 'clusterroletemplatebindings-manage',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
label: this.t('members.clusterPermissions.manageNodes'),
|
||||
key: 'nodes-manage',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
label: this.t('members.clusterPermissions.manageStorage'),
|
||||
key: 'storage-manage',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
label: this.t('members.clusterPermissions.viewAllProjects'),
|
||||
key: 'projects-view',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
label: this.t('members.clusterPermissions.viewClusterCatalogs'),
|
||||
key: 'clustercatalogs-view',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
label: this.t('members.clusterPermissions.viewClusterMembers'),
|
||||
key: 'clusterroletemplatebindings-view',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
label: this.t('members.clusterPermissions.viewNodes'),
|
||||
key: 'nodes-view',
|
||||
value: false
|
||||
}
|
||||
],
|
||||
permissionGroup: 'member',
|
||||
custom: {},
|
||||
roleTemplates: [],
|
||||
userPrincipalId: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
customRoles() {
|
||||
return this.roleTemplates
|
||||
.filter((role) => {
|
||||
return !role.builtin && !role.external && !role.hidden && role.context === 'cluster';
|
||||
});
|
||||
},
|
||||
doneLocationOverride() {
|
||||
return this.value.listLocation;
|
||||
},
|
||||
roleTemplateIds() {
|
||||
if (this.permissionGroup === 'owner') {
|
||||
return ['cluster-owner'];
|
||||
}
|
||||
|
||||
if (this.permissionGroup === 'member') {
|
||||
return ['cluster-member'];
|
||||
}
|
||||
|
||||
if (this.permissionGroup === 'custom') {
|
||||
return this.customPermissions
|
||||
.filter(permission => permission.value)
|
||||
.map(permission => permission.key);
|
||||
}
|
||||
|
||||
return [this.permissionGroup];
|
||||
},
|
||||
options() {
|
||||
const customRoles = this.customRoles.map(role => ({
|
||||
label: role.nameDisplay,
|
||||
description: role.description || this.t('members.clusterPermissions.noDescription'),
|
||||
value: role.id
|
||||
}));
|
||||
|
||||
return [
|
||||
{
|
||||
label: this.t('members.clusterPermissions.owner.label'),
|
||||
description: this.t('members.clusterPermissions.owner.description'),
|
||||
value: 'owner'
|
||||
},
|
||||
{
|
||||
label: this.t('members.clusterPermissions.member.label'),
|
||||
description: this.t('members.clusterPermissions.member.description'),
|
||||
value: 'member'
|
||||
},
|
||||
...customRoles,
|
||||
{
|
||||
label: this.t('members.clusterPermissions.custom.label'),
|
||||
description: this.t('members.clusterPermissions.custom.description'),
|
||||
value: 'custom'
|
||||
}
|
||||
];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onAdd(userId) {
|
||||
this.$set(this, 'userPrincipalId', userId);
|
||||
},
|
||||
async saveOverride() {
|
||||
const asyncBindings = this.roleTemplateIds.map(roleTemplateId => this.$store.dispatch(`rancher/create`, {
|
||||
type: NORMAN.CLUSTER_ROLE_TEMPLATE_BINDING,
|
||||
clusterId: this.$store.getters['currentCluster'].id,
|
||||
roleTemplateId,
|
||||
userPrincipalId: this.userPrincipalId
|
||||
}));
|
||||
|
||||
const bindings = await Promise.all(asyncBindings);
|
||||
|
||||
await Promise.all(bindings.map(binding => binding.save()));
|
||||
await this.$store.dispatch(`management/findAll`, { type: MANAGEMENT.CLUSTER_ROLE_TEMPLATE_BINDING, opt: { force: true } });
|
||||
|
||||
this.$router.replace(this.value.listLocation);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Loading v-if="$fetchState.pending" />
|
||||
<CruResource
|
||||
v-else
|
||||
class="cluster-role-template-binding"
|
||||
:errors="errors"
|
||||
:mode="mode"
|
||||
:resource="value"
|
||||
:subtypes="[]"
|
||||
:can-yaml="false"
|
||||
:validation-passed="!!userPrincipalId"
|
||||
@error="e=>errors = e"
|
||||
@finish="saveOverride"
|
||||
@cancel="done"
|
||||
>
|
||||
<div class="row m-10">
|
||||
<div class="col span-12">
|
||||
<SelectPrincipal class="mb-20" :mode="mode" :retain-selection="true" @add="onAdd" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card :show-highlight-border="false" :show-actions="false">
|
||||
<template v-slot:title>
|
||||
<div class="type-title">
|
||||
<h3>{{ t('members.clusterPermissions.label') }}</h3>
|
||||
<div class="type-description">
|
||||
{{ t('members.clusterPermissions.description') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:body>
|
||||
<RadioGroup
|
||||
v-model="permissionGroup"
|
||||
:options="options"
|
||||
name="permission-group"
|
||||
/>
|
||||
<div v-if="permissionGroup === 'custom'" class="custom-permissions ml-20 mt-10">
|
||||
<Checkbox v-for="permission in customPermissions" :key="permission.key" v-model="permission.value" class="mb-5" :label="permission.label" />
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</CruResource>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
$detailSize: 11px;
|
||||
|
||||
::v-deep .type-description {
|
||||
font-size: $detailSize;
|
||||
}
|
||||
|
||||
label.radio {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.custom-permissions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,6 +1,41 @@
|
|||
import { _CREATE } from '@/config/query-params';
|
||||
import { MANAGEMENT } from '@/config/types';
|
||||
|
||||
export default {
|
||||
detailPageHeaderActionOverride() {
|
||||
return (realMode) => {
|
||||
if (realMode === _CREATE) {
|
||||
return this.t('members.createActionLabel');
|
||||
}
|
||||
};
|
||||
},
|
||||
canCustomEdit() {
|
||||
return false;
|
||||
},
|
||||
|
||||
canYaml() {
|
||||
return false;
|
||||
},
|
||||
|
||||
canClone() {
|
||||
return false;
|
||||
},
|
||||
|
||||
user() {
|
||||
return this.$rootGetters['management/byId'](MANAGEMENT.USER, this.userName);
|
||||
},
|
||||
|
||||
nameDisplay() {
|
||||
return this.user?.nameDisplay;
|
||||
},
|
||||
|
||||
roleDisplay() {
|
||||
return this.roleTemplate.nameDisplay;
|
||||
},
|
||||
|
||||
roleDescription() {
|
||||
return this.roleTemplate.description;
|
||||
},
|
||||
|
||||
roleTemplate() {
|
||||
return this.$rootGetters['management/byId'](MANAGEMENT.ROLE_TEMPLATE, this.roleTemplateName);
|
||||
|
|
@ -29,4 +64,20 @@ export default {
|
|||
|
||||
return { name, params };
|
||||
},
|
||||
|
||||
listLocation() {
|
||||
return { name: 'c-cluster-explorer-members' };
|
||||
},
|
||||
|
||||
doneOverride() {
|
||||
return this.listLocation;
|
||||
},
|
||||
|
||||
parentLocationOverride() {
|
||||
return this.listLocation;
|
||||
},
|
||||
|
||||
subSearch() {
|
||||
return [{ nameDisplay: this.nameDisplay }];
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,6 +17,12 @@ export default {
|
|||
return !!(this.principalIds || []).find(p => p === currentPrincipal);
|
||||
},
|
||||
|
||||
principals() {
|
||||
return this.principalIds
|
||||
.map(id => this.$rootGetters['rancher/byId'](NORMAN.PRINCIPAL, id))
|
||||
.filter(p => p);
|
||||
},
|
||||
|
||||
nameDisplay() {
|
||||
return this.displayName || this.username || this.id;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ export default {
|
|||
},
|
||||
|
||||
projectNameSort() {
|
||||
return this.project?.nameSort || '}';
|
||||
return this.project?.nameSort || '';
|
||||
},
|
||||
|
||||
istioInstalled() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
<script>
|
||||
import { MANAGEMENT } from '@/config/types';
|
||||
import ResourceTable from '@/components/ResourceTable';
|
||||
import Loading from '@/components/Loading';
|
||||
import { NAME } from '@/config/product/explorer';
|
||||
import Masthead from '@/components/ResourceList/Masthead';
|
||||
import { AGE, ROLE, STATE, PRINCIPAL } from '@/config/table-headers';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Loading, Masthead, ResourceTable
|
||||
},
|
||||
|
||||
async fetch() {
|
||||
const clusterRoleTemplateBindingSchema = this.$store.getters[`management/schemaFor`](MANAGEMENT.CLUSTER_ROLE_TEMPLATE_BINDING);
|
||||
|
||||
const hydration = [
|
||||
this.$store.dispatch(`management/findAll`, { type: MANAGEMENT.USER }),
|
||||
this.$store.dispatch(`management/findAll`, { type: MANAGEMENT.ROLE_TEMPLATE })
|
||||
];
|
||||
const clusterRoleTemplateBindings = clusterRoleTemplateBindingSchema ? await this.$store.dispatch(`management/findAll`, { type: MANAGEMENT.CLUSTER_ROLE_TEMPLATE_BINDING }) : [];
|
||||
|
||||
await Promise.all(hydration);
|
||||
this.$set(this, 'clusterRoleTemplateBindings', clusterRoleTemplateBindings);
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
schema: this.$store.getters[`management/schemaFor`](MANAGEMENT.CLUSTER_ROLE_TEMPLATE_BINDING),
|
||||
headers: [
|
||||
STATE,
|
||||
PRINCIPAL,
|
||||
ROLE,
|
||||
AGE
|
||||
],
|
||||
createLocation: {
|
||||
name: 'c-cluster-product-resource-create',
|
||||
params: {
|
||||
product: NAME,
|
||||
resource: MANAGEMENT.CLUSTER_ROLE_TEMPLATE_BINDING,
|
||||
}
|
||||
},
|
||||
resource: MANAGEMENT.CLUSTER_ROLE_TEMPLATE_BINDING,
|
||||
clusterRoleTemplateBindings: null,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
filteredClusterRoleTemplateBindings() {
|
||||
return this.clusterRoleTemplateBindings
|
||||
.filter(b => !b.user?.isSystem && b.clusterName === this.$store.getters['currentCluster'].id);
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Loading v-if="$fetchState.pending" />
|
||||
<div v-else>
|
||||
<Masthead
|
||||
:schema="schema"
|
||||
:resource="resource"
|
||||
:create-location="createLocation"
|
||||
:create-button-label="t('members.createActionLabel')"
|
||||
/>
|
||||
<ResourceTable
|
||||
:schema="schema"
|
||||
:headers="headers"
|
||||
:rows="filteredClusterRoleTemplateBindings"
|
||||
:groupable="false"
|
||||
sub-search="subSearch"
|
||||
:sub-fields="['nameDisplay']"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -684,7 +684,7 @@ export default {
|
|||
action: (this.canCustomEdit ? 'goToClone' : 'cloneYaml'),
|
||||
label: this.t('action.clone'),
|
||||
icon: 'icon icon-copy',
|
||||
enabled: this.canCreate && (this.canCustomEdit || this.canYaml),
|
||||
enabled: this.canClone && this.canCreate && (this.canCustomEdit || this.canYaml),
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
|
|
@ -717,6 +717,10 @@ export default {
|
|||
return this.hasLink('remove') && this.$rootGetters['type-map/optionsFor'](this.type).isRemovable;
|
||||
},
|
||||
|
||||
canClone() {
|
||||
return true;
|
||||
},
|
||||
|
||||
canUpdate() {
|
||||
return this.hasLink('update') && this.$rootGetters['type-map/optionsFor'](this.type).isEditable;
|
||||
},
|
||||
|
|
@ -839,7 +843,7 @@ export default {
|
|||
const schema = this.$getters['schemaFor'](this.type);
|
||||
let url = schema.linkFor('collection');
|
||||
|
||||
if ( schema.attributes && schema.attributes.namespaced ) {
|
||||
if ( schema.attributes && schema.attributes.namespaced && this.metadata && this.metadata.namespace ) {
|
||||
url += `/${ this.metadata.namespace }`;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue