-
Notifications
You must be signed in to change notification settings - Fork 9
Virtual cluster policy #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 44 commits
8c527c7
13567c8
07c13d1
340a51e
f97f557
72879ca
c33c572
49b9ad9
3e74070
49dbd04
412be42
c82b1c7
07d9504
93cf5ee
74b0861
348846d
c40e4b0
fc7cc89
17a90c0
779484b
34eb72e
60661fe
905d1b3
7cd2103
022631b
7768d98
f15e257
24934dc
424950b
d871adb
ea9f10d
156a04f
d69496b
586b92f
9b5b650
ab70068
9d36f25
927a541
1c1c7f2
66cfedb
88bf0a8
4431b51
def33ce
4ba0732
ba6c003
fa46dfc
091c0bc
c71a980
dfbda46
f062334
c5784ff
f7f6762
24ac759
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "typescript.tsdk": "node_modules/typescript/lib" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,9 @@ | ||
| module.exports = require('./.shell/pkg/babel.config.js'); | ||
| const baseConfig = require('@rancher/shell/pkg/babel.config'); | ||
|
|
||
| module.exports = { | ||
| ...baseConfig, | ||
| plugins: [ | ||
| ...(baseConfig.plugins || []), | ||
| '@babel/plugin-transform-class-static-block' | ||
| ] | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,264 @@ | ||
| <script> | ||
| import { _CREATE } from '@shell/config/query-params'; | ||
|
|
||
| import LabeledSelect from '@shell/components/form/LabeledSelect'; | ||
| import { LABELS, K3K } from '../../types'; | ||
| import { NAMESPACE } from '@shell/config/types'; | ||
| import { Banner } from '@rancher/components'; | ||
|
|
||
| import isEmpty from 'lodash/isEmpty'; | ||
|
|
||
| export default { | ||
| name: 'K3kPolicySelector', | ||
|
|
||
| emits: ['update:policy', 'update:targetNamespace'], | ||
|
|
||
| components: { LabeledSelect, Banner }, | ||
|
|
||
| props: { | ||
|
|
||
| mode: { | ||
| type: String, | ||
| default: _CREATE | ||
| }, | ||
|
|
||
| targetNamespace: { | ||
| type: String, | ||
| default: '' | ||
| }, | ||
|
|
||
| hostCluster: { | ||
| type: Object, | ||
| default: () => { | ||
| return {}; | ||
| } | ||
| }, | ||
|
|
||
| k3kInstalled: { | ||
| type: Boolean, | ||
| default: false | ||
| }, | ||
|
|
||
| policy: { | ||
| type: Object, | ||
| default: () => { | ||
| return {}; | ||
| } | ||
| }, | ||
| }, | ||
|
|
||
| async fetch() { | ||
| this.fetchPolicies(); | ||
| if (this.mode !== _CREATE) { | ||
| await this.findSelectedPolicy(); | ||
| } | ||
| }, | ||
|
|
||
| data() { | ||
| return { | ||
| policies: [], | ||
| namespaces: [], | ||
| loadingPolicies: false, | ||
| loadingNamespaces: false, | ||
| namespaceError: false, | ||
| policyError: false | ||
| }; | ||
| }, | ||
|
|
||
| watch: { | ||
| hostClusterId(neu, old) { | ||
mantis-toboggan-md marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| this.$emit('update:policy', {}); | ||
| this.$emit('update:targetNamespace', ''); | ||
| if (neu && this.k3kInstalled) { | ||
| this.fetchPolicies(); | ||
| } | ||
| }, | ||
|
|
||
| k3kInstalled(neu) { | ||
| if (neu) { | ||
| this.fetchPolicies(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here with await, sorry, I thought I mentioned it in the previous review. It's okay if this intentionally doesn't wait
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one is intentional |
||
| } | ||
| }, | ||
|
|
||
| policyOptions(neu = []) { | ||
| const policyOpt = neu.find((p) => !!p?.value ) ; | ||
|
|
||
| if (this.mode === _CREATE) { | ||
| this.$emit('update:policy', policyOpt?.value || null); | ||
| this.$emit('update:targetNamespace', ''); | ||
| } | ||
| }, | ||
|
|
||
| namespaceOptions(neu = []) { | ||
| if (this.mode === _CREATE && !neu.includes(this.targetNamespace)) { | ||
| this.$emit('update:targetNamespace', neu[0] || ''); | ||
| } | ||
| } | ||
| }, | ||
|
|
||
| methods: { | ||
| async fetchPolicies() { | ||
| if (this.hostClusterId) { | ||
| this.loadingPolicies = true; | ||
|
|
||
| this.policyError = false; | ||
|
|
||
| this.policies = []; | ||
|
|
||
| try { | ||
| const res = await this.$store.dispatch('management/request', { | ||
| url: `/k8s/clusters/${ this.hostClusterId }/v1/${ K3K.POLICY }`, | ||
| method: 'GET' | ||
| }); | ||
|
|
||
| this.policies = res.data || []; | ||
| } catch (err) { | ||
| this.policies = []; | ||
| this.policyError = true; | ||
| } | ||
|
|
||
| this.loadingPolicies = false; | ||
|
|
||
| return await this.fetchNamespaces(); | ||
| } | ||
| }, | ||
|
|
||
| async fetchNamespaces() { | ||
| this.loadingNamespaces = true; | ||
eva-vashkevich marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| this.namespaceError = false; | ||
|
|
||
| try { | ||
| const res = await this.$store.dispatch('management/request', { | ||
| url: `/k8s/clusters/${ this.hostClusterId }/v1/${ NAMESPACE }`, | ||
| method: 'GET' | ||
| }); | ||
|
|
||
| this.namespaces = res.data || []; | ||
| } catch (e) { | ||
| this.namespaces = []; | ||
| this.namespaceError = true; | ||
| } | ||
|
|
||
| this.loadingNamespaces = false; | ||
| }, | ||
|
|
||
| // we show policies in this form but they are not saved as part of the k3k cluster spec | ||
| // get the namespace the k3k cluster is in and check its labels to work out which policy the cluster falls under | ||
| async findSelectedPolicy() { | ||
| if (!this.policies.length) { | ||
| await this.fetchPolicies(); | ||
| } | ||
|
|
||
| const nsObject = this.namespaces.find((ns) => ns.id === this.targetNamespace); | ||
|
|
||
| const policyName = nsObject?.metadata?.labels?.[LABELS.POLICY] || ''; | ||
|
|
||
| // if we can't find the policy name, the namespace may be labeled with a policy that has since been deleted | ||
| // we should show 'none' in that case | ||
| const policyObject = this.policies.find((p) => p?.metadata?.name === policyName); | ||
|
|
||
| if (policyObject) { | ||
| this.$emit('update:policy', policyObject); | ||
| } | ||
|
|
||
| return ''; | ||
mantis-toboggan-md marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }, | ||
|
|
||
| isEmpty | ||
| }, | ||
|
|
||
| computed: { | ||
| isCreate() { | ||
| return this.mode === _CREATE; | ||
| }, | ||
|
|
||
| hostClusterId() { | ||
| const mgmt = this.hostCluster?.mgmt; | ||
|
|
||
| return mgmt?.id; | ||
| }, | ||
|
|
||
| policyOptions() { | ||
| return [{ label: this.t('generic.none'), value: null }, ...this.policies.map((p) => { | ||
| return { label: p?.metadata?.name, value: p }; | ||
| })]; | ||
| }, | ||
|
|
||
| namespaceOptions() { | ||
| // if "no policy" is selected, show all NS without policy label | ||
| if ( !this.policy) { | ||
| return (this.namespaces || []).reduce((all, ns) => { | ||
| if (!ns?.metadata?.labels?.[LABELS.POLICY]) { | ||
| all.push(ns.id); | ||
| } | ||
|
|
||
| return all; | ||
| }, []); | ||
| } | ||
|
|
||
| return (this.namespaces || []).reduce((all, ns) => { | ||
| if (ns?.metadata?.labels?.[LABELS.POLICY] === this.policy?.metadata?.name) { | ||
| all.push(ns.id); | ||
| } | ||
|
|
||
| return all; | ||
| }, []); | ||
| } | ||
| }, | ||
| }; | ||
|
|
||
| </script> | ||
|
|
||
| <template> | ||
| <Banner | ||
| v-if="namespaceError" | ||
| color="error" | ||
| :label="t('k3k.errors.loadingNamespaces', {cluster:hostCluster?.displayName || hostCluster?.metadata?.name || '' })" | ||
| /> | ||
| <Banner | ||
| v-if="policyError && k3kInstalled" | ||
| color="error" | ||
| :label="t('k3k.errors.loadingPolicies', {cluster:hostCluster?.displayName || hostCluster?.metadata?.name || '' })" | ||
| /> | ||
| <div class="row mb-20"> | ||
| <div | ||
| class="col span-6" | ||
| > | ||
| <LabeledSelect | ||
| :value="policy && !isEmpty(policy) ? policy : t('generic.none')" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't value be null instead of t('generic.none') ?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is making the LabeledSelect component display |
||
| :loading="loadingPolicies || $fetchState.pending" | ||
| :disabled="!hostClusterId || !k3kInstalled || !isCreate" | ||
| :mode="mode" | ||
| :label="t('k3k.policy.label')" | ||
| :options="policyOptions" | ||
| :hover-tooltip="false" | ||
| @selecting="e=>$emit('update:policy', e)" | ||
| /> | ||
| <span | ||
| v-if="!policy && !loadingPolicies && k3kInstalled && !$fetchState.pending" | ||
| class="nonepolicy-warning text-muted" | ||
| ><i class="icon icon-warning" />{{ t('k3k.policy.noneWarning') }}</span> | ||
| </div> | ||
| <div class="col span-6"> | ||
| <LabeledSelect | ||
| :value="targetNamespace" | ||
| :loading="loadingNamespaces || loadingPolicies" | ||
| :mode="mode" | ||
| :disabled="!isCreate" | ||
| :label="t('k3k.targetNamespace.label')" | ||
| :options="namespaceOptions" | ||
| @selecting="e=>$emit('update:targetNamespace', e)" | ||
| /> | ||
| </div> | ||
| </div> | ||
| </template> | ||
|
|
||
| <style lang="scss"> | ||
| .nonepolicy-warning { | ||
| margin: 3px; | ||
| display: flex; | ||
| & i { | ||
| margin-right: 3px; | ||
| } | ||
| } | ||
| </style> | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we do not need to await for it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, fair point. We don't really need to await this in create mode; the component wont break if it tries to render without policies loaded, and the dropdowns have their own load spinner independent of $fetchState.pending. But if not in create mode,
findSelectedPolicyis likely going to call fetchPolicies over again (and await it).