1<template> 2 <b-container fluid="xl"> 3 <page-title :description="$t('pageNetwork.pageDescription')" /> 4 <page-section :section-title="$t('pageNetwork.interface')"> 5 <b-row> 6 <b-col lg="3"> 7 <b-form-group 8 label-for="interface-select" 9 :label="$t('pageNetwork.form.networkInterface')" 10 > 11 <b-form-select 12 id="interface-select" 13 v-model="selectedInterfaceIndex" 14 :disabled="loading" 15 data-test-id="network-select-interface" 16 :options="interfaceSelectOptions" 17 @change="selectInterface" 18 > 19 </b-form-select> 20 </b-form-group> 21 </b-col> 22 </b-row> 23 </page-section> 24 <b-form novalidate @submit.prevent="submitForm"> 25 <b-form-group :disabled="loading"> 26 <page-section :section-title="$t('pageNetwork.system')"> 27 <b-row> 28 <b-col lg="3"> 29 <b-form-group 30 :label="$t('pageNetwork.form.defaultGateway')" 31 label-for="default-gateway" 32 > 33 <b-form-input 34 id="default-gateway" 35 v-model.trim="form.gateway" 36 data-test-id="network-input-gateway" 37 type="text" 38 :state="getValidationState($v.form.gateway)" 39 @change="$v.form.gateway.$touch()" 40 /> 41 <b-form-invalid-feedback role="alert"> 42 <div v-if="!$v.form.gateway.required"> 43 {{ $t('global.form.fieldRequired') }} 44 </div> 45 <div v-if="!$v.form.gateway.ipAddress"> 46 {{ $t('global.form.invalidFormat') }} 47 </div> 48 </b-form-invalid-feedback> 49 </b-form-group> 50 </b-col> 51 <b-col lg="3"> 52 <b-form-group 53 :label="$t('pageNetwork.form.hostname')" 54 label-for="hostname-field" 55 > 56 <b-form-input 57 id="hostname-field" 58 v-model.trim="form.hostname" 59 data-test-id="network-input-hostname" 60 type="text" 61 :state="getValidationState($v.form.hostname)" 62 @change="$v.form.hostname.$touch()" 63 /> 64 <b-form-invalid-feedback role="alert"> 65 <div v-if="!$v.form.hostname.required"> 66 {{ $t('global.form.fieldRequired') }} 67 </div> 68 <div v-if="!$v.form.hostname.validateHostname"> 69 {{ 70 $t('global.form.lengthMustBeBetween', { min: 1, max: 64 }) 71 }} 72 </div> 73 </b-form-invalid-feedback> 74 </b-form-group> 75 </b-col> 76 <b-col lg="3"> 77 <b-form-group 78 :label="$t('pageNetwork.form.macAddress')" 79 label-for="mac-address" 80 > 81 <b-form-input 82 id="mac-address" 83 v-model.trim="form.macAddress" 84 data-test-id="network-input-macAddress" 85 type="text" 86 :state="getValidationState($v.form.macAddress)" 87 @change="$v.form.macAddress.$touch()" 88 /> 89 <b-form-invalid-feedback role="alert"> 90 <div v-if="!$v.form.macAddress.required"> 91 {{ $t('global.form.fieldRequired') }} 92 </div> 93 <div v-if="!$v.form.macAddress.macAddress"> 94 {{ $t('global.form.invalidFormat') }} 95 </div> 96 </b-form-invalid-feedback> 97 </b-form-group> 98 </b-col> 99 </b-row> 100 </page-section> 101 <page-section :section-title="$t('pageNetwork.ipv4')"> 102 <b-form-group :label="$t('pageNetwork.ipv4Configuration')"> 103 <b-form-text id="enable-secure-help-block"> 104 {{ $t('pageNetwork.ipv4Helper') }} 105 </b-form-text> 106 <b-form-radio 107 v-model="form.dhcpEnabled" 108 name="dhcp-radio" 109 :value="true" 110 @change="onChangeIpv4Config" 111 > 112 {{ $t('pageNetwork.dhcp') }} 113 </b-form-radio> 114 <b-form-radio 115 v-model="form.dhcpEnabled" 116 name="static-radio" 117 :value="false" 118 @change="onChangeIpv4Config" 119 > 120 {{ $t('pageNetwork.static') }} 121 </b-form-radio> 122 </b-form-group> 123 <b-row> 124 <b-col lg="9" class="mb-3"> 125 <h3 class="h4"> 126 {{ $t('pageNetwork.dhcp') }} 127 </h3> 128 <b-table 129 responsive="md" 130 hover 131 :fields="ipv4DhcpTableFields" 132 :items="form.ipv4DhcpTableItems" 133 :empty-text="$t('global.table.emptyMessage')" 134 class="mb-0" 135 show-empty 136 > 137 <template #cell(Address)="{ item, index }"> 138 <b-form-input 139 v-model.trim="item.Address" 140 :data-test-id="`network-input-dhcpIpv4-${index}`" 141 :aria-label=" 142 $t('pageNetwork.table.dhcpIpv4AddressRow') + 143 ' ' + 144 (index + 1) 145 " 146 readonly 147 /> 148 </template> 149 <template #cell(SubnetMask)="{ item, index }"> 150 <b-form-input 151 v-model.trim="item.SubnetMask" 152 :data-test-id="`network-input-subnetMask-${index}`" 153 :aria-label=" 154 $t('pageNetwork.table.dhcpIpv4SubnetRow') + 155 ' ' + 156 (index + 1) 157 " 158 readonly 159 /> 160 </template> 161 <template #cell(actions)="{ item, index }"> 162 <table-row-action 163 v-for="(action, actionIndex) in item.actions" 164 :key="actionIndex" 165 :value="action.value" 166 :title="action.title" 167 :enabled="false" 168 @click-table-action=" 169 onDeleteIpv4StaticTableRow($event, index) 170 " 171 > 172 <template #icon> 173 <icon-trashcan v-if="action.value === 'delete'" /> 174 </template> 175 </table-row-action> 176 </template> 177 </b-table> 178 </b-col> 179 <b-col lg="9" class="mb-3"> 180 <h3 class="h4"> 181 {{ $t('pageNetwork.static') }} 182 </h3> 183 <b-table 184 responsive="md" 185 hover 186 :fields="ipv4StaticTableFields" 187 :items="form.ipv4StaticTableItems" 188 :empty-text="$t('global.table.emptyMessage')" 189 class="mb-0" 190 show-empty 191 > 192 <template #cell(Address)="{ item, index }"> 193 <b-form-input 194 v-model.trim="item.Address" 195 :data-test-id="`network-input-staticIpv4-${index}`" 196 :aria-label=" 197 $t('pageNetwork.table.staticIpv4AddressRow') + 198 ' ' + 199 (index + 1) 200 " 201 :state=" 202 getValidationState( 203 $v.form.ipv4StaticTableItems.$each.$iter[index].Address 204 ) 205 " 206 @change=" 207 $v.form.ipv4StaticTableItems.$each.$iter[ 208 index 209 ].Address.$touch() 210 " 211 /> 212 <b-form-invalid-feedback role="alert"> 213 <div 214 v-if=" 215 !$v.form.ipv4StaticTableItems.$each.$iter[index].Address 216 .required 217 " 218 > 219 {{ $t('global.form.fieldRequired') }} 220 </div> 221 <div 222 v-if=" 223 !$v.form.ipv4StaticTableItems.$each.$iter[index].Address 224 .ipAddress 225 " 226 > 227 {{ $t('global.form.invalidFormat') }} 228 </div> 229 </b-form-invalid-feedback> 230 </template> 231 <template #cell(SubnetMask)="{ item, index }"> 232 <b-form-input 233 v-model.trim="item.SubnetMask" 234 :data-test-id="`network-input-subnetMask-${index}`" 235 :aria-label=" 236 $t('pageNetwork.table.staticIpv4SubnetRow') + 237 ' ' + 238 (index + 1) 239 " 240 :state=" 241 getValidationState( 242 $v.form.ipv4StaticTableItems.$each.$iter[index] 243 .SubnetMask 244 ) 245 " 246 @change=" 247 $v.form.ipv4StaticTableItems.$each.$iter[ 248 index 249 ].SubnetMask.$touch() 250 " 251 /> 252 <b-form-invalid-feedback role="alert"> 253 <div 254 v-if=" 255 !$v.form.ipv4StaticTableItems.$each.$iter[index] 256 .SubnetMask.required 257 " 258 > 259 {{ $t('global.form.fieldRequired') }} 260 </div> 261 <div 262 v-if=" 263 !$v.form.ipv4StaticTableItems.$each.$iter[index] 264 .SubnetMask.ipAddress 265 " 266 > 267 {{ $t('global.form.invalidFormat') }} 268 </div> 269 </b-form-invalid-feedback> 270 </template> 271 <template #cell(actions)="{ item, index }"> 272 <table-row-action 273 v-for="(action, actionIndex) in item.actions" 274 :key="actionIndex" 275 :value="action.value" 276 :title="action.title" 277 @click-table-action=" 278 onDeleteIpv4StaticTableRow($event, index) 279 " 280 > 281 <template #icon> 282 <icon-trashcan v-if="action.value === 'delete'" /> 283 </template> 284 </table-row-action> 285 </template> 286 </b-table> 287 <b-button variant="link" @click="addIpv4StaticTableRow"> 288 <icon-add /> 289 {{ $t('pageNetwork.table.addStaticIpv4Address') }} 290 </b-button> 291 </b-col> 292 </b-row> 293 </page-section> 294 <page-section :section-title="$t('pageNetwork.staticDns')"> 295 <b-row> 296 <b-col lg="4" class="mb-3"> 297 <b-table 298 responsive 299 hover 300 :fields="dnsTableFields" 301 :items="form.dnsStaticTableItems" 302 :empty-text="$t('global.table.emptyMessage')" 303 class="mb-0" 304 show-empty 305 > 306 <template #cell(address)="{ item, index }"> 307 <b-form-input 308 v-model.trim="item.address" 309 :data-test-id="`network-input-dnsAddress-${index}`" 310 :aria-label=" 311 $t('pageNetwork.table.staticDnsRow') + ' ' + (index + 1) 312 " 313 :state=" 314 getValidationState( 315 $v.form.dnsStaticTableItems.$each.$iter[index].address 316 ) 317 " 318 @change=" 319 $v.form.dnsStaticTableItems.$each.$iter[ 320 index 321 ].address.$touch() 322 " 323 /> 324 <b-form-invalid-feedback role="alert"> 325 <div 326 v-if=" 327 !$v.form.dnsStaticTableItems.$each.$iter[index].address 328 .required 329 " 330 > 331 {{ $t('global.form.fieldRequired') }} 332 </div> 333 <div 334 v-if=" 335 !$v.form.dnsStaticTableItems.$each.$iter[index].address 336 .ipAddress 337 " 338 > 339 {{ $t('global.form.invalidFormat') }} 340 </div> 341 </b-form-invalid-feedback> 342 </template> 343 <template #cell(actions)="{ item, index }"> 344 <table-row-action 345 v-for="(action, actionIndex) in item.actions" 346 :key="actionIndex" 347 :value="action.value" 348 :title="action.title" 349 @click-table-action="onDeleteDnsTableRow($event, index)" 350 > 351 <template #icon> 352 <icon-trashcan v-if="action.value === 'delete'" /> 353 </template> 354 </table-row-action> 355 </template> 356 </b-table> 357 <b-button variant="link" @click="addDnsTableRow"> 358 <icon-add /> {{ $t('pageNetwork.table.addDns') }} 359 </b-button> 360 </b-col> 361 </b-row> 362 </page-section> 363 <b-button 364 variant="primary" 365 type="submit" 366 data-test-id="network-button-saveNetworkSettings" 367 > 368 {{ $t('global.action.saveSettings') }} 369 </b-button> 370 </b-form-group> 371 </b-form> 372 </b-container> 373</template> 374 375<script> 376import IconTrashcan from '@carbon/icons-vue/es/trash-can/20'; 377import IconAdd from '@carbon/icons-vue/es/add--alt/20'; 378import BVToastMixin from '@/components/Mixins/BVToastMixin'; 379import LoadingBarMixin, { loading } from '@/components/Mixins/LoadingBarMixin'; 380import PageSection from '@/components/Global/PageSection'; 381import PageTitle from '@/components/Global/PageTitle'; 382import TableRowAction from '@/components/Global/TableRowAction'; 383import VuelidateMixin from '@/components/Mixins/VuelidateMixin'; 384import { mapState } from 'vuex'; 385import { 386 required, 387 helpers, 388 ipAddress, 389 macAddress, 390} from 'vuelidate/lib/validators'; 391 392// Hostname pattern 393const validateHostname = helpers.regex('validateHostname', /^\S{0,64}$/); 394 395export default { 396 name: 'Network', 397 components: { 398 PageTitle, 399 PageSection, 400 TableRowAction, 401 IconTrashcan, 402 IconAdd, 403 }, 404 mixins: [BVToastMixin, VuelidateMixin, LoadingBarMixin], 405 beforeRouteLeave(to, from, next) { 406 this.hideLoader(); 407 next(); 408 }, 409 data() { 410 return { 411 ipv4DhcpTableFields: [ 412 { 413 key: 'Address', 414 label: this.$t('pageNetwork.table.ipAddress'), 415 }, 416 { 417 key: 'SubnetMask', 418 label: this.$t('pageNetwork.table.subnet'), 419 }, 420 { key: 'actions', label: '', tdClass: 'text-right' }, 421 ], 422 ipv4StaticTableFields: [ 423 { 424 key: 'Address', 425 label: this.$t('pageNetwork.table.ipAddress'), 426 }, 427 { 428 key: 'SubnetMask', 429 label: this.$t('pageNetwork.table.subnet'), 430 }, 431 { key: 'actions', label: '', tdClass: 'text-right' }, 432 ], 433 dnsTableFields: [ 434 { 435 key: 'address', 436 label: this.$t('pageNetwork.table.ipAddress'), 437 }, 438 { key: 'actions', label: '', tdClass: 'text-right' }, 439 ], 440 selectedInterfaceIndex: 0, 441 selectedInterface: {}, 442 form: { 443 dhcpEnabled: null, 444 gateway: '', 445 hostname: '', 446 macAddress: '', 447 ipv4StaticTableItems: [], 448 ipv4DhcpTableItems: [], 449 dnsStaticTableItems: [], 450 }, 451 loading, 452 }; 453 }, 454 validations() { 455 return { 456 form: { 457 gateway: { required, ipAddress }, 458 hostname: { required, validateHostname }, 459 ipv4StaticTableItems: { 460 $each: { 461 Address: { 462 required, 463 ipAddress, 464 }, 465 SubnetMask: { 466 required, 467 ipAddress, 468 }, 469 }, 470 }, 471 macAddress: { required, macAddress: macAddress() }, 472 dnsStaticTableItems: { 473 $each: { 474 address: { 475 required, 476 ipAddress, 477 }, 478 }, 479 }, 480 }, 481 }; 482 }, 483 computed: { 484 ...mapState('network', [ 485 'ethernetData', 486 'interfaceOptions', 487 'defaultGateway', 488 ]), 489 interfaceSelectOptions() { 490 return this.interfaceOptions.map((option, index) => { 491 return { 492 text: option, 493 value: index, 494 }; 495 }); 496 }, 497 }, 498 watch: { 499 ethernetData: function () { 500 this.selectInterface(); 501 }, 502 }, 503 created() { 504 this.startLoader(); 505 this.$store 506 .dispatch('network/getEthernetData') 507 .finally(() => this.endLoader()); 508 }, 509 methods: { 510 selectInterface() { 511 this.selectedInterface = this.ethernetData[this.selectedInterfaceIndex]; 512 this.getIpv4DhcpTableItems(); 513 this.getIpv4StaticTableItems(); 514 this.getDnsStaticTableItems(); 515 this.getInterfaceSettings(); 516 }, 517 getInterfaceSettings() { 518 this.form.gateway = this.defaultGateway; 519 this.form.hostname = this.selectedInterface.HostName; 520 this.form.macAddress = this.selectedInterface.MACAddress; 521 this.form.dhcpEnabled = this.selectedInterface.DHCPv4.DHCPEnabled; 522 }, 523 onChangeIpv4Config(value) { 524 this.form.dhcpEnabled = value; 525 }, 526 getDnsStaticTableItems() { 527 const dns = this.selectedInterface.StaticNameServers || []; 528 this.form.dnsStaticTableItems = dns.map((server) => { 529 return { 530 address: server, 531 actions: [ 532 { 533 value: 'delete', 534 enabled: this.form.dhcpEnabled, 535 title: this.$t('pageNetwork.table.deleteDns'), 536 }, 537 ], 538 }; 539 }); 540 }, 541 addDnsTableRow() { 542 this.$v.form.dnsStaticTableItems.$touch(); 543 this.form.dnsStaticTableItems.push({ 544 address: '', 545 actions: [ 546 { 547 value: 'delete', 548 enabled: this.form.dhcpEnabled, 549 title: this.$t('pageNetwork.table.deleteDns'), 550 }, 551 ], 552 }); 553 }, 554 deleteDnsTableRow(index) { 555 this.$v.form.dnsStaticTableItems.$touch(); 556 this.form.dnsStaticTableItems.splice(index, 1); 557 }, 558 onDeleteDnsTableRow(action, row) { 559 this.deleteDnsTableRow(row); 560 }, 561 getIpv4DhcpTableItems() { 562 const addresses = this.selectedInterface.IPv4Addresses || []; 563 this.form.ipv4DhcpTableItems = addresses 564 .filter((ipv4) => ipv4.AddressOrigin === 'DHCP') 565 .map((ipv4) => { 566 return { 567 Address: ipv4.Address, 568 SubnetMask: ipv4.SubnetMask, 569 actions: [ 570 { 571 value: 'delete', 572 enabled: false, 573 title: this.$t('pageNetwork.table.deleteDhcpIpv4'), 574 }, 575 ], 576 }; 577 }); 578 }, 579 getIpv4StaticTableItems() { 580 const addresses = this.selectedInterface.IPv4StaticAddresses || []; 581 this.form.ipv4StaticTableItems = addresses.map((ipv4) => { 582 return { 583 Address: ipv4.Address, 584 SubnetMask: ipv4.SubnetMask, 585 actions: [ 586 { 587 value: 'delete', 588 enabled: this.form.dhcpEnabled, 589 title: this.$t('pageNetwork.table.deleteStaticIpv4'), 590 }, 591 ], 592 }; 593 }); 594 }, 595 addIpv4StaticTableRow() { 596 this.$v.form.ipv4StaticTableItems.$touch(); 597 this.form.ipv4StaticTableItems.push({ 598 Address: '', 599 SubnetMask: '', 600 actions: [ 601 { 602 value: 'delete', 603 enabled: this.form.dhcpEnabled, 604 title: this.$t('pageNetwork.table.deleteStaticIpv4'), 605 }, 606 ], 607 }); 608 }, 609 deleteIpv4StaticTableRow(index) { 610 this.$v.form.ipv4StaticTableItems.$touch(); 611 this.form.ipv4StaticTableItems.splice(index, 1); 612 }, 613 onDeleteIpv4StaticTableRow(action, row) { 614 this.deleteIpv4StaticTableRow(row); 615 }, 616 submitForm() { 617 this.$v.$touch(); 618 if (this.$v.$invalid) return; 619 this.startLoader(); 620 let networkInterfaceSelected = this.selectedInterface; 621 let selectedInterfaceIndex = this.selectedInterfaceIndex; 622 let interfaceId = networkInterfaceSelected.Id; 623 let isDhcpEnabled = this.form.dhcpEnabled; 624 let macAddress = this.form.macAddress; 625 let hostname = this.form.hostname; 626 let networkSettingsForm = { 627 interfaceId, 628 hostname, 629 macAddress, 630 selectedInterfaceIndex, 631 }; 632 // Enabling DHCP without any available IP addresses will bring network down 633 if (this.form.ipv4DhcpTableItems.length) { 634 networkSettingsForm.isDhcpEnabled = isDhcpEnabled; 635 } else { 636 networkSettingsForm.isDhcpEnabled = false; 637 this.errorToast(this.$t('pageNetwork.toast.errorSaveDhcpSettings')); 638 } 639 networkSettingsForm.staticIpv4 = this.form.ipv4StaticTableItems.map( 640 (updateIpv4) => { 641 delete updateIpv4.actions; 642 updateIpv4.Gateway = this.form.gateway; 643 return updateIpv4; 644 } 645 ); 646 networkSettingsForm.staticNameServers = this.form.dnsStaticTableItems.map( 647 (updateDns) => { 648 return updateDns.address; 649 } 650 ); 651 this.$store 652 .dispatch('network/updateInterfaceSettings', networkSettingsForm) 653 .then((success) => { 654 this.successToast(success); 655 }) 656 .catch(({ message }) => this.errorToast(message)) 657 .finally(() => { 658 this.$v.form.$reset(); 659 this.endLoader(); 660 }); 661 }, 662 }, 663}; 664</script> 665