1
1
import { useQuery } from "@tanstack/react-query" ;
2
2
import classNames from "classnames" ;
3
3
import MaterialReactTable , { MRT_ColumnDef } from "material-react-table" ;
4
- import { ChangeEvent , useEffect , useMemo , useReducer , useState } from "react" ;
5
- import Modal from "react-modal" ;
4
+ import { useEffect , useMemo , useReducer , useState } from "react" ;
6
5
import { Link , useNavigate , useParams } from "react-router-dom" ;
7
6
8
7
import { KotsPageTitle } from "@components/Head" ;
9
8
import { useApps } from "@features/App" ;
10
9
import { rbacRoles } from "../../constants/rbac" ;
11
10
import { Utilities } from "../../utilities/utilities" ;
12
- import Icon from "../Icon" ;
13
11
import CodeSnippet from "../shared/CodeSnippet" ;
14
12
15
13
import "@src/scss/components/apps/EmbeddedClusterManagement.scss" ;
16
14
import { isEqual } from "lodash" ;
15
+ import AddANodeModal from "@components/modals/AddANodeModal" ;
16
+ import Icon from "@components/Icon" ;
17
17
18
18
const testData = {
19
19
nodes : undefined ,
@@ -241,24 +241,23 @@ const EmbeddedClusterManagement = ({
241
241
if ( nodeTypes . length === 1 ) {
242
242
// if there's only one node type, select it by default
243
243
setSelectedNodeTypes ( nodeTypes ) ;
244
- } else {
245
- setSelectedNodeTypes ( [ ] ) ;
244
+ } else if ( nodeTypes . length > 1 ) {
245
+ setSelectedNodeTypes ( [ nodeTypes [ 0 ] ] ) ;
246
246
}
247
247
} , [ rolesData ] ) ;
248
248
249
249
const determineDisabledState = ( ) => {
250
250
return false ;
251
251
} ;
252
252
253
- const handleSelectNodeType = ( e : ChangeEvent < HTMLInputElement > ) => {
254
- let nodeType = e . currentTarget . value ;
255
- let types = selectedNodeTypes ;
256
-
257
- if ( selectedNodeTypes . includes ( nodeType ) ) {
258
- setSelectedNodeTypes ( types . filter ( ( type ) => type !== nodeType ) ) ;
259
- } else {
260
- setSelectedNodeTypes ( [ ...types , nodeType ] ) ;
261
- }
253
+ const handleSelectNodeType = ( nodeType ) => {
254
+ setSelectedNodeTypes ( ( prevSelectedNodeTypes ) => {
255
+ if ( prevSelectedNodeTypes . includes ( nodeType ) ) {
256
+ return prevSelectedNodeTypes . filter ( ( type ) => type !== nodeType ) ;
257
+ } else {
258
+ return [ ...prevSelectedNodeTypes , nodeType ] ;
259
+ }
260
+ } ) ;
262
261
} ;
263
262
// #endregion
264
263
@@ -413,6 +412,111 @@ const EmbeddedClusterManagement = ({
413
412
}
414
413
} ;
415
414
415
+ const AddNodeInstructions = ( ) => {
416
+ return (
417
+ < div className = "tw-mb-2 tw-text-base" >
418
+ < p >
419
+ Optionally add nodes to the cluster. Click{ " " }
420
+ < span className = "tw-font-semibold" > Continue </ span >
421
+ to proceed with a single node.
422
+ </ p >
423
+ < p >
424
+ { rolesData ?. roles &&
425
+ rolesData . roles . length > 1 &&
426
+ "Select one or more roles to assign to the new node." } { " " }
427
+ Copy the join command and run it on the machine you'd like to join to
428
+ the cluster.
429
+ </ p >
430
+ </ div >
431
+ ) ;
432
+ } ;
433
+
434
+ const AddNodeCommands = ( ) => {
435
+ return (
436
+ < >
437
+ { rolesLoading && (
438
+ < p className = "tw-text-base tw-w-full tw-text-center tw-py-4 tw-text-gray-500 tw-font-semibold" >
439
+ Loading roles...
440
+ </ p >
441
+ ) }
442
+ { ! rolesData && rolesError && (
443
+ < p className = "tw-text-base tw-w-full tw-text-center tw-py-4 tw-text-pink-500 tw-font-semibold" >
444
+ { rolesError ?. message || "Unable to fetch roles" }
445
+ </ p >
446
+ ) }
447
+
448
+ { rolesData ?. roles && rolesData . roles . length > 1 && (
449
+ < div className = "tw-flex tw-gap-2 tw-items-center tw-mt-2" >
450
+ < p className = "tw-text-gray-600 tw-font-semibold" > Roles: </ p >
451
+ { rolesData . roles . map ( ( nodeType ) => (
452
+ < div
453
+ key = { nodeType }
454
+ className = { classNames (
455
+ "tw-border-[1px] tw-border-solid tw-border-[#326DE6] tw-rounded tw-px-2 tw-py-2 tw-flex tw-items-center tw-cursor-pointer" ,
456
+ {
457
+ "tw-text-white tw-bg-[#326DE6]" :
458
+ selectedNodeTypes . includes ( nodeType ) ,
459
+ "is-disabled" : determineDisabledState ( ) ,
460
+ "tw-text-[#326DE6] tw-bg-white tw-hover:tw-bg-[#f8fafe]" :
461
+ ! selectedNodeTypes . includes ( nodeType ) ,
462
+ }
463
+ ) }
464
+ onClick = { ( ) => {
465
+ handleSelectNodeType ( nodeType ) ;
466
+ } }
467
+ >
468
+ < label
469
+ htmlFor = { `${ nodeType } NodeType` }
470
+ className = " u-userSelect--none tw-text-gray-600 u-fontSize--normal u-fontWeight--medium tw-text-center tw-flex tw-items-center"
471
+ >
472
+ { selectedNodeTypes . includes ( nodeType ) && (
473
+ < Icon icon = "check" size = { 12 } className = "tw-mr-2" />
474
+ ) }
475
+ < input
476
+ id = { `${ nodeType } NodeType` }
477
+ className = "u-cursor--pointer tw-mr-2 hidden-input"
478
+ type = "checkbox"
479
+ name = { `${ nodeType } NodeType` }
480
+ value = { nodeType }
481
+ disabled = { determineDisabledState ( ) }
482
+ checked = { selectedNodeTypes . includes ( nodeType ) }
483
+ />
484
+ </ label >
485
+ { nodeType }
486
+ </ div >
487
+ ) ) }
488
+ </ div >
489
+ ) }
490
+ < div className = "tw-max-w-[700px]" >
491
+ { selectedNodeTypes . length > 0 && generateAddNodeCommandLoading && (
492
+ < p className = "tw-text-base tw-w-full tw-text-center tw-py-4 tw-text-gray-500 tw-font-semibold" >
493
+ Generating command...
494
+ </ p >
495
+ ) }
496
+ { ! generateAddNodeCommand && generateAddNodeCommandError && (
497
+ < p className = "tw-text-base tw-w-full tw-text-center tw-py-4 tw-text-pink-500 tw-font-semibold" >
498
+ { generateAddNodeCommandError ?. message }
499
+ </ p >
500
+ ) }
501
+ { ! generateAddNodeCommandLoading && generateAddNodeCommand ?. command && (
502
+ < >
503
+ < CodeSnippet
504
+ key = { selectedNodeTypes . toString ( ) }
505
+ language = "bash"
506
+ canCopy = { true }
507
+ onCopyText = {
508
+ < span className = "u-textColor--success" > Copied!</ span >
509
+ }
510
+ >
511
+ { generateAddNodeCommand ?. command }
512
+ </ CodeSnippet >
513
+ </ >
514
+ ) }
515
+ </ div >
516
+ </ >
517
+ ) ;
518
+ } ;
519
+
416
520
return (
417
521
< div className = "EmbeddedClusterManagement--wrapper container u-overflow--auto u-paddingTop--50 tw-font-sans" >
418
522
< KotsPageTitle pageName = "Cluster Management" />
@@ -421,15 +525,32 @@ const EmbeddedClusterManagement = ({
421
525
Nodes
422
526
</ p >
423
527
< div className = "tw-flex tw-gap-6 tw-items-center" >
424
- { Utilities . sessionRolesHasOneOf ( [ rbacRoles . CLUSTER_ADMIN ] ) && (
425
- < button
426
- className = "btn primary tw-ml-auto tw-w-fit tw-h-fit"
427
- onClick = { onAddNodeClick }
428
- >
429
- Add node
430
- </ button >
528
+ { " " }
529
+ { ! Utilities . isInitialAppInstall ( app ) && (
530
+ < div className = "tw-flex tw-gap-6" >
531
+ < p >
532
+ View the nodes in your cluster, generate commands to add nodes
533
+ to the cluster, and view workloads running on each node.
534
+ </ p >
535
+ </ div >
431
536
) }
537
+ { Utilities . sessionRolesHasOneOf ( [ rbacRoles . CLUSTER_ADMIN ] ) &&
538
+ ! Utilities . isInitialAppInstall ( app ) && (
539
+ < button
540
+ className = "btn primary tw-ml-auto tw-w-fit tw-h-fit"
541
+ onClick = { onAddNodeClick }
542
+ >
543
+ Add node
544
+ </ button >
545
+ ) }
432
546
</ div >
547
+ { Utilities . isInitialAppInstall ( app ) && (
548
+ < div className = "tw-mt-4 tw-flex tw-flex-col" >
549
+ < AddNodeInstructions />
550
+ < AddNodeCommands />
551
+ </ div >
552
+ ) }
553
+
433
554
< div className = "flex1 u-overflow--auto card-item" >
434
555
{ nodesLoading && (
435
556
< p className = "tw-text-base tw-w-full tw-text-center tw-py-4 tw-text-gray-500 tw-font-semibold" >
@@ -498,109 +619,13 @@ const EmbeddedClusterManagement = ({
498
619
) }
499
620
</ div >
500
621
{ /* MODALS */ }
501
- < Modal
502
- isOpen = { state . displayAddNode }
503
- onRequestClose = { ( ) => setState ( { displayAddNode : false } ) }
504
- contentLabel = "Add Node"
505
- className = "Modal"
506
- ariaHideApp = { false }
622
+ < AddANodeModal
623
+ displayAddNode = { state . displayAddNode }
624
+ toggleDisplayAddNode = { ( ) => setState ( { displayAddNode : false } ) }
625
+ rolesData = { rolesData }
507
626
>
508
- < div className = "Modal-body tw-flex tw-flex-col tw-gap-4 tw-font-sans" >
509
- < div className = "tw-flex" >
510
- < h1 className = "u-fontSize--largest u-fontWeight--bold u-textColor--primary u-lineHeight--normal u-marginBottom--more" >
511
- Add a Node
512
- </ h1 >
513
- < Icon
514
- icon = "close"
515
- size = { 14 }
516
- className = "tw-ml-auto gray-color clickable close-icon"
517
- onClick = { ( ) => setState ( { displayAddNode : false } ) }
518
- />
519
- </ div >
520
- < p className = "tw-text-base tw-text-gray-600" >
521
- { rolesData ?. roles &&
522
- rolesData . roles . length > 1 &&
523
- "Select one or more roles to assign to the new node. " }
524
- Copy the join command and run it on the machine you'd like to join
525
- to the cluster.
526
- </ p >
527
- { rolesLoading && (
528
- < p className = "tw-text-base tw-w-full tw-text-center tw-py-4 tw-text-gray-500 tw-font-semibold" >
529
- Loading roles...
530
- </ p >
531
- ) }
532
- { ! rolesData && rolesError && (
533
- < p className = "tw-text-base tw-w-full tw-text-center tw-py-4 tw-text-pink-500 tw-font-semibold" >
534
- { rolesError ?. message || "Unable to fetch roles" }
535
- </ p >
536
- ) }
537
- { rolesData ?. roles && rolesData . roles . length > 1 && (
538
- < div className = "tw-grid tw-gap-2 tw-grid-cols-4 tw-auto-rows-auto" >
539
- { rolesData . roles . map ( ( nodeType ) => (
540
- < div
541
- key = { nodeType }
542
- className = { classNames ( "BoxedCheckbox" , {
543
- "is-active" : selectedNodeTypes . includes ( nodeType ) ,
544
- "is-disabled" : determineDisabledState ( ) ,
545
- } ) }
546
- >
547
- < input
548
- id = { `${ nodeType } NodeType` }
549
- className = "u-cursor--pointer hidden-input"
550
- type = "checkbox"
551
- name = { `${ nodeType } NodeType` }
552
- value = { nodeType }
553
- disabled = { determineDisabledState ( ) }
554
- checked = { selectedNodeTypes . includes ( nodeType ) }
555
- onChange = { handleSelectNodeType }
556
- />
557
- < label
558
- htmlFor = { `${ nodeType } NodeType` }
559
- className = "tw-block u-cursor--pointer u-userSelect--none u-textColor--primary u-fontSize--normal u-fontWeight--medium tw-text-center"
560
- >
561
- { nodeType }
562
- </ label >
563
- </ div >
564
- ) ) }
565
- </ div >
566
- ) }
567
- < div >
568
- { selectedNodeTypes . length > 0 && generateAddNodeCommandLoading && (
569
- < p className = "tw-text-base tw-w-full tw-text-center tw-py-4 tw-text-gray-500 tw-font-semibold" >
570
- Generating command...
571
- </ p >
572
- ) }
573
- { ! generateAddNodeCommand && generateAddNodeCommandError && (
574
- < p className = "tw-text-base tw-w-full tw-text-center tw-py-4 tw-text-pink-500 tw-font-semibold" >
575
- { generateAddNodeCommandError ?. message }
576
- </ p >
577
- ) }
578
- { ! generateAddNodeCommandLoading && generateAddNodeCommand ?. command && (
579
- < >
580
- < CodeSnippet
581
- key = { selectedNodeTypes . toString ( ) }
582
- language = "bash"
583
- canCopy = { true }
584
- onCopyText = {
585
- < span className = "u-textColor--success" > Copied!</ span >
586
- }
587
- >
588
- { generateAddNodeCommand ?. command }
589
- </ CodeSnippet >
590
- </ >
591
- ) }
592
- </ div >
593
- { /* buttons */ }
594
- < div className = "tw-w-full tw-flex tw-justify-end tw-gap-2" >
595
- < button
596
- className = "btn secondary large"
597
- onClick = { ( ) => setState ( { displayAddNode : false } ) }
598
- >
599
- Close
600
- </ button >
601
- </ div >
602
- </ div >
603
- </ Modal >
627
+ < AddNodeCommands />
628
+ </ AddANodeModal >
604
629
</ div >
605
630
) ;
606
631
} ;
0 commit comments