mirror of https://github.com/rancher/dashboard.git
Featured carousel (#5585)
* Created featured carousel * Created dummy data for Featured chart * Added style * Added toggle button * Added slider width dynamically * Fixed review comments * Removed not used code * Added link to Carousel slides * Created featured carousel * Created dummy data for Featured chart * Added style * Added toggle button * Added slider width dynamically * Fixed review comments * Removed not used code * Added link to Carousel slides * Merged master * Added featured annotation * Added auto scroll on page load * Fixed filtering of featured charts data * Removed duplicate keys
This commit is contained in:
parent
4861b1e490
commit
cd3a06269c
|
|
@ -0,0 +1,207 @@
|
|||
.theme-dark {
|
||||
// Local variables for reused colors
|
||||
|
||||
//dark sidebar
|
||||
$darkest: #141419;
|
||||
|
||||
//dark body
|
||||
$darker: #1b1c21;
|
||||
|
||||
//dark inputs
|
||||
$dark: #27292e;
|
||||
|
||||
//dark borders and button
|
||||
$medium: #4a4b52;
|
||||
|
||||
// dark disabled,
|
||||
$light: #6c6c76;
|
||||
|
||||
//dark secondary
|
||||
$lighter: #b6b6c2;
|
||||
|
||||
// dark main text
|
||||
$lightest: #ffffff;
|
||||
|
||||
$secondary: $lighter;
|
||||
$disabled: $light;
|
||||
|
||||
//Contrast colors
|
||||
$contrasted-dark: $lightest !default;
|
||||
$contrasted-light: $darkest !default;
|
||||
|
||||
|
||||
--default : #{$dark};
|
||||
--default-text : #{$light};
|
||||
--default-hover-bg : #{darken($dark, 10%)};
|
||||
--default-hover-text : #{saturate($lightest, 20%)};
|
||||
--default-active-bg : #{darken($dark, 25%)};
|
||||
--default-active-text : #{contrast-color(darken($dark, 25%))};
|
||||
--default-border : #($dark);
|
||||
--default-banner-bg : #{rgba($dark, 0.15)};
|
||||
--default-light-bg : #{rgba($dark, 0.05)};
|
||||
--slider-light-bg : #{rgba($darker, 1)};
|
||||
--slider-light-bg-right : #{rgba($darker, 0)};
|
||||
|
||||
--muted : #{$disabled};
|
||||
|
||||
--body-bg : #{$darker};
|
||||
--body-text : #{$lightest};
|
||||
--scrollbar-thumb : #{$medium};
|
||||
--scrollbar-thumb-dropdown : #{$medium};
|
||||
|
||||
--header-bg : #{$darker};
|
||||
--header-border : #{$medium};
|
||||
--header-btn-bg : transparent;
|
||||
--header-btn-text : #{$lightest};
|
||||
--header-input-text : #{$lightest};
|
||||
|
||||
// Header, Footer and Consent banner defaults
|
||||
--banner-text-color : #{$lightest};
|
||||
|
||||
--nav-bg : #{$darkest};
|
||||
--nav-active : var(--primary-active-bg);
|
||||
--nav-border : #{$medium};
|
||||
--nav-hover : var(--primary);
|
||||
--nav-expander-hover : var(--primary-banner-bg);
|
||||
|
||||
--disabled-bg : #{darken($disabled, 10%)};
|
||||
--disabled-text : #{$secondary};
|
||||
--box-bg : #{$darkest};
|
||||
--subtle-border : #{$darkest};
|
||||
--border : #{$medium};
|
||||
|
||||
--topmenu-bg : #{$darkest};
|
||||
--topmenu-text : #{$lightest};
|
||||
--topmost-border : #{$medium};
|
||||
--topmost-shadow : #{lighten($darkest, 5%)};
|
||||
--topmost-light-hover : #{$medium};
|
||||
|
||||
--accent-btn : var(--primary-banner-bg);
|
||||
--accent-btn-hover : var(--primary);
|
||||
--accent-btn-hover-text : #{$lightest};
|
||||
|
||||
--modal-bg : #{$dark};
|
||||
--modal-border : #{$medium};
|
||||
--overlay-bg : #{rgba($darkest, 0.75)};
|
||||
--shadow : #{rgba($darkest, 0.9)};
|
||||
|
||||
--checkbox-tick : #{$lightest};
|
||||
--checkbox-border : #{$medium};
|
||||
--checkbox-tick-disabled : #{lighten($disabled, 50%)};
|
||||
--checkbox-disabled-bg : #{$disabled};
|
||||
--checkbox-ticked-bg : var(--primary);
|
||||
|
||||
--dropdown-bg : #{mix($medium, $dark, 10%)};
|
||||
--dropdown-border : #{$light};
|
||||
--dropdown-divider : #{$light};
|
||||
--dropdown-text : #{$link};
|
||||
--dropdown-active-text : #{$lightest};
|
||||
--dropdown-active-bg : #{$selected};
|
||||
--dropdown-hover-text : #{$lightest};
|
||||
--dropdown-hover-bg : #{$link};
|
||||
--dropdown-disabled-bg : #{$disabled};
|
||||
--dropdown-disabled-text : #{$disabled};
|
||||
|
||||
--input-text : #{$lightest};
|
||||
--input-label : #{$lighter};
|
||||
--input-placeholder : #{$disabled};
|
||||
--input-border : var(--border);
|
||||
--input-bg : var(--body-bg);
|
||||
--input-bg-accent : #{darken($dark, 3%)};
|
||||
--input-hover-bg : var(--box-bg);
|
||||
--input-focus-bg : var(--box-bg);
|
||||
--input-disabled-text : #{darken($lightest, 50%)};
|
||||
--input-disabled-label : #{darken($lighter, 30%)};
|
||||
--input-disabled-bg : #{darken($disabled, 30%)};
|
||||
--input-disabled-border : #{darken($medium, 30%)};
|
||||
--input-disabled-placeholder : #{darken($disabled, 10%)};
|
||||
--input-addon-bg : #{$darker};
|
||||
|
||||
--progress-bg : #{$medium};
|
||||
--progress-divider : #{$lightest};
|
||||
|
||||
--sortable-table-bg : #{lighten($darkest, 10%)};
|
||||
--sortable-table-row-bg : #{$darker};;
|
||||
--sortable-table-header-bg : #{$darkest};
|
||||
--sortable-table-top-divider : var(--border);
|
||||
--sortable-table-hover-bg : #{$darkest};
|
||||
--sortable-table-selected-bg : var(--primary-light-bg);
|
||||
--sortable-table-group-label : #{$lighter};
|
||||
|
||||
--tag-primary : #{$lightest};
|
||||
--tag-bg : #{$medium};
|
||||
|
||||
--popover-bg : var(--body-bg);
|
||||
--popover-border : var(--border);
|
||||
--popover-text : var(--body-text);
|
||||
|
||||
--tooltip-bg : #{$medium};
|
||||
--tooltip-border : var(--tag-primary);
|
||||
--tooltip-text : var(--body-text);
|
||||
--tooltip-text-warning : var(--body-text);
|
||||
|
||||
|
||||
--tabbed-border : #{$medium};
|
||||
--tabbed-sidebar-bg : #{$darkest};
|
||||
--tabbed-container-bg : #{mix($medium, $dark, 20%)};
|
||||
|
||||
--yaml-editor-bg : #{$darkest};
|
||||
|
||||
--diff-border : var(--border);
|
||||
--diff-header-bg : var(--nav-bg);
|
||||
--diff-header-border : var(--border);
|
||||
--diff-header : #{rgba($darkest, 0.3)};
|
||||
--diff-linenum-bg : var(--nav-bg);
|
||||
--diff-linenum : var(--muted);
|
||||
--diff-linenum-border : var(--border);
|
||||
--diff-line-ins-bg : $success;
|
||||
--diff-line-del-bg : #{rgba($error, 0.75)};
|
||||
--diff-del-bg : #{rgba($error, 0.3)};
|
||||
--diff-del-border : #{$error};
|
||||
--diff-ins-bg : #{rgba($success, 0.3)};
|
||||
--diff-ins-border : #{rgba($success, 0.5)};
|
||||
--diff-chg-ins : #{rgba($success, 0.25)};
|
||||
--diff-chg-del : #{rgba($warning, 0.5)};
|
||||
--diff-chg-dela : #{rgba($warning, 1)};
|
||||
--diff-empty-placeholder : #{$darker};
|
||||
|
||||
|
||||
--wm-tabs-bg : #{mix($medium, $dark, 10%)};
|
||||
--wm-tab-bg : #{$darkest};
|
||||
--wm-closer-hover-bg : #{$medium};
|
||||
--wm-tab-active-bg : #{$darker};
|
||||
--wm-title-bg : #{$darkest};
|
||||
--wm-title-border : #{$medium};
|
||||
--wm-body-bg : #{$darkest};
|
||||
--wm-border : black;
|
||||
|
||||
--glance-divider : #{$medium};
|
||||
|
||||
--resource-gauge-back-circle : 74, 75, 82, 0.5;
|
||||
|
||||
--simple-box-bg : #{$darker};
|
||||
--simple-box-border : #{$medium};
|
||||
--simple-box-divider : #{$medium};
|
||||
--simple-box-shadow : none;
|
||||
|
||||
--terminal-bg : var(--wm-body-bg);
|
||||
--terminal-cursor : var(--warning);
|
||||
--terminal-selection : #{$selected};
|
||||
--terminal-text : var(--body-text);
|
||||
|
||||
--logs-bg : var(--wm-body-bg);
|
||||
--logs-highlight : var(--wm-body-bg);
|
||||
--logs-highlight-bg : var(--warning);
|
||||
--logs-text : var(--body-text);
|
||||
|
||||
--gauge-divider : rgba(255, 255, 255, 0.3);
|
||||
--gauge-success-primary : 75, 95, 64;
|
||||
--gauge-success-secondary : 150, 189, 127;
|
||||
--gauge-warning-primary : 218, 195, 66;
|
||||
--gauge-warning-secondary : 109, 98, 33;
|
||||
--gauge-error-primary : 239, 90, 83;
|
||||
--gauge-error-secondary : 120, 45, 42;
|
||||
|
||||
--product-icon : #{$lighter};
|
||||
--product-icon-active : #{$lightest};
|
||||
}
|
||||
|
|
@ -0,0 +1,536 @@
|
|||
// Local variables for reused colors
|
||||
//light main text
|
||||
$darkest : #141419;
|
||||
|
||||
//light secondary
|
||||
$darker : #6C6C76;
|
||||
|
||||
//light disabled
|
||||
$dark : #B6B6C2;
|
||||
|
||||
//light border and buttons
|
||||
$medium : #DCDEE7;
|
||||
|
||||
//light inputs
|
||||
$light : #EEEFF4;
|
||||
|
||||
//light sidebar and box
|
||||
$lighter : #F4F5FA;
|
||||
|
||||
//light body bg
|
||||
$lightest : #FFFFFF;
|
||||
|
||||
//color for items that are not enabled
|
||||
$disabled : $medium;
|
||||
|
||||
$primary : #3D98D3;
|
||||
$secondary : $darker;
|
||||
$link : #3D98D3;
|
||||
|
||||
// Status colors
|
||||
$success : #5D995D;
|
||||
$warning : #DAC342;
|
||||
$error : #F64747;
|
||||
$info : #3D98D3;
|
||||
|
||||
$contrasted-dark: $darkest !default;
|
||||
$contrasted-light: $lightest !default;
|
||||
|
||||
// Text selection color for terminal window (we don't want this to change with the primary color)
|
||||
// The terminal alway uses a light background, so okay to use a fixed color
|
||||
$selected: rgba(#3D98D3, .5);
|
||||
|
||||
BODY, .theme-light {
|
||||
|
||||
--primary : #{$primary};
|
||||
--primary-text : #{contrast-color($primary)};
|
||||
--primary-hover-bg : #{darken($primary, 10%)};
|
||||
--primary-hover-text : #{saturate($lightest, 20%)};
|
||||
--primary-active-bg : #{darken($primary, 25%)};
|
||||
--primary-active-text : #{contrast-color(darken($primary, 25%))};
|
||||
--primary-border : #{$primary};
|
||||
--primary-banner-bg : #{rgba($primary, 0.15)};
|
||||
--primary-light-bg : #{rgba($primary, 0.05)};
|
||||
|
||||
|
||||
.text-primary {
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
|
||||
.bg-primary {
|
||||
background-color: var(--primary);
|
||||
color: var(--primary-text);
|
||||
|
||||
&.btn:hover {
|
||||
color: var(--primary-hover-text);
|
||||
background: var(--primary-hover-bg);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&.btn:active {
|
||||
color: var(--primary-active-text);
|
||||
background: var(--primary-active-bg);
|
||||
}
|
||||
}
|
||||
|
||||
--link : #{$link};
|
||||
--link-text : #{contrast-color($link)};
|
||||
--link-hover-bg : #{darken($link, 10%)};
|
||||
--link-hover-text : #{saturate($lightest, 20%)};
|
||||
--link-active-bg : #{darken($link, 25%)};
|
||||
--link-active-text : #{contrast-color(darken($link, 25%))};
|
||||
--link-border : #{$link};
|
||||
--link-banner-bg : #{rgba($link, 0.15)};
|
||||
--link-light-bg : #{rgba($link, 0.05)};
|
||||
|
||||
|
||||
|
||||
.text-link {
|
||||
color: var(--link) !important;
|
||||
}
|
||||
|
||||
.bg-link {
|
||||
background-color: var(--link);
|
||||
color: var(--link-text);
|
||||
|
||||
&.btn:hover {
|
||||
color: var(--link-hover-text);
|
||||
background: var(--link-hover-bg);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&.btn:active {
|
||||
color: var(--link-active-text);
|
||||
background: var(--link-active-bg);
|
||||
}
|
||||
}
|
||||
|
||||
--default : #{$light};
|
||||
--default-text : #{contrast-color($light)};
|
||||
--default-hover-bg : #{darken($light, 10%)};
|
||||
--default-hover-text : #{saturate($lightest, 20%)};
|
||||
--default-active-bg : #{darken($light, 25%)};
|
||||
--default-active-text : #{contrast-color(darken($light, 25%))};
|
||||
--default-border : #{$light};
|
||||
--default-banner-bg : #{rgba($light, 0.15)};
|
||||
--default-light-bg : #{rgba($light, 0.05)};
|
||||
--slider-light-bg : #{rgba($lightest, 1)};
|
||||
--slider-light-bg-right : #{rgba($lightest, 0)};
|
||||
|
||||
|
||||
.text-default {
|
||||
color: var(--default) !important;
|
||||
}
|
||||
|
||||
.bg-default {
|
||||
background-color: var(--default);
|
||||
color: var(--default-text);
|
||||
|
||||
&.btn:hover {
|
||||
color: var(--default-hover-text);
|
||||
background: var(--default-hover-bg);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&.btn:active {
|
||||
color: var(--default-active-text);
|
||||
background: var(--default-active-bg);
|
||||
}
|
||||
}
|
||||
|
||||
--muted : #{$dark};
|
||||
|
||||
.text-muted {
|
||||
color: var(--muted) !important;
|
||||
}
|
||||
|
||||
--darker : #{$darker};
|
||||
--darker-text : #{contrast-color($darker)};
|
||||
--darker-hover-bg : #{darken($darker, 10%)};
|
||||
--darker-hover-text : #{saturate($lightest, 20%)};
|
||||
--darker-active-bg : #{darken($darker, 25%)};
|
||||
--darker-active-text : #{contrast-color(darken($darker, 25%))};
|
||||
--darker-border : #{$darker};
|
||||
--darker-banner-bg : #{rgba($darker, 0.15)};
|
||||
--darker-light-bg : #{rgba($darker, 0.05)};
|
||||
|
||||
.text-darker {
|
||||
color: var(--default) !important;
|
||||
}
|
||||
|
||||
.bg-darker {
|
||||
background-color: var(--darker);
|
||||
color: var(--darker-text);
|
||||
|
||||
&.btn:hover {
|
||||
color: var(--darker-hover-text);
|
||||
background: var(--darker-hover-bg);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&.btn:active {
|
||||
color: var(--darker-active-text);
|
||||
background: var(--darker-active-bg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
--success : #{$success};
|
||||
--success-text : #{contrast-color($success)};
|
||||
--success-hover-bg : #{darken($success, 10%)};
|
||||
--success-hover-text : #{saturate($lightest, 20%)};
|
||||
--success-active-bg : #{darken($success, 25%)};
|
||||
--success-active-text : #{contrast-color(darken($success, 25%))};
|
||||
--success-border : #{$success};
|
||||
--success-banner-bg : #{rgba($success, 0.15)};
|
||||
--success-light-bg : #{rgba($success, 0.05)};
|
||||
|
||||
.text-success {
|
||||
color: var(--success) !important;
|
||||
}
|
||||
|
||||
.bg-success {
|
||||
background-color: var(--success);
|
||||
color: var(--success-text);
|
||||
|
||||
&.btn:hover {
|
||||
color: var(--success-hover-text);
|
||||
background: var(--success-hover-bg);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&.btn:active {
|
||||
color: var(--success-active-text);
|
||||
background: var(--success-active-bg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--info : #{$info};
|
||||
--info-text : #{contrast-color($info)};
|
||||
--info-hover-bg : #{darken($info, 10%)};
|
||||
--info-hover-text : #{saturate($lightest, 20%)};
|
||||
--info-active-bg : #{darken($info, 25%)};
|
||||
--info-active-text : #{contrast-color(darken($info, 25%))};
|
||||
--info-border : #{$info};
|
||||
--info-banner-bg : #{rgba($info, 0.15)};
|
||||
--info-light-bg : #{rgba($info, 0.05)};
|
||||
|
||||
.text-info {
|
||||
color: var(--info) !important;
|
||||
}
|
||||
|
||||
.bg-info {
|
||||
background-color: var(--info);
|
||||
color: var(--info-text);
|
||||
|
||||
&.btn:hover {
|
||||
color: var(--info-hover-text);
|
||||
background: var(--info-hover-bg);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&.btn:active {
|
||||
color: var(--info-active-text);
|
||||
background: var(--info-active-bg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--warning : #{$warning};
|
||||
--warning-text : #{contrast-color($warning)};
|
||||
--warning-hover-bg : #{darken($warning, 10%)};
|
||||
--warning-hover-text : #{saturate($lightest, 20%)};
|
||||
--warning-active-bg : #{darken($warning, 25%)};
|
||||
--warning-active-text : #{contrast-color(darken($warning, 25%))};
|
||||
--warning-border : #{$warning};
|
||||
--warning-banner-bg : #{rgba($warning, 0.15)};
|
||||
--warning-light-bg : #{rgba($warning, 0.05)};
|
||||
|
||||
.text-warning {
|
||||
color: var(--error) !important;
|
||||
}
|
||||
|
||||
.bg-warning {
|
||||
background-color: var(--warning);
|
||||
color: var(--warning-text);
|
||||
|
||||
&.btn:hover {
|
||||
color: var(--warning-hover-text);
|
||||
background: var(--warning-hover-bg);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&.btn:active {
|
||||
color: var(--warning-active-text);
|
||||
background: var(--warning-active-bg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--error : #{$error};
|
||||
--error-text : #{contrast-color($error)};
|
||||
--error-hover-bg : #{darken($error, 10%)};
|
||||
--error-hover-text : #{saturate($lightest, 20%)};
|
||||
--error-active-bg : #{darken($error, 25%)};
|
||||
--error-active-text : #{contrast-color(darken($error, 25%))};
|
||||
--error-border : #{$error};
|
||||
--error-banner-bg : #{rgba($error, 0.15)};
|
||||
--error-light-bg : #{rgba($error, 0.05)};
|
||||
|
||||
|
||||
.text-error {
|
||||
color: var(--error) !important;
|
||||
}
|
||||
|
||||
.bg-error {
|
||||
background-color: var(--error);
|
||||
color: var(--error-text);
|
||||
|
||||
&.btn:hover {
|
||||
color: var(--error-hover-text);
|
||||
background: var(--error-hover-bg);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&.btn:active {
|
||||
color: var(--error-active-text);
|
||||
background: var(--error-active-bg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--body-bg : #{$lightest};
|
||||
--body-text : #{$darkest};
|
||||
--scrollbar-thumb : #{$dark};
|
||||
--scrollbar-thumb-dropdown : #{$lighter};
|
||||
--scrollbar-track : transparent;
|
||||
|
||||
// Header, Footer and Consent banner defaults
|
||||
--banner-text-color : #{$darkest};
|
||||
|
||||
--header-bg : #{$lightest};
|
||||
--header-btn-bg : transparent;
|
||||
--header-btn-text : #{$darkest};
|
||||
--header-input-text : #{$darkest};
|
||||
--header-height : 55px;
|
||||
--header-border : #{$medium};
|
||||
--header-border-size : 1px;
|
||||
--nav-width : 230px;
|
||||
--nav-bg : #{$lightest};
|
||||
--nav-active : #{$light};
|
||||
--nav-hover : #{$medium};
|
||||
--nav-expander-hover : #{darken($medium, 10%)};
|
||||
--nav-border : #{$medium};
|
||||
--nav-border-size : 1px;
|
||||
|
||||
--footer-bg : transparent;
|
||||
--footer-height : 0px;
|
||||
|
||||
--topmenu-bg : #{$lightest};
|
||||
--topmenu-text : #{$darkest};
|
||||
--topmost-border : #{$medium};
|
||||
--topmost-shadow : #{lighten($lightest, 10%)};
|
||||
--topmost-light-hover : #{$light};
|
||||
|
||||
--disabled-bg : #{$disabled};
|
||||
--disabled-text : #{$secondary};
|
||||
--box-bg : #{$lighter};
|
||||
--subtle-border : #{$medium};
|
||||
--border : #{$medium};
|
||||
--border-width : 1px;
|
||||
--border-radius : 4px;
|
||||
--outline : var(--primary);
|
||||
--outline-width : 1px;
|
||||
|
||||
--accent-btn : var(--primary-banner-bg);
|
||||
--accent-btn-hover : var(--primary);
|
||||
--accent-btn-hover-text : #{$lightest};
|
||||
|
||||
--modal-bg : #{$lightest};
|
||||
--modal-border : #{$dark};
|
||||
--overlay-bg : #{rgba($lighter, 0.75)};
|
||||
--shadow : #{rgba($medium, 0.85)};
|
||||
|
||||
--checkbox-tick : #{$lightest};
|
||||
--checkbox-border : #{$medium};
|
||||
--checkbox-tick-disabled : #{darken($disabled, 40%)};
|
||||
--checkbox-disabled-bg : #{$disabled};
|
||||
--checkbox-ticked-bg : #{$link};
|
||||
|
||||
--dropdown-bg : #{$lightest};
|
||||
--dropdown-border : #{$medium};
|
||||
--dropdown-divider : #{$medium};
|
||||
--dropdown-text : #{$link};
|
||||
--dropdown-active-text : #{$lightest};
|
||||
--dropdown-active-bg : #{$dark};
|
||||
--dropdown-hover-text : #{$lightest};
|
||||
--dropdown-hover-bg : var(--primary);
|
||||
--dropdown-disabled-text : var(--muted);
|
||||
--dropdown-disabled-bg : #{$disabled};
|
||||
|
||||
--card-badge-text : #ffffff;
|
||||
|
||||
// UNUSED?
|
||||
--card-header : var(--primary-banner-bg);
|
||||
|
||||
--input-text : #{$darkest};
|
||||
--input-label : #{$secondary};
|
||||
--input-placeholder : #{darken($disabled, 10%)};
|
||||
--input-border : var(--border);
|
||||
--input-bg : var(--body-bg);
|
||||
--input-bg-accent : #{darken($light, 2%)};
|
||||
--input-hover-bg : var(--box-bg);
|
||||
--input-focus-bg : var(--box-bg);
|
||||
--input-disabled-text : #{darken($disabled, 60%)};
|
||||
--input-disabled-label : #{darken($disabled, 40%)};
|
||||
--input-disabled-bg : #{$disabled};
|
||||
--input-disabled-border : #{darken($medium, 10%)};
|
||||
--input-disabled-placeholder : #{darken($medium, 15%)};
|
||||
--input-addon-bg : #{$darker};
|
||||
--input-hover-border : #{darken($medium, 25%)};
|
||||
|
||||
--progress-bg : #{$medium};
|
||||
--progress-divider : #{$medium};
|
||||
|
||||
--sortable-table-bg : #{darken($lightest, 5%)};
|
||||
--sortable-table-row-bg : #{$lightest};
|
||||
--sortable-table-header-bg : #{$lighter};
|
||||
--sortable-table-top-divider : var(--border);
|
||||
--sortable-table-body-divider : #{$medium};
|
||||
|
||||
--sortable-table-hover-bg : #{$lighter};
|
||||
//--sortable-table-selected-bg : #{rgba($primary, 0.02)};
|
||||
|
||||
--sortable-table-selected-bg : var(--primary-light-bg);
|
||||
--sortable-table-group-label : #{$secondary};
|
||||
|
||||
--tag-primary : #{$darkest};
|
||||
--tag-bg : #{$medium};
|
||||
|
||||
--popover-bg : var(--body-bg);
|
||||
--popover-border : var(--border);
|
||||
--popover-text : var(--body-text);
|
||||
--popover-border-radius : var(--border-radius);
|
||||
--tooltip-bg : #{$medium};
|
||||
--tooltip-border : var(--tag-primary);
|
||||
--tooltip-text : var(--tag-primary);
|
||||
--tooltip-bg-warning : #{rgba($warning, 0.8)};
|
||||
--tooltip-text-warning : var(--body-text);
|
||||
|
||||
--tabbed-border : #{$medium};
|
||||
--tabbed-sidebar-bg : #{$lighter};
|
||||
--tabbed-container-bg : #{mix($light, $lighter, 15%)};
|
||||
|
||||
--yaml-editor-bg : #{$lighter};
|
||||
|
||||
--diff-border : var(--border);
|
||||
--diff-header-bg : var(--nav-bg);
|
||||
--diff-header-border : var(--border);
|
||||
--diff-header : #{rgba($darkest, 0.3)};
|
||||
--diff-linenum-bg : var(--nav-bg);
|
||||
--diff-linenum : var(--muted);
|
||||
--diff-linenum-border : var(--border);
|
||||
--diff-line-ins-bg : $success;
|
||||
--diff-line-del-bg : #{rgba($error, 0.75)};
|
||||
--diff-del-bg : #{rgba($error, 0.3)};
|
||||
--diff-del-border : #{$error};
|
||||
--diff-ins-bg : #{rgba($success, 0.3)};
|
||||
--diff-ins-border : #{rgba($success, 0.5)};
|
||||
--diff-chg-ins : #{rgba($success, 0.25)};
|
||||
--diff-chg-del : #{rgba($warning, 0.5)};
|
||||
--diff-empty-placeholder : #{$lightest};
|
||||
|
||||
--wm-tabs-bg : #{$medium};
|
||||
--wm-tab-bg : #{$light};
|
||||
--wm-closer-hover-bg : #{$lighter};
|
||||
--wm-tab-active-bg : #{$lighter};
|
||||
--wm-title-bg : #{$lightest};
|
||||
--wm-title-border : #{$medium};
|
||||
--wm-body-bg : #{$lighter};
|
||||
--wm-border : var(--border);
|
||||
--wm-tab-height : 29px;
|
||||
|
||||
--glance-bg-rgb : 61, 152, 211;
|
||||
--glance-divider : #{$medium};
|
||||
|
||||
--resource-gauge-back-circle : 255, 255, 255, 0.15;
|
||||
|
||||
--simple-box-bg : #{$lightest};
|
||||
--simple-box-border : #{$medium};
|
||||
--simple-box-divider : #{$medium};
|
||||
--simple-box-shadow : none;
|
||||
|
||||
--terminal-bg : var(--body-bg);
|
||||
--terminal-cursor : var(--warning);
|
||||
--terminal-selection : #{$selected};
|
||||
--terminal-text : var(--body-text);
|
||||
|
||||
--logs-bg : var(--wm-body-bg);
|
||||
--logs-highlight : var(--wm-body-bg);
|
||||
--logs-highlight-bg : var(--warning);
|
||||
--logs-text : var(--body-text);
|
||||
|
||||
--gauge-divider : #{$lightest};
|
||||
--gauge-zero : #{$medium};
|
||||
--gauge-success-primary : 150, 189, 127;
|
||||
--gauge-success-secondary : 190, 211, 172;
|
||||
--gauge-warning-primary : 238, 226, 176;
|
||||
--gauge-warning-secondary : 218, 195, 66;
|
||||
--gauge-error-primary : 249, 186, 171;
|
||||
--gauge-error-secondary : 239, 90, 83;
|
||||
|
||||
--sizzle-0 : 180, 210, 30;
|
||||
--sizzle-1 : 225, 45, 74;
|
||||
--sizzle-2 : 212, 66, 148;
|
||||
--sizzle-3 : 0, 169, 217;
|
||||
--sizzle-4 : 244, 136, 68;
|
||||
--sizzle-5 : 0, 147, 128;
|
||||
--sizzle-6 : 136, 81, 165;
|
||||
--sizzle-7 : 45, 47, 149;
|
||||
--sizzle-8 : 255, 235, 0;
|
||||
|
||||
--sizzle-success : #{red($success)}, #{green($success)}, #{blue($success)};
|
||||
--sizzle-info : #{red($info)}, #{green($info)}, #{blue($info)};
|
||||
--sizzle-warning : #{red($warning)}, #{green($warning)}, #{blue($warning)};
|
||||
--sizzle-error : #{red($error)}, #{green($error)}, #{blue($error)};
|
||||
--sizzle-unknown : #{red($disabled)},#{green($disabled)},#{blue($disabled)};
|
||||
|
||||
$rancher : $primary;
|
||||
$partner : #FEA424;
|
||||
$other : #614EA2;
|
||||
|
||||
--app-rancher-accent : #{$rancher};
|
||||
--app-rancher-accent-text : #ffffff;
|
||||
|
||||
--app-partner-accent : #{$partner};
|
||||
--app-partner-accent-text : black;
|
||||
|
||||
--app-color1-accent : rgba(var(--sizzle-1), 1);
|
||||
--app-color1-accent-text : white;
|
||||
|
||||
--app-color2-accent : rgba(var(--sizzle-2), 1);
|
||||
--app-color2-accent-text : white;
|
||||
|
||||
--app-color3-accent : rgba(var(--sizzle-3), 1);
|
||||
--app-color3-accent-text : white;
|
||||
|
||||
--app-color4-accent : rgba(var(--sizzle-4), 1);
|
||||
--app-color4-accent-text : white;
|
||||
|
||||
--app-color5-accent : rgba(var(--sizzle-5), 1);
|
||||
--app-color5-accent-text : white;
|
||||
|
||||
--app-color6-accent : rgba(var(--sizzle-6), 1);
|
||||
--app-color6-accent-text : white;
|
||||
|
||||
--app-color7-accent : rgba(var(--sizzle-7), 1);
|
||||
--app-color7-accent-text : white;
|
||||
|
||||
--app-color8-accent : rgba(var(--sizzle-8), 1);
|
||||
--app-color8-accent-text : white;
|
||||
|
||||
--product-icon : #{$darker};
|
||||
--product-icon-active : #{$darkest};
|
||||
}
|
||||
|
|
@ -0,0 +1,661 @@
|
|||
<script>
|
||||
import AsyncButton from '@/components/AsyncButton';
|
||||
import Loading from '@/components/Loading';
|
||||
import Banner from '@/components/Banner';
|
||||
import Carousel from '@/components/Carousel';
|
||||
import ButtonGroup from '@/components/ButtonGroup';
|
||||
import SelectIconGrid from '@/components/SelectIconGrid';
|
||||
import TypeDescription from '@/components/TypeDescription';
|
||||
import {
|
||||
REPO_TYPE, REPO, CHART, VERSION, SEARCH_QUERY, _FLAGGED, CATEGORY, DEPRECATED, HIDDEN, OPERATING_SYSTEM
|
||||
} from '@/config/query-params';
|
||||
import { lcFirst } from '@/utils/string';
|
||||
import { sortBy } from '@/utils/sort';
|
||||
import { mapGetters } from 'vuex';
|
||||
import Checkbox from '@/components/form/Checkbox';
|
||||
import Select from '@/components/form/Select';
|
||||
import { mapPref, HIDE_REPOS, SHOW_PRE_RELEASE, SHOW_CHART_MODE } from '@/store/prefs';
|
||||
import { removeObject, addObject, findBy } from '@/utils/array';
|
||||
import { compatibleVersionsFor, filterAndArrangeCharts } from '@/store/catalog';
|
||||
import { CATALOG } from '@/config/labels-annotations';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AsyncButton,
|
||||
Banner,
|
||||
Carousel,
|
||||
ButtonGroup,
|
||||
Loading,
|
||||
Checkbox,
|
||||
Select,
|
||||
SelectIconGrid,
|
||||
TypeDescription
|
||||
},
|
||||
|
||||
async fetch() {
|
||||
await this.$store.dispatch('catalog/load');
|
||||
|
||||
const query = this.$route.query;
|
||||
|
||||
this.searchQuery = query[SEARCH_QUERY] || '';
|
||||
this.showDeprecated = query[DEPRECATED] === _FLAGGED;
|
||||
this.showHidden = query[HIDDEN] === _FLAGGED;
|
||||
this.category = query[CATEGORY] || '';
|
||||
this.allRepos = this.areAllEnabled();
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
allRepos: null,
|
||||
category: null,
|
||||
operatingSystem: null,
|
||||
searchQuery: null,
|
||||
showDeprecated: null,
|
||||
showHidden: null,
|
||||
chartMode: this.$store.getters['prefs/get'](SHOW_CHART_MODE),
|
||||
chartOptions: [
|
||||
{
|
||||
label: 'Browse',
|
||||
value: 'browse',
|
||||
},
|
||||
{
|
||||
label: 'Featured',
|
||||
value: 'featured'
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
||||
...mapGetters(['currentCluster']),
|
||||
...mapGetters({ allCharts: 'catalog/charts', loadingErrors: 'catalog/errors' }),
|
||||
|
||||
hideRepos: mapPref(HIDE_REPOS),
|
||||
|
||||
viewOptions() {
|
||||
const out = [];
|
||||
|
||||
if ( this.hasDetail ) {
|
||||
out.push({
|
||||
labelKey: 'resourceDetail.masthead.detail',
|
||||
value: 'detail',
|
||||
});
|
||||
}
|
||||
|
||||
if ( this.hasEdit ) {
|
||||
out.push({
|
||||
labelKey: 'resourceDetail.masthead.config',
|
||||
value: 'config',
|
||||
});
|
||||
}
|
||||
|
||||
if ( !out.length ) {
|
||||
// If there's only YAML, return nothing and the button group will be hidden entirely
|
||||
return null;
|
||||
}
|
||||
|
||||
out.push({
|
||||
labelKey: 'resourceDetail.masthead.yaml',
|
||||
value: 'yaml',
|
||||
});
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
repoOptions() {
|
||||
let nextColor = 0;
|
||||
// Colors 3 and 4 match `rancher` and `partner` colors, so just avoid them
|
||||
const colors = [1, 2, 5, 6, 7, 8];
|
||||
|
||||
let out = this.$store.getters['catalog/repos'].map((r) => {
|
||||
return {
|
||||
_key: r._key,
|
||||
label: r.nameDisplay,
|
||||
color: r.color,
|
||||
weight: ( r.isRancher ? 1 : ( r.isPartner ? 2 : 3 ) ),
|
||||
enabled: !this.hideRepos.includes(r._key),
|
||||
};
|
||||
});
|
||||
|
||||
out = sortBy(out, ['weight', 'label']);
|
||||
|
||||
for ( const entry of out ) {
|
||||
if ( !entry.color ) {
|
||||
entry.color = `color${ colors[nextColor] }`;
|
||||
nextColor++;
|
||||
if ( nextColor >= colors.length ) {
|
||||
nextColor = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
repoOptionsForDropdown() {
|
||||
return [{
|
||||
label: this.t('catalog.repo.all'), all: true, enabled: this.areAllEnabled()
|
||||
}, ...this.repoOptions];
|
||||
},
|
||||
|
||||
flattenedRepoNames() {
|
||||
const allChecked = this.repoOptionsForDropdown.find(repo => repo.all && repo.enabled);
|
||||
|
||||
if (allChecked) {
|
||||
return allChecked.label;
|
||||
}
|
||||
|
||||
// None checked
|
||||
if (!this.repoOptionsForDropdown.find(repo => repo.enabled)) {
|
||||
return this.t('generic.none');
|
||||
}
|
||||
|
||||
const shownRepos = this.repoOptions.filter(repo => !this.hideRepos.includes(repo._key));
|
||||
const reducedRepos = shownRepos.reduce((acc, c, i) => {
|
||||
acc += c.label;
|
||||
const length = shownRepos.length;
|
||||
|
||||
if (i < length - 1) {
|
||||
acc += ', ';
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, '');
|
||||
|
||||
return reducedRepos;
|
||||
},
|
||||
|
||||
enabledCharts() {
|
||||
return (this.allCharts || []).filter((c) => {
|
||||
if ( c.deprecated && !this.showDeprecated ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( c.hidden && !this.showHidden ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( this.hideRepos.includes(c.repoKey) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
},
|
||||
|
||||
filteredCharts() {
|
||||
const enabledCharts = (this.enabledCharts || []);
|
||||
|
||||
return filterAndArrangeCharts(enabledCharts, {
|
||||
category: this.category,
|
||||
searchQuery: this.searchQuery,
|
||||
showDeprecated: this.showDeprecated,
|
||||
showHidden: this.showHidden,
|
||||
hideRepos: this.hideRepos,
|
||||
hideTypes: [CATALOG._CLUSTER_TPL],
|
||||
showPrerelease: this.$store.getters['prefs/get'](SHOW_PRE_RELEASE),
|
||||
});
|
||||
},
|
||||
|
||||
getFeaturedCharts() {
|
||||
const newArray = (this.filteredCharts || []);
|
||||
|
||||
return newArray.slice(0, 5);
|
||||
},
|
||||
|
||||
categories() {
|
||||
const map = {};
|
||||
|
||||
for ( const chart of this.enabledCharts ) {
|
||||
for ( const c of chart.categories ) {
|
||||
if ( !map[c] ) {
|
||||
const labelKey = `catalog.charts.categories.${ lcFirst(c) }`;
|
||||
|
||||
map[c] = {
|
||||
label: this.$store.getters['i18n/withFallback'](labelKey, null, c),
|
||||
value: c,
|
||||
count: 0
|
||||
};
|
||||
}
|
||||
|
||||
map[c].count++;
|
||||
}
|
||||
}
|
||||
|
||||
const out = Object.values(map);
|
||||
|
||||
out.unshift({
|
||||
label: this.t('catalog.charts.categories.all'),
|
||||
value: '',
|
||||
count: this.enabledCharts.length
|
||||
});
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
showCarousel() {
|
||||
return this.chartMode === 'featured' && this.getFeaturedCharts.length;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
searchQuery(q) {
|
||||
this.$router.applyQuery({ [SEARCH_QUERY]: q || undefined });
|
||||
},
|
||||
|
||||
category(cat) {
|
||||
this.$router.applyQuery({ [CATEGORY]: cat || undefined });
|
||||
},
|
||||
|
||||
operatingSystem(os) {
|
||||
this.$router.applyQuery({ [OPERATING_SYSTEM]: os || undefined });
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if ( typeof window !== 'undefined' ) {
|
||||
window.c = this;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
colorForChart(chart) {
|
||||
const repos = this.repoOptions;
|
||||
const repo = findBy(repos, '_key', chart.repoKey);
|
||||
|
||||
if ( repo ) {
|
||||
return repo.color;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
toggleAll(on) {
|
||||
for ( const r of this.repoOptions ) {
|
||||
this.toggleRepo(r, on, false);
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.allRepos = this.areAllEnabled();
|
||||
});
|
||||
},
|
||||
|
||||
areAllEnabled() {
|
||||
const all = this.$store.getters['catalog/repos'];
|
||||
|
||||
for ( const r of all ) {
|
||||
if ( this.hideRepos.includes(r._key) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
toggleRepo(repo, on, updateAll = true) {
|
||||
const hidden = this.hideRepos;
|
||||
|
||||
if ( on ) {
|
||||
removeObject(hidden, repo._key);
|
||||
} else {
|
||||
addObject(hidden, repo._key);
|
||||
}
|
||||
|
||||
this.hideRepos = hidden;
|
||||
|
||||
if ( updateAll ) {
|
||||
this.allRepos = this.areAllEnabled();
|
||||
}
|
||||
},
|
||||
|
||||
selectChart(chart) {
|
||||
let version;
|
||||
const OSs = this.currentCluster.workerOSs;
|
||||
const showPrerelease = this.$store.getters['prefs/get'](SHOW_PRE_RELEASE);
|
||||
const compatibleVersions = compatibleVersionsFor(chart, OSs, showPrerelease);
|
||||
const versions = chart.versions;
|
||||
|
||||
if (compatibleVersions.length > 0) {
|
||||
version = compatibleVersions[0].version;
|
||||
} else {
|
||||
version = versions[0].version;
|
||||
}
|
||||
|
||||
this.$router.push({
|
||||
name: 'c-cluster-apps-charts-chart',
|
||||
params: {
|
||||
cluster: this.$route.params.cluster,
|
||||
product: this.$store.getters['productId'],
|
||||
},
|
||||
query: {
|
||||
[REPO_TYPE]: chart.repoType,
|
||||
[REPO]: chart.repoName,
|
||||
[CHART]: chart.chartName,
|
||||
[VERSION]: version,
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
focusSearch() {
|
||||
if ( this.$refs.searchQuery ) {
|
||||
this.$refs.searchQuery.focus();
|
||||
this.$refs.searchQuery.select();
|
||||
}
|
||||
},
|
||||
|
||||
async refresh(btnCb) {
|
||||
try {
|
||||
await this.$store.dispatch('catalog/refresh');
|
||||
btnCb(true);
|
||||
} catch (e) {
|
||||
this.$store.dispatch('growl/fromError', e);
|
||||
btnCb(false);
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Loading v-if="$fetchState.pending" />
|
||||
<div v-else>
|
||||
<header>
|
||||
<div class="title">
|
||||
<h1 class="m-0">
|
||||
{{ t('catalog.charts.header') }}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="actions-container">
|
||||
<ButtonGroup
|
||||
v-model="chartMode"
|
||||
:options="chartOptions"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
<Carousel
|
||||
v-if="showCarousel"
|
||||
:sliders="getFeaturedCharts"
|
||||
@clicked="(row) => selectChart(row)"
|
||||
/>
|
||||
|
||||
<TypeDescription resource="chart" />
|
||||
<div class="left-right-split">
|
||||
<Select
|
||||
:searchable="false"
|
||||
:options="repoOptionsForDropdown"
|
||||
:value="flattenedRepoNames"
|
||||
class="checkbox-select"
|
||||
:close-on-select="false"
|
||||
@option:selecting="$event.all ? toggleAll(!$event.enabled) : toggleRepo($event, !$event.enabled) "
|
||||
>
|
||||
<template #selected-option="selected">
|
||||
{{ selected.label }}
|
||||
</template>
|
||||
<template #option="repo">
|
||||
<Checkbox
|
||||
:value="repo.enabled"
|
||||
:label="repo.label"
|
||||
class="pull-left repo in-select"
|
||||
:class="{ [repo.color]: true}"
|
||||
:color="repo.color"
|
||||
>
|
||||
<template #label>
|
||||
<span>{{ repo.label }}</span><i v-if="!repo.all" class=" pl-5 icon icon-dot icon-sm" :class="{[repo.color]: true}" />
|
||||
</template>
|
||||
</Checkbox>
|
||||
</template>
|
||||
</Select>
|
||||
|
||||
<Select
|
||||
v-model="category"
|
||||
:clearable="false"
|
||||
:searchable="false"
|
||||
:options="categories"
|
||||
placement="bottom"
|
||||
label="label"
|
||||
style="min-width: 200px;"
|
||||
:reduce="opt => opt.value"
|
||||
>
|
||||
<template #option="opt">
|
||||
{{ opt.label }} ({{ opt.count }})
|
||||
</template>
|
||||
</Select>
|
||||
|
||||
<div class="filter-block">
|
||||
<input
|
||||
ref="searchQuery"
|
||||
v-model="searchQuery"
|
||||
type="search"
|
||||
class="input-sm"
|
||||
:placeholder="t('catalog.charts.search')"
|
||||
>
|
||||
|
||||
<button v-shortkey.once="['/']" class="hide" @shortkey="focusSearch()" />
|
||||
<AsyncButton class="refresh-btn" mode="refresh" size="sm" @click="refresh" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Banner v-for="err in loadingErrors" :key="err" color="error" :label="err" />
|
||||
|
||||
<div v-if="allCharts.length">
|
||||
<div v-if="filteredCharts.length === 0" style="width: 100%;">
|
||||
<div class="m-50 text-center">
|
||||
<h1>{{ t('catalog.charts.noCharts') }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<SelectIconGrid
|
||||
v-else
|
||||
:rows="filteredCharts"
|
||||
name-field="chartNameDisplay"
|
||||
description-field="chartDescription"
|
||||
:color-for="colorForChart"
|
||||
@clicked="(row) => selectChart(row)"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="m-50 text-center">
|
||||
<h1>{{ t('catalog.charts.noCharts') }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.left-right-split {
|
||||
padding: 0 0 20px 0;
|
||||
width: 100%;
|
||||
z-index: z-index('fixedTableHeader');
|
||||
background: transparent;
|
||||
display: grid;
|
||||
grid-template-columns: 40% auto auto;
|
||||
align-content: center;
|
||||
grid-column-gap: 10px;
|
||||
|
||||
.filter-block {
|
||||
display: flex;
|
||||
}
|
||||
.refresh-btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
&.with-os-options {
|
||||
grid-template-columns: 40% auto auto auto;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: map-get($breakpoints, '--viewport-12')) {
|
||||
&{
|
||||
grid-template-columns: auto auto !important;
|
||||
grid-template-rows: 40px 40px;
|
||||
grid-row-gap: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: map-get($breakpoints, '--viewport-7')) {
|
||||
&{
|
||||
&{
|
||||
grid-template-columns: auto !important;
|
||||
grid-template-rows: 40px 40px 40px !important;
|
||||
|
||||
&.with-os-options {
|
||||
grid-template-rows: 40px 40px 40px 40px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-select {
|
||||
.vs__search {
|
||||
position: absolute;
|
||||
right: 0
|
||||
}
|
||||
|
||||
.vs__selected-options {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
line-height: 2.4rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.checkbox-outer-container.in-select {
|
||||
transform: translateX(-5px);
|
||||
padding: 7px 0 6px 13px;
|
||||
width: calc(100% + 10px);
|
||||
|
||||
::v-deep.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& i {
|
||||
line-height: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
&:hover {
|
||||
background: var(--input-hover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover ::v-deep.checkbox-label {
|
||||
color: var(--body-text);
|
||||
}
|
||||
|
||||
&.rancher {
|
||||
&:hover {
|
||||
background: var(--app-rancher-accent);
|
||||
}
|
||||
&:hover ::v-deep.checkbox-label {
|
||||
color: var(--app-rancher-accent-text);
|
||||
}
|
||||
& i {
|
||||
color: var(--app-rancher-accent)
|
||||
}
|
||||
}
|
||||
|
||||
&.partner {
|
||||
&:hover {
|
||||
background: var(--app-partner-accent);
|
||||
}
|
||||
&:hover ::v-deep.checkbox-label {
|
||||
color: var(--app-partner-accent-text);
|
||||
}
|
||||
& i {
|
||||
color: var(--app-partner-accent)
|
||||
}
|
||||
}
|
||||
|
||||
&.color1 {
|
||||
&:hover {
|
||||
background: var(--app-color1-accent);
|
||||
}
|
||||
&:hover ::v-deep.checkbox-label {
|
||||
color: var(--app-color1-accent-text);
|
||||
}
|
||||
& i {
|
||||
color: var(--app-color1-accent)
|
||||
}
|
||||
}
|
||||
&.color2 {
|
||||
&:hover {
|
||||
background: var(--app-color2-accent);
|
||||
}
|
||||
&:hover ::v-deep.checkbox-label {
|
||||
color: var(--app-color2-accent-text);
|
||||
}
|
||||
& i {
|
||||
color: var(--app-color2-accent)
|
||||
}
|
||||
}
|
||||
&.color3 {
|
||||
&:hover {
|
||||
background: var(--app-color3-accent);
|
||||
}
|
||||
&:hover ::v-deep.checkbox-label {
|
||||
color: var(--app-color3-accent-text);
|
||||
}
|
||||
& i {
|
||||
color: var(--app-color3-accent)
|
||||
}
|
||||
}
|
||||
&.color4 {
|
||||
&:hover {
|
||||
background: var(--app-color4-accent);
|
||||
}
|
||||
&:hover ::v-deep.checkbox-label {
|
||||
color: var(--app-color4-accent-text);
|
||||
}
|
||||
& i {
|
||||
color: var(--app-color4-accent)
|
||||
}
|
||||
}
|
||||
&.color5 {
|
||||
&:hover {
|
||||
background: var(--app-color5-accent);
|
||||
}
|
||||
&:hover ::v-deep.checkbox-label {
|
||||
color: var(--app-color5-accent-text);
|
||||
}
|
||||
& i {
|
||||
color: var(--app-color5-accent)
|
||||
}
|
||||
}
|
||||
&.color6 {
|
||||
&:hover {
|
||||
background: var(--app-color6-accent);
|
||||
}
|
||||
&:hover ::v-deep.checkbox-label {
|
||||
color: var(--app-color6-accent-text);
|
||||
}
|
||||
& i {
|
||||
color: var(--app-color6-accent)
|
||||
}
|
||||
}
|
||||
&.color7 {
|
||||
&:hover {
|
||||
background: var(--app-color7-accent);
|
||||
}
|
||||
&:hover ::v-deep.checkbox-label {
|
||||
color: var(--app-color7-accent-text);
|
||||
}
|
||||
& i {
|
||||
color: var(--app-color7-accent)
|
||||
}
|
||||
}
|
||||
&.color8 {
|
||||
&:hover {
|
||||
background: var(--app-color8-accent);
|
||||
}
|
||||
&:hover ::v-deep.checkbox-label {
|
||||
color: var(--app-color8-accent-text);
|
||||
}
|
||||
& i {
|
||||
color: var(--app-color8-accent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
|
|
@ -39,6 +39,8 @@
|
|||
--default-border : #($dark);
|
||||
--default-banner-bg : #{rgba($dark, 0.15)};
|
||||
--default-light-bg : #{rgba($dark, 0.05)};
|
||||
--slider-light-bg : #{rgba($darker, 1)};
|
||||
--slider-light-bg-right : #{rgba($darker, 0)};
|
||||
|
||||
--muted : #{$disabled};
|
||||
|
||||
|
|
@ -160,6 +162,7 @@
|
|||
--diff-ins-border : #{rgba($success, 0.5)};
|
||||
--diff-chg-ins : #{rgba($success, 0.25)};
|
||||
--diff-chg-del : #{rgba($warning, 0.5)};
|
||||
--diff-chg-dela : #{rgba($warning, 1)};
|
||||
--diff-empty-placeholder : #{$darker};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ BODY, .theme-light {
|
|||
--link-border : #{$link};
|
||||
--link-banner-bg : #{rgba($link, 0.15)};
|
||||
--link-light-bg : #{rgba($link, 0.05)};
|
||||
|
||||
|
||||
|
||||
.text-link {
|
||||
|
|
@ -113,6 +114,8 @@ BODY, .theme-light {
|
|||
--default-border : #{$light};
|
||||
--default-banner-bg : #{rgba($light, 0.15)};
|
||||
--default-light-bg : #{rgba($light, 0.05)};
|
||||
--slider-light-bg : #{rgba($lightest, 1)};
|
||||
--slider-light-bg-right : #{rgba($lightest, 0)};
|
||||
|
||||
|
||||
.text-default {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,291 @@
|
|||
<script>
|
||||
import { get } from '@shell/utils/object';
|
||||
import { BadgeState } from '@components/BadgeState';
|
||||
|
||||
export default {
|
||||
components: { BadgeState },
|
||||
name: 'Carousel',
|
||||
props: {
|
||||
sliders: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
keyField: {
|
||||
type: String,
|
||||
default: 'key',
|
||||
},
|
||||
asLink: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
linkField: {
|
||||
type: String,
|
||||
default: 'link'
|
||||
},
|
||||
targetField: {
|
||||
type: String,
|
||||
default: 'target',
|
||||
},
|
||||
rel: {
|
||||
type: String,
|
||||
default: 'noopener noreferrer nofollow'
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
slider: this.sliders,
|
||||
activeItemId: 0,
|
||||
autoScroll: true
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
||||
trackStyle() {
|
||||
const sliderItem = this.activeItemId * 100 / this.slider.length;
|
||||
const width = 60 * this.slider.length;
|
||||
|
||||
return `transform: translateX(-${ sliderItem }%); width: ${ width }%`;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
get,
|
||||
|
||||
select(slide, i) {
|
||||
this.$emit('clicked', slide, i);
|
||||
},
|
||||
|
||||
scrollSlide(i) {
|
||||
this.autoScroll = false;
|
||||
this.activeItemId = i;
|
||||
setTimeout(() => {
|
||||
this.slidePosition();
|
||||
}, 400);
|
||||
},
|
||||
|
||||
nextPrev(item) {
|
||||
this.autoScroll = false;
|
||||
if (item === 'next' && this.activeItemId < this.slider.length - 1) {
|
||||
this.activeItemId++;
|
||||
}
|
||||
|
||||
if (item === 'prev' && this.activeItemId > 0) {
|
||||
this.activeItemId--;
|
||||
}
|
||||
|
||||
this.slidePosition();
|
||||
},
|
||||
|
||||
timer() {
|
||||
setInterval(this.autoScrollSlide, 2000);
|
||||
},
|
||||
autoScrollSlide() {
|
||||
if (this.activeItemId < this.slider.length && this.autoScroll ) {
|
||||
this.activeItemId++;
|
||||
}
|
||||
|
||||
if (this.activeItemId > this.slider.length - 1) {
|
||||
this.autoScroll = false;
|
||||
this.activeItemId = 0;
|
||||
}
|
||||
this.slidePosition();
|
||||
},
|
||||
|
||||
slidePosition() {
|
||||
if (this.activeItemId <= 1) {
|
||||
this.$refs.slide[this.slider.length - 1].style.left = '-93%';
|
||||
this.$refs.slide[0].style.left = '7%';
|
||||
} else {
|
||||
this.$refs.slide[this.slider.length - 1].style.left = '7%';
|
||||
this.$refs.slide[0].style.left = '107%';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.timer();
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="slider">
|
||||
<div id="slide-track" ref="slider" :style="trackStyle" class="slide-track">
|
||||
<div
|
||||
:is="asLink ? 'a' : 'div'"
|
||||
v-for="(slide, i) in sliders"
|
||||
:id="`slide` + i"
|
||||
ref="slide"
|
||||
:key="get(slide, keyField)"
|
||||
class="slide"
|
||||
:href="asLink ? get(slide, linkField) : null"
|
||||
:target="get(slide, targetField)"
|
||||
:rel="rel"
|
||||
@click="select(slide, i)"
|
||||
>
|
||||
<div class="slide-content">
|
||||
<div class="slide-img">
|
||||
<img :src="slide.icon ? slide.icon : `/_nuxt/shell/assets/images/generic-catalog.svg`" />
|
||||
</div>
|
||||
<div class="slide-content-right">
|
||||
<BadgeState :label="slide.repoName" color="slider-badge mb-20" />
|
||||
<h1>{{ slide.chartNameDisplay }}</h1>
|
||||
<p>{{ slide.chartDescription }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<div
|
||||
v-for="(slide, i) in slider"
|
||||
:key="i"
|
||||
class="control-item"
|
||||
:class="{'active': activeItemId === i}"
|
||||
@click="scrollSlide(i, slider.length)"
|
||||
></div>
|
||||
</div>
|
||||
<div ref="prev" class="prev" :class="[activeItemId === 0 ? 'disabled' : 'prev']" @click="nextPrev('prev')">
|
||||
<i class="icon icon-chevron-left icon-4x"></i>
|
||||
</div>
|
||||
<div ref="next" class="next" :class="[activeItemId === slider.length - 1 ? 'disabled' : 'next']" @click="nextPrev('next')">
|
||||
<i class="icon icon-chevron-right icon-4x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.slider {
|
||||
margin: auto;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
place-items: center;
|
||||
overflow: hidden;
|
||||
margin-bottom: 30px;
|
||||
min-width: 700px;
|
||||
|
||||
&:hover {
|
||||
.prev,
|
||||
.next {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.slide-track {
|
||||
display: flex;
|
||||
animation: scrolls 10s ;
|
||||
position: relative;
|
||||
transition: 1s ease-in-out;
|
||||
}
|
||||
|
||||
.slider-badge {
|
||||
background: var(--app-partner-accent);
|
||||
color: var(--body-bg);
|
||||
}
|
||||
.slide {
|
||||
min-height: 210px;
|
||||
width: 60%;
|
||||
max-width: 60%;
|
||||
margin: 0 10px;
|
||||
position: relative;
|
||||
border: 1px solid var(--tabbed-border);
|
||||
border-radius: var(--border-radius);
|
||||
left: 7%;
|
||||
cursor: pointer;
|
||||
|
||||
&:last-child {
|
||||
left: -93%;
|
||||
}
|
||||
|
||||
.slide-header {
|
||||
background: var(--default);
|
||||
width: 100%;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
.slide-content {
|
||||
display: flex;
|
||||
padding: 30px;
|
||||
|
||||
.slide-img {
|
||||
width: 150px;
|
||||
background: var(--card-badge-text);
|
||||
border-radius: calc(2 * var(--border-radius));
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.slide-content-right {
|
||||
border-left: 1px solid var(--tabbed-border);
|
||||
margin-left: 30px;
|
||||
padding-left: 30px;
|
||||
|
||||
span {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.slider::before,
|
||||
.slider::after {
|
||||
background: linear-gradient(to right, var(--slider-light-bg) 0%, var(--slider-light-bg-right) 100%);
|
||||
content: "";
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
width: 15%;
|
||||
z-index: z-index('overContent');
|
||||
}
|
||||
|
||||
.slider::before {
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.slider::after{
|
||||
right: -1px;
|
||||
top: 0;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.controls {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
|
||||
.control-item {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: var(--scrollbar-thumb);
|
||||
margin: 5px;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
background:var(--body-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
.prev,
|
||||
.next {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
top: 90px;
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
|
||||
&.disabled .icon {
|
||||
color: var(--disabled-bg);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.next {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -95,7 +95,7 @@ export const CATALOG = {
|
|||
DEPLOYED_OS: 'catalog.cattle.io/deploys-on-os',
|
||||
|
||||
MIGRATED: 'apps.cattle.io/migrated',
|
||||
MANAGED: 'catalog.cattle.io/managed'
|
||||
MANAGED: 'catalog.cattle.io/managed',
|
||||
};
|
||||
|
||||
export const FLEET = {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
import AsyncButton from '@shell/components/AsyncButton';
|
||||
import Loading from '@shell/components/Loading';
|
||||
import { Banner } from '@components/Banner';
|
||||
import Carousel from '@shell/components/Carousel';
|
||||
import ButtonGroup from '@shell/components/ButtonGroup';
|
||||
import SelectIconGrid from '@shell/components/SelectIconGrid';
|
||||
import TypeDescription from '@shell/components/TypeDescription';
|
||||
import {
|
||||
|
|
@ -12,7 +14,7 @@ import { sortBy } from '@shell/utils/sort';
|
|||
import { mapGetters } from 'vuex';
|
||||
import { Checkbox } from '@components/Form/Checkbox';
|
||||
import Select from '@shell/components/form/Select';
|
||||
import { mapPref, HIDE_REPOS, SHOW_PRE_RELEASE } from '@shell/store/prefs';
|
||||
import { mapPref, HIDE_REPOS, SHOW_PRE_RELEASE, SHOW_CHART_MODE } from '@shell/store/prefs';
|
||||
import { removeObject, addObject, findBy } from '@shell/utils/array';
|
||||
import { compatibleVersionsFor, filterAndArrangeCharts } from '@shell/store/catalog';
|
||||
import { CATALOG } from '@shell/config/labels-annotations';
|
||||
|
|
@ -21,6 +23,8 @@ export default {
|
|||
components: {
|
||||
AsyncButton,
|
||||
Banner,
|
||||
Carousel,
|
||||
ButtonGroup,
|
||||
Loading,
|
||||
Checkbox,
|
||||
Select,
|
||||
|
|
@ -48,6 +52,17 @@ export default {
|
|||
searchQuery: null,
|
||||
showDeprecated: null,
|
||||
showHidden: null,
|
||||
chartMode: this.$store.getters['prefs/get'](SHOW_CHART_MODE),
|
||||
chartOptions: [
|
||||
{
|
||||
label: 'Browse',
|
||||
value: 'browse',
|
||||
},
|
||||
{
|
||||
label: 'Featured',
|
||||
value: 'featured'
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -154,6 +169,14 @@ export default {
|
|||
});
|
||||
},
|
||||
|
||||
getFeaturedCharts() {
|
||||
const allCharts = (this.filteredCharts || []);
|
||||
|
||||
const featuredCharts = allCharts.filter(value => value.featured).sort((a, b) => a.featured - b.featured);
|
||||
|
||||
return featuredCharts.slice(0, 5);
|
||||
},
|
||||
|
||||
categories() {
|
||||
const map = {};
|
||||
|
||||
|
|
@ -184,6 +207,10 @@ export default {
|
|||
return out;
|
||||
},
|
||||
|
||||
showCarousel() {
|
||||
return this.chartMode === 'featured' && this.getFeaturedCharts.length;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
|
@ -313,7 +340,20 @@ export default {
|
|||
{{ t('catalog.charts.header') }}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="actions-container">
|
||||
<ButtonGroup
|
||||
v-model="chartMode"
|
||||
:options="chartOptions"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
<div v-if="showCarousel">
|
||||
<h3>Featured Charts</h3>
|
||||
<Carousel
|
||||
:sliders="getFeaturedCharts"
|
||||
@clicked="(row) => selectChart(row)"
|
||||
/>
|
||||
</div>
|
||||
<TypeDescription resource="chart" />
|
||||
<div class="left-right-split">
|
||||
<Select
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ export const HIDE_REPOS = create('hide-repos', [], { parseJSON });
|
|||
export const HIDE_DESC = create('hide-desc', [], { parseJSON });
|
||||
export const HIDE_SENSITIVE = create('hide-sensitive', true, { options: [true, false], parseJSON });
|
||||
export const SHOW_PRE_RELEASE = create('show-pre-release', false, { options: [false, true], parseJSON });
|
||||
export const SHOW_CHART_MODE = create('chartMode', 'featured', { parseJSON });
|
||||
|
||||
export const DATE_FORMAT = create('date-format', 'ddd, MMM D YYYY', {
|
||||
options: [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,485 @@
|
|||
import Vue from 'vue';
|
||||
import { MANAGEMENT, STEVE } from '@/config/types';
|
||||
import { clone } from '@/utils/object';
|
||||
import { SETTING } from '@/config/settings';
|
||||
|
||||
const definitions = {};
|
||||
|
||||
export const create = function(name, def, opt = {}) {
|
||||
const parseJSON = opt.parseJSON === true;
|
||||
const asCookie = opt.asCookie === true;
|
||||
const asUserPreference = opt.asUserPreference !== false;
|
||||
const options = opt.options;
|
||||
|
||||
definitions[name] = {
|
||||
def,
|
||||
options,
|
||||
parseJSON,
|
||||
asCookie,
|
||||
asUserPreference,
|
||||
mangleRead: opt.mangleRead, // Alter the value read from the API (to match old Rancher expectations)
|
||||
mangleWrite: opt.mangleWrite, // Alter the value written back to the API (ditto)
|
||||
};
|
||||
|
||||
return name;
|
||||
};
|
||||
|
||||
export const mapPref = function(name) {
|
||||
return {
|
||||
get() {
|
||||
return this.$store.getters['prefs/get'](name);
|
||||
},
|
||||
|
||||
set(value) {
|
||||
this.$store.dispatch('prefs/set', { key: name, value });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// --------------------
|
||||
const parseJSON = true; // Shortcut for setting it below
|
||||
const asCookie = true; // Store as a cookie so that it's available before auth + on server-side
|
||||
|
||||
// Keys must be lowercase and valid dns label (a-z 0-9 -)
|
||||
export const CLUSTER = create('cluster', '');
|
||||
export const LAST_NAMESPACE = create('last-namespace', '');
|
||||
export const NAMESPACE_FILTERS = create('ns-by-cluster', {}, { parseJSON });
|
||||
export const WORKSPACE = create('workspace', '');
|
||||
export const EXPANDED_GROUPS = create('open-groups', ['cluster', 'rbac', 'serviceDiscovery', 'storage', 'workload'], { parseJSON });
|
||||
export const FAVORITE_TYPES = create('fav-type', [], { parseJSON });
|
||||
export const GROUP_RESOURCES = create('group-by', 'namespace');
|
||||
export const DIFF = create('diff', 'unified', { options: ['unified', 'split'] });
|
||||
export const THEME = create('theme', 'auto', {
|
||||
options: ['light', 'auto', 'dark'],
|
||||
asCookie,
|
||||
parseJSON,
|
||||
mangleRead: x => x.replace(/^ui-/, ''),
|
||||
mangleWrite: x => `ui-${ x }`,
|
||||
});
|
||||
export const PREFERS_SCHEME = create('pcs', '', { asCookie, asUserPreference: false });
|
||||
export const LOCALE = create('locale', 'en-us', { asCookie });
|
||||
export const KEYMAP = create('keymap', 'sublime', { options: ['sublime', 'emacs', 'vim'] });
|
||||
export const ROWS_PER_PAGE = create('per-page', 100, { options: [10, 25, 50, 100, 250, 500, 1000], parseJSON });
|
||||
export const LOGS_WRAP = create('logs-wrap', true, { parseJSON });
|
||||
export const LOGS_TIME = create('logs-time', true, { parseJSON });
|
||||
export const LOGS_RANGE = create('logs-range', '30 minutes', { parseJSON });
|
||||
export const HIDE_REPOS = create('hide-repos', [], { parseJSON });
|
||||
export const HIDE_DESC = create('hide-desc', [], { parseJSON });
|
||||
export const HIDE_SENSITIVE = create('hide-sensitive', true, { options: [true, false], parseJSON });
|
||||
export const SHOW_PRE_RELEASE = create('show-pre-release', false, { options: [false, true], parseJSON });
|
||||
export const SHOW_CHART_MODE = create('chartMode', 'featured', { parseJSON });
|
||||
|
||||
export const DATE_FORMAT = create('date-format', 'ddd, MMM D YYYY', {
|
||||
options: [
|
||||
'ddd, MMM D YYYY',
|
||||
'ddd, D MMM YYYY',
|
||||
'D/M/YYYY',
|
||||
'M/D/YYYY',
|
||||
'YYYY-MM-DD'
|
||||
]
|
||||
});
|
||||
|
||||
export const TIME_FORMAT = create('time-format', 'h:mm:ss a', {
|
||||
options: [
|
||||
'h:mm:ss a',
|
||||
'HH:mm:ss'
|
||||
]
|
||||
});
|
||||
|
||||
export const TIME_ZONE = create('time-zone', 'local');
|
||||
export const DEV = create('dev', false, { parseJSON });
|
||||
export const LAST_VISITED = create('last-visited', 'home', { parseJSON });
|
||||
export const SEEN_WHATS_NEW = create('seen-whatsnew', '', { parseJSON });
|
||||
export const READ_WHATS_NEW = create('read-whatsnew', '', { parseJSON });
|
||||
export const AFTER_LOGIN_ROUTE = create('after-login-route', 'home', { parseJSON } );
|
||||
export const HIDE_HOME_PAGE_CARDS = create('home-page-cards', {}, { parseJSON } );
|
||||
|
||||
export const _RKE1 = 'rke1';
|
||||
export const _RKE2 = 'rke2';
|
||||
export const PROVISIONER = create('provisioner', _RKE1, { options: [_RKE1, _RKE2] });
|
||||
|
||||
// Promo for Cluster Tools feature on Cluster Dashboard page
|
||||
export const CLUSTER_TOOLS_TIP = create('hide-cluster-tools-tip', false, { parseJSON });
|
||||
|
||||
// Maximum number of clusters to show in the slide-in menu
|
||||
export const MENU_MAX_CLUSTERS = create('menu-max-clusters', 4, { options: [2, 3, 4, 5, 6, 7, 8, 9, 10], parseJSON });
|
||||
|
||||
// --------------------
|
||||
|
||||
const cookiePrefix = 'R_';
|
||||
const cookieOptions = {
|
||||
maxAge: 365 * 86400,
|
||||
path: '/',
|
||||
sameSite: true,
|
||||
secure: true,
|
||||
};
|
||||
|
||||
export const state = function() {
|
||||
return {
|
||||
cookiesLoaded: false,
|
||||
data: {},
|
||||
};
|
||||
};
|
||||
|
||||
export const getters = {
|
||||
get: state => (key) => {
|
||||
const definition = definitions[key];
|
||||
|
||||
if (!definition) {
|
||||
throw new Error(`Unknown preference: ${ key }`);
|
||||
}
|
||||
|
||||
const user = state.data[key];
|
||||
|
||||
if (user !== undefined) {
|
||||
return clone(user);
|
||||
}
|
||||
|
||||
const def = clone(definition.def);
|
||||
|
||||
return def;
|
||||
},
|
||||
|
||||
defaultValue: state => (key) => {
|
||||
const definition = definitions[key];
|
||||
|
||||
if (!definition) {
|
||||
throw new Error(`Unknown preference: ${ key }`);
|
||||
}
|
||||
|
||||
return clone(definition.def);
|
||||
},
|
||||
|
||||
options: state => (key) => {
|
||||
const definition = definitions[key];
|
||||
|
||||
if (!definition) {
|
||||
throw new Error(`Unknown preference: ${ key }`);
|
||||
}
|
||||
|
||||
if (!definition.options) {
|
||||
throw new Error(`Preference does not have options: ${ key }`);
|
||||
}
|
||||
|
||||
return definition.options.slice();
|
||||
},
|
||||
|
||||
theme: (state, getters) => {
|
||||
let theme = getters['get'](THEME);
|
||||
const pcs = getters['get'](PREFERS_SCHEME);
|
||||
|
||||
// console.log('Get Theme', theme, pcs);
|
||||
|
||||
// Ember UI uses this prefix
|
||||
if ( theme.startsWith('ui-') ) {
|
||||
theme = theme.substr(3);
|
||||
}
|
||||
|
||||
if ( theme === 'auto' ) {
|
||||
if ( pcs === 'light' || pcs === 'dark' ) {
|
||||
return pcs;
|
||||
}
|
||||
|
||||
return 'dark';
|
||||
}
|
||||
|
||||
return theme;
|
||||
},
|
||||
|
||||
afterLoginRoute: (state, getters) => {
|
||||
const afterLoginRoutePref = getters['get'](AFTER_LOGIN_ROUTE);
|
||||
|
||||
if (typeof afterLoginRoutePref !== 'string') {
|
||||
return afterLoginRoutePref;
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case (afterLoginRoutePref === 'home'):
|
||||
return { name: 'home' };
|
||||
case (afterLoginRoutePref === 'last-visited'): {
|
||||
const lastVisitedPref = getters['get'](LAST_VISITED);
|
||||
|
||||
if (lastVisitedPref) {
|
||||
return lastVisitedPref;
|
||||
}
|
||||
const clusterPref = getters['get'](CLUSTER);
|
||||
|
||||
return { name: 'c-cluster-explorer', params: { product: 'explorer', cluster: clusterPref } };
|
||||
}
|
||||
case (!!afterLoginRoutePref.match(/.+-dashboard$/)):
|
||||
{
|
||||
const clusterId = afterLoginRoutePref.split('-dashboard')[0];
|
||||
|
||||
return { name: 'c-cluster-explorer', params: { product: 'explorer', cluster: clusterId } };
|
||||
}
|
||||
default:
|
||||
return { name: afterLoginRoutePref };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const mutations = {
|
||||
load(state, { key, value }) {
|
||||
Vue.set(state.data, key, value);
|
||||
},
|
||||
|
||||
cookiesLoaded(state) {
|
||||
state.cookiesLoaded = true;
|
||||
},
|
||||
|
||||
reset(state) {
|
||||
for (const key in definitions) {
|
||||
if ( definitions[key]?.asCookie ) {
|
||||
continue;
|
||||
}
|
||||
delete state.data[key];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
async set({ dispatch, commit }, opt) {
|
||||
let { key, value } = opt; // eslint-disable-line prefer-const
|
||||
const definition = definitions[key];
|
||||
let server;
|
||||
|
||||
if ( opt.val ) {
|
||||
throw new Error('Use value, not val');
|
||||
}
|
||||
|
||||
commit('load', { key, value });
|
||||
|
||||
if ( definition.asCookie ) {
|
||||
const opt = {
|
||||
...cookieOptions,
|
||||
parseJSON: definition.parseJSON === true
|
||||
};
|
||||
|
||||
this.$cookies.set(`${ cookiePrefix }${ key }`.toUpperCase(), value, opt);
|
||||
}
|
||||
|
||||
if ( definition.asUserPreference ) {
|
||||
try {
|
||||
server = await dispatch('loadServer', key); // There's no watch on prefs, so get before set...
|
||||
|
||||
if ( server?.data ) {
|
||||
if ( definition.mangleWrite ) {
|
||||
value = definition.mangleWrite(value);
|
||||
}
|
||||
|
||||
if ( definition.parseJSON ) {
|
||||
Vue.set(server.data, key, JSON.stringify(value));
|
||||
} else {
|
||||
Vue.set(server.data, key, value);
|
||||
}
|
||||
|
||||
await server.save({ redirectUnauthorized: false });
|
||||
}
|
||||
} catch (e) {
|
||||
// Well it failed, but not much to do about it...
|
||||
|
||||
// Return the error
|
||||
return { type: e.type, status: e.status };
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async setTheme({ dispatch }, val) {
|
||||
await dispatch('set', { key: THEME, value: val });
|
||||
},
|
||||
|
||||
loadCookies({ state, commit }) {
|
||||
if ( state.cookiesLoaded ) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const key in definitions) {
|
||||
const definition = definitions[key];
|
||||
|
||||
if ( !definition.asCookie ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const opt = { parseJSON: definition.parseJSON === true };
|
||||
const value = this.$cookies.get(`${ cookiePrefix }${ key }`.toUpperCase(), opt);
|
||||
|
||||
if (value !== undefined) {
|
||||
commit('load', { key, value });
|
||||
}
|
||||
}
|
||||
|
||||
commit('cookiesLoaded');
|
||||
},
|
||||
|
||||
loadTheme({ state, dispatch }) {
|
||||
if ( process.client ) {
|
||||
const watchDark = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const watchLight = window.matchMedia('(prefers-color-scheme: light)');
|
||||
const watchNone = window.matchMedia('(prefers-color-scheme: no-preference)');
|
||||
|
||||
const interval = 30 * 60 * 1000;
|
||||
const nextHalfHour = interval - Math.round(new Date().getTime()) % interval;
|
||||
|
||||
setTimeout(() => {
|
||||
dispatch('loadTheme');
|
||||
}, nextHalfHour);
|
||||
// console.log('Update theme in', nextHalfHour, 'ms');
|
||||
|
||||
if ( watchDark.matches ) {
|
||||
changed('dark');
|
||||
} else if ( watchLight.matches ) {
|
||||
changed('light');
|
||||
} else {
|
||||
changed(fromClock());
|
||||
}
|
||||
|
||||
watchDark.addListener((e) => {
|
||||
if ( e.matches ) {
|
||||
changed('dark');
|
||||
}
|
||||
});
|
||||
|
||||
watchLight.addListener((e) => {
|
||||
if ( e.matches ) {
|
||||
changed('light');
|
||||
}
|
||||
});
|
||||
|
||||
watchNone.addListener((e) => {
|
||||
if ( e.matches ) {
|
||||
changed(fromClock());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function changed(value) {
|
||||
// console.log('Prefers Theme:', value);
|
||||
dispatch('set', { key: PREFERS_SCHEME, value });
|
||||
}
|
||||
|
||||
function fromClock() {
|
||||
const hour = new Date().getHours();
|
||||
|
||||
if ( hour < 7 || hour >= 18 ) {
|
||||
return 'dark';
|
||||
}
|
||||
|
||||
return 'light';
|
||||
}
|
||||
},
|
||||
|
||||
async loadServer({ state, dispatch, commit }, ignoreKey) {
|
||||
let server = { data: {} };
|
||||
|
||||
try {
|
||||
const all = await dispatch('management/findAll', {
|
||||
type: STEVE.PREFERENCE,
|
||||
opt: {
|
||||
url: 'userpreferences',
|
||||
force: true,
|
||||
watch: false,
|
||||
redirectUnauthorized: false,
|
||||
stream: false,
|
||||
}
|
||||
}, { root: true });
|
||||
|
||||
server = all?.[0];
|
||||
} catch (e) {
|
||||
console.error('Error loading preferences', e); // eslint-disable-line no-console
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !server?.data ) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const key in definitions) {
|
||||
const definition = definitions[key];
|
||||
let value = clone(server.data[key]);
|
||||
|
||||
if ( value === undefined || key === ignoreKey) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( definition.parseJSON ) {
|
||||
try {
|
||||
value = JSON.parse(value);
|
||||
} catch (err) {
|
||||
console.error('Error parsing server pref', key, value, err); // eslint-disable-line no-console
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ( definition.mangleRead ) {
|
||||
value = definition.mangleRead(value);
|
||||
}
|
||||
|
||||
commit('load', { key, value });
|
||||
}
|
||||
|
||||
return server;
|
||||
},
|
||||
|
||||
setLastVisited({ state, dispatch }, route) {
|
||||
if (!route) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toSave = getLoginRoute(route);
|
||||
|
||||
return dispatch('set', { key: LAST_VISITED, value: toSave });
|
||||
},
|
||||
|
||||
toggleTheme({ getters, dispatch }) {
|
||||
const value = getters[THEME] === 'light' ? 'dark' : 'light';
|
||||
|
||||
return dispatch('set', { key: THEME, value });
|
||||
},
|
||||
|
||||
setBrandStyle({ rootState, rootGetters }, dark = false) {
|
||||
if (rootState.managementReady) {
|
||||
try {
|
||||
const brandSetting = rootGetters['management/byId'](MANAGEMENT.SETTING, SETTING.BRAND);
|
||||
|
||||
if (brandSetting && brandSetting.value && brandSetting.value !== '') {
|
||||
const brand = brandSetting.value;
|
||||
|
||||
const brandMeta = require(`~/assets/brand/${ brand }/metadata.json`);
|
||||
const hasStylesheet = brandMeta.hasStylesheet === 'true';
|
||||
|
||||
if (hasStylesheet) {
|
||||
document.body.classList.add(brand);
|
||||
} else {
|
||||
// TODO option apply color at runtime
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function getLoginRoute(route) {
|
||||
let parts = route.name?.split('-') || [];
|
||||
const params = {};
|
||||
const routeParams = route.params || {};
|
||||
|
||||
// Find the 'resource' part of the route, if it is there
|
||||
const index = parts.findIndex(p => p === 'resource');
|
||||
|
||||
if (index >= 0) {
|
||||
parts = parts.slice(0, index);
|
||||
}
|
||||
|
||||
// Just keep the params that are needed
|
||||
parts.forEach((param) => {
|
||||
if (routeParams[param]) {
|
||||
params[param] = routeParams[param];
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
name: parts.join('-'),
|
||||
params
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue