Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions shell/assets/translations/en-us.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ generic:
genericRow: row {index}
showLess: Show less
showMore: Show more
externalIps: External IPs
internalIps: Internal IPs
opensInNewTab: Opens in a new tab

tabs:
Expand Down Expand Up @@ -3652,6 +3654,7 @@ internalExternalIP:
none: None
copyInternalIp: Copy internal IP address to clipboard
copyExternalIp: Copy external IP address to clipboard
clickToShowMoreIps: "Click to show {count} more {count, plural, one {IP} other {IPs}}"

istio:
links:
Expand Down
219 changes: 195 additions & 24 deletions shell/components/formatter/InternalExternalIP.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,60 @@
import { isV4Format, isV6Format } from 'ip';
import CopyToClipboard from '@shell/components/CopyToClipboard';
import { mapGetters } from 'vuex';
import RcStatusBadge from '@components/Pill/RcStatusBadge/RcStatusBadge';

export default {
components: { CopyToClipboard },
components: { CopyToClipboard, RcStatusBadge },
props: {
row: {
type: Object,
required: true
},
},
computed: {
...mapGetters({ t: 'i18n/t' }),
filteredExternalIps() {
return this.row.externalIps?.filter((ip) => this.isIp(ip)) || [];
},
filteredInternalIps() {
return this.row.internalIps?.filter((ip) => this.isIp(ip)) || [];
},
internalSameAsExternal() {
return this.row.internalIp === this.row.externalIp;
return this.filteredInternalIps[0] === this.filteredExternalIps[0];
},
showPopover() {
return this.filteredExternalIps.length > 1 || this.filteredInternalIps.length > 1;
},
...mapGetters({ t: 'i18n/t' })
externalIp() {
return this.filteredExternalIps[0] || null;
},
internalIp() {
return this.filteredInternalIps[0] || null;
},
remainingIpCount() {
let count = 0;

if (this.filteredExternalIps.length > 1) {
count += this.filteredExternalIps.length - 1;
}

if (!this.internalSameAsExternal && this.filteredInternalIps.length > 1) {
count += this.filteredInternalIps.length - 1;
}

return count;
},
tooltipContent() {
const count = this.remainingIpCount;

return this.t('internalExternalIP.clickToShowMoreIps', { count });
},
remainingExternalIps() {
return this.filteredExternalIps.slice(1);
},
remainingInternalIps() {
return this.filteredInternalIps.slice(1);
}
},
methods: {
isIp(ip) {
Expand All @@ -25,40 +66,170 @@ export default {
</script>

<template>
<span>
<template v-if="isIp(row.externalIp)">
{{ row.externalIp }} <CopyToClipboard
:aria-label="t('internalExternalIP.copyExternalIp')"
label-as="tooltip"
:text="row.externalIp"
class="icon-btn"
action-color="bg-transparent"
/>
<div class="ip-container">
<template v-if="externalIp">
<span data-testid="external-ip">
{{ externalIp }}
<CopyToClipboard
:aria-label="t('internalExternalIP.copyExternalIp')"
label-as="tooltip"
:text="externalIp"
class="icon-btn"
action-color="bg-transparent"
/>
</span>
</template>
<template v-else>
-
</template>
/
<template v-if="internalSameAsExternal && isIp(row.internalIp)">
<span class="separator">/</span>
<template v-if="internalSameAsExternal">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This formatter will render - / Same as External when neither internal nor external IPs are defined. Though technically they are the same when they're both null, I think we still want the table formatter to render - / - in that case. I think one way of doing that would be changing this line to something like

<template v-if="internalSameAsExternal && internalIp">

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, I updated the logic inside the computed property internalSameAsExternal

{{ t('tableHeaders.internalIpSameAsExternal') }}
</template>
<template v-else-if="isIp(row.internalIp)">
{{ row.internalIp }}<CopyToClipboard
:aria-label="t('internalExternalIP.copyInternalIp')"
label-as="tooltip"
:text="row.internalIp"
class="icon-btn"
action-color="bg-transparent"
/>
<template v-else-if="internalIp">
<span data-testid="internal-ip">
{{ internalIp }}
<CopyToClipboard
:aria-label="t('internalExternalIP.copyInternalIp')"
label-as="tooltip"
:text="internalIp"
class="icon-btn"
action-color="bg-transparent"
/>
</span>
</template>
<template v-else>
-
</template>
</span>
<v-dropdown
v-if="showPopover"
ref="dropdown"
placement="bottom-start"
>
<template #default>
<RcStatusBadge
v-clean-tooltip="tooltipContent"
status="info"
data-testid="plus-more"
@click.stop
>
{{ t('generic.plusMore', {n: remainingIpCount}) }}
</RcStatusBadge>
</template>
<template #popper>
<div
class="ip-addresses-popover"
data-testid="ip-addresses-popover"
>
<button
class="btn btn-sm close-button"
@click="$refs.dropdown.hide()"
>
<i class="icon icon-close" />
</button>
<div
v-if="remainingExternalIps.length"
class="ip-list"
data-testid="external-ip-list"
>
<h5>{{ t('generic.externalIps') }}</h5>
<div
v-for="ip in remainingExternalIps"
:key="ip"
class="ip-address"
>
<span>{{ ip }}</span>
<CopyToClipboard
:text="ip"
label-as="tooltip"
class="icon-btn"
action-color="bg-transparent"
/>
</div>
</div>
<div
v-if="remainingInternalIps.length"
class="ip-list"
data-testid="internal-ip-list"
>
<h5>{{ t('generic.internalIps') }}</h5>
<div
v-for="ip in remainingInternalIps"
:key="ip"
class="ip-address"
>
<span>{{ ip }}</span>
<CopyToClipboard
:text="ip"
label-as="tooltip"
class="icon-btn"
action-color="bg-transparent"
/>
</div>
</div>
</div>
</template>
</v-dropdown>
</div>
</template>

<style lang='scss' scoped>
.ip-container {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px;
margin: 8px 0;
}

.icon-btn {
margin-left: 8px;
padding: 2px;
min-height: 24px;
}

.rc-status-badge {
cursor: pointer;
padding: 0 4px;
}

.ip-addresses-popover {
display: flex;
flex-direction: column;
min-width: 120px;
padding: 8px;
gap: 16px;

.ip-list {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 8px;

h5 {
margin-bottom: 4px;
font-weight: 600;
}
}

.ip-address {
display: flex;
align-items: center;
gap: 4px;
}

.close-button {
position: absolute;
top: -6px;
right: -6px;
padding: 8px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;

&:hover .icon-close{
color: var(--primary);
}
}
}
</style>
Loading