dashboard/store/prefs.js

418 lines
11 KiB
JavaScript

import Vue from 'vue';
import { STEVE } from '@/config/types';
import { clone } from '@/utils/object';
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', ['all://user'], { 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 LANDING = create('landing', '', { parseJSON, options: ['ember', 'vue'] });
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 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-home', '', { parseJSON });
export const AFTER_LOGIN_ROUTE = create('after-login-route', 'home' );
export const HIDE_HOME_PAGE_CARDS = create('home-page-cards', {}, { 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);
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;
},
};
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...
}
}
},
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,
}
}, { root: true });
server = all?.[0];
} catch (e) {
console.error('Error loading preferences', e); // eslint-disable-line no-console
}
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 routeComponents = route.name.split('-');
const toSave = { name: '', params: {} };
for ( const i in routeComponents) {
const part = routeComponents[i];
if (!!part && i < 3) {
toSave.name += `${ part }-`;
if (route.params[part]) {
toSave.params[part] = route.params[part];
}
} else {
break;
}
}
toSave.name = toSave.name.replace(/-$/, '');
dispatch('set', { key: LAST_VISITED, value: toSave });
},
toggleTheme({ getters, dispatch }) {
const value = getters[THEME] === 'light' ? 'dark' : 'light';
return dispatch('set', { key: THEME, value });
},
};