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:
richa 2022-09-29 14:03:48 +02:00 committed by GitHub
parent 4861b1e490
commit cd3a06269c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 3862 additions and 2 deletions

View File

@ -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};
}

View File

@ -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};
}

View File

@ -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

View File

@ -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};

View File

@ -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 {

View File

@ -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>

View File

@ -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 = {

View File

@ -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

View File

@ -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: [

485
store/prefs.js Normal file
View File

@ -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
};
}