mirror of https://github.com/rancher/dashboard.git
419 lines
12 KiB
JavaScript
419 lines
12 KiB
JavaScript
// This file has 2 sections that control getting and using specializatinos of particular types
|
|
// over the generic info that is specified or generated from schemas.
|
|
//
|
|
// 1) Getting info about types
|
|
//
|
|
// singularLabelFor(schema) Get the singular form of this schema's type
|
|
// pluralLabelFor(schema) Get the plural form of this schema's type
|
|
// groupLabelFor(schema) Get the label for the API group of this schema's type
|
|
// isIgnored(schema) Returns true if this type should be hidden from the tree
|
|
// isBasic(schema) Returns true if this type should be included in basic view
|
|
// typeWeightFor(type) Get the weight value for a particular type label
|
|
// groupWeightFor(group) Get the weight value for a particular group
|
|
// virtualTypes() Returns a list of the virtual pages to add to the treee
|
|
// headersFor(schema) Returns the column definitions for a type to give to SortableTable
|
|
//
|
|
// 2) Detecting and usign custom list/detail/edit/header components
|
|
//
|
|
// hasCustomList(type) Does type have a custom list implementation?
|
|
// hasCustomDetail(type) Does type have a custom detail implementation?
|
|
// hasCustomEdit(type) Does type have a custom edit implementation?
|
|
// importList(type) Returns a promise that resolves to the list component for type
|
|
// importDetail(type) Returns a promise that resolves to the detail component for type
|
|
// importEdit(type) Returns a promise that resolves to the edit component for type
|
|
//
|
|
// 3) Changing specialization info about a type
|
|
//
|
|
// virtualType(obj) Add an item to the tree that goes to a route instead of an actual type.
|
|
// -- Obj can contain anything in the objects getTree returns.
|
|
// -- `cluster` is automatically added to route.params if it exists.
|
|
// basicType(type) Mark type as one shown in basic view
|
|
// ignoreType(type) Never show type
|
|
// weightType( Set the weight (sorting) order of one or more types
|
|
// typeOrArrayOfTypes,
|
|
// weight -- Higher numbers are shown first/higher up on the nav tree
|
|
// )
|
|
// mapType( Remap a type id to a display name
|
|
// matchRegexOrString, -- Type to match, or regex that matches types
|
|
// replacementStringOrFn, -- String to replace the type with, or
|
|
// -- sync function: (type, capturedString, schemaObj => { return 'new-type'; }
|
|
// mapWeight, -- Priority for apply this mapping (higher numbers applied first)
|
|
// continueOnMatch -- If true, continue applying to hit other rules that might match the new type.
|
|
// )
|
|
// mapTypeToComponentName( Map matching types to a single component name (this is helpful if multiple types should be rendered by a single component)
|
|
// (
|
|
// matchRegexOrString, -- Type to match, or regex that matches types
|
|
// replacementString -- String to replace the type with
|
|
// )
|
|
// labelType( Remap the displayed label for a type
|
|
// type,
|
|
// singular,
|
|
// plural
|
|
// )
|
|
//
|
|
// ignoreGroup(group): Never show group or any types in it
|
|
// weightGroup( Set the weight (sort) order of one or more groups
|
|
// groupOrArrayOfGroups, -- see weightType...
|
|
// weight
|
|
// )
|
|
// mapGroup( Remap a group name to a display name
|
|
// matchRegexOrString, -- see mapType...
|
|
// replacementString,
|
|
// mapWeight,
|
|
// continueOnMatch
|
|
// )
|
|
|
|
import { escapeRegex } from '@/utils/string';
|
|
import { isArray, removeObject } from '@/utils/array';
|
|
import { get } from '@/utils/object';
|
|
|
|
import { STATE, NAMESPACE_NAME, NAME, AGE } from '@/config/table-headers';
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 1 ) Getting info
|
|
// ----------------------------------------------------------------------------
|
|
// Turns a type name into a display label (e.g. management.cattle.io.v3.cluster -> Cluster)
|
|
export function singularLabelFor(schema) {
|
|
return _applyMapping(schema, _typeMappings, 'id', _typeLabelCache);
|
|
}
|
|
|
|
export function pluralLabelFor(schema) {
|
|
if ( _pluralLabels[schema.id] ) {
|
|
return _pluralLabels[schema.id];
|
|
}
|
|
|
|
const singular = singularLabelFor(schema);
|
|
|
|
if ( singular.endsWith('s') ) {
|
|
return `${ singular }es`;
|
|
}
|
|
|
|
return `${ singular }s`;
|
|
}
|
|
|
|
// Turns a group name into a display label (e.g. management.cattle.io.v3.cluster -> Cluster)
|
|
export function groupLabelFor(schema) {
|
|
return _applyMapping(schema, _groupMappings, 'attributes.group', _groupLabelCache);
|
|
}
|
|
|
|
export function isIgnored(schema) {
|
|
return _groupIgnore[schema.attributes.group] || _typeIgnore[schema.id] || false;
|
|
}
|
|
|
|
export function isBasic(schema) {
|
|
return _basicTypes[schema.id] || false;
|
|
}
|
|
|
|
export function typeWeightFor(type) {
|
|
return _typeWeights[type.toLowerCase()] || 0;
|
|
}
|
|
|
|
export function groupWeightFor(group) {
|
|
return _groupWeights[group.toLowerCase()] || 0;
|
|
}
|
|
|
|
export function virtualTypes() {
|
|
return _virtualTypes.slice();
|
|
}
|
|
|
|
export function headersFor(schema) {
|
|
if ( _headers[schema.id] ) {
|
|
return _headers[schema.id];
|
|
}
|
|
|
|
const out = [STATE]; // Everybody gets a state
|
|
|
|
const attributes = schema.attributes || {};
|
|
const columns = attributes.columns;
|
|
const namespaced = attributes.namespaced;
|
|
|
|
let hasName = false;
|
|
|
|
for ( const col of columns ) {
|
|
if ( col.format === 'name' ) {
|
|
hasName = true;
|
|
out.push(namespaced ? NAMESPACE_NAME : NAME);
|
|
} else {
|
|
let formatter, width;
|
|
|
|
if ( col.format === '' && col.name === 'Age' ) {
|
|
out.push(AGE);
|
|
continue;
|
|
}
|
|
|
|
if ( col.format === 'date' || col.type === 'date' ) {
|
|
formatter = 'Date';
|
|
width = 120;
|
|
}
|
|
|
|
out.push({
|
|
name: col.name.toLowerCase(),
|
|
label: col.name,
|
|
value: col.field.startsWith('.') ? `$${ col.field }` : col.field,
|
|
sort: [col.field],
|
|
formatter,
|
|
width,
|
|
});
|
|
}
|
|
}
|
|
|
|
if ( !hasName ) {
|
|
out.unshift(namespaced ? NAMESPACE_NAME : NAME);
|
|
}
|
|
|
|
// Age always goes last
|
|
if ( out.includes(AGE) ) {
|
|
removeObject(out, AGE);
|
|
out.push(AGE);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
// ------------------------------------
|
|
// Custom list/detail/edit/header component detection
|
|
//
|
|
// Note: you can't refactor these into one function that does `@/${kind}/${type}`,
|
|
// because babel needs some hardcoded idea where to look for the dependency.
|
|
// ------------------------------------
|
|
export function hasCustomList(rawType) {
|
|
const type = _normalizeType(rawType);
|
|
const cache = _hasCustom.list;
|
|
|
|
if ( cache[type] !== undefined ) {
|
|
return cache[type];
|
|
}
|
|
|
|
try {
|
|
require.resolve(`@/list/${ type }`);
|
|
cache[type] = true;
|
|
|
|
return true;
|
|
} catch (e) {
|
|
cache[type] = false;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function hasCustomDetail(rawType) {
|
|
const type = _normalizeType(rawType);
|
|
const cache = _hasCustom.detail;
|
|
|
|
if ( cache[type] !== undefined ) {
|
|
return cache[type];
|
|
}
|
|
|
|
try {
|
|
require.resolve(`@/detail/${ type }`);
|
|
cache[type] = true;
|
|
|
|
return true;
|
|
} catch (e) {
|
|
cache[type] = false;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function hasCustomEdit(rawType) {
|
|
const type = _normalizeType(rawType);
|
|
const cache = _hasCustom.edit;
|
|
|
|
if ( cache[type] !== undefined ) {
|
|
return cache[type];
|
|
}
|
|
|
|
try {
|
|
require.resolve(`@/edit/${ type }`);
|
|
cache[type] = true;
|
|
|
|
return true;
|
|
} catch (e) {
|
|
cache[type] = false;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function importList(rawType) {
|
|
const type = _normalizeType(rawType);
|
|
|
|
return () => import(`@/list/${ type }`);
|
|
}
|
|
|
|
export function importDetail(rawType) {
|
|
const type = _normalizeType(rawType);
|
|
|
|
return () => import(`@/detail/${ type }`);
|
|
}
|
|
|
|
export function importEdit(rawType) {
|
|
const type = _normalizeType(rawType);
|
|
|
|
console.log('boom', type);
|
|
|
|
return () => import(`@/edit/${ type }`);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 3) Changing info
|
|
// ----------------------------------------------------------------------------
|
|
export function virtualType(obj) {
|
|
_virtualTypes.push(obj);
|
|
}
|
|
|
|
export function basicType(types) {
|
|
if ( !isArray(types) ) {
|
|
types = [types];
|
|
}
|
|
|
|
for ( const t of types ) {
|
|
_basicTypes[t] = true;
|
|
}
|
|
}
|
|
|
|
export function ignoreGroup(group) {
|
|
_groupIgnore[group] = true;
|
|
}
|
|
|
|
export function ignoreType(type) {
|
|
_typeIgnore[type] = true;
|
|
}
|
|
|
|
export function headers(type, headers) {
|
|
_headers[type] = headers;
|
|
}
|
|
|
|
// setGroupWeight('core' 99); -- higher groups are shown first
|
|
// These operate on *displayed* group names, after mapping
|
|
export function weightGroup(groupOrGroups, weight) {
|
|
if ( !isArray(groupOrGroups) ) {
|
|
groupOrGroups = [groupOrGroups];
|
|
}
|
|
|
|
for ( const g of groupOrGroups ) {
|
|
_groupWeights[g.toLowerCase()] = weight;
|
|
}
|
|
}
|
|
|
|
// setTypeWeight('Cluster' 99); -- higher groups are shown first
|
|
// These operate on *displayed* type names, after mapping
|
|
export function weightType(typeOrTypes, weight) {
|
|
if ( !isArray(typeOrTypes) ) {
|
|
typeOrTypes = [typeOrTypes];
|
|
}
|
|
|
|
for ( const e of typeOrTypes ) {
|
|
_typeWeights[e.toLowerCase()] = weight;
|
|
}
|
|
}
|
|
|
|
// addGroupMapping('ugly.thing', 'Nice Thing', 1);
|
|
// addGroupMapping(/ugly.thing.(stuff)', '$1', 2);
|
|
// addGroupMapping(/ugly.thing.(stuff)', function(groupStr, ruleObj, regexMatch, typeObj) { return ucFirst(group.id) } , 2);
|
|
export function mapGroup(match, replace, weight = 5, continueOnMatch = false) {
|
|
_addMapping(_groupMappings, match, replace, weight, continueOnMatch);
|
|
}
|
|
|
|
export function mapType(match, replace, weight = 5, continueOnMatch = false) {
|
|
_addMapping(_typeMappings, match, replace, weight, continueOnMatch);
|
|
}
|
|
|
|
export function mapTypeToComponentName(match, replace, weight = 5, continueOnMatch = false) {
|
|
_typeToComponentMappings.push({ match, replace });
|
|
}
|
|
|
|
export function pluralizeType(type, plural) {
|
|
_pluralLabels[type] = plural;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 4) Internals
|
|
// ----------------------------------------------------------------------------
|
|
const _virtualTypes = [];
|
|
const _basicTypes = {};
|
|
const _groupIgnore = {};
|
|
const _groupWeights = {};
|
|
const _groupMappings = [];
|
|
const _groupLabelCache = {};
|
|
const _typeIgnore = {};
|
|
const _typeWeights = {};
|
|
const _typeMappings = [];
|
|
const _typeToComponentMappings = [];
|
|
const _typeLabelCache = {};
|
|
const _pluralLabels = {};
|
|
const _headers = {};
|
|
const _hasCustom = {
|
|
list: {},
|
|
detail: {},
|
|
edit: {},
|
|
};
|
|
|
|
function _applyMapping(obj, mappings, keyField, cache) {
|
|
const key = get(obj, keyField);
|
|
|
|
if ( typeof key !== 'string' ) {
|
|
return null;
|
|
}
|
|
|
|
if ( cache[key] ) {
|
|
return cache[key];
|
|
}
|
|
|
|
let out = `${ key }`;
|
|
|
|
for ( const rule of mappings ) {
|
|
const captured = out.match(rule.match);
|
|
|
|
if ( captured && rule.replace ) {
|
|
if ( typeof rule.replace === 'function' ) {
|
|
out = rule['replace'](out, captured, obj);
|
|
} else {
|
|
out = out.replace(rule.match, rule.replace);
|
|
}
|
|
|
|
if ( !rule.continueOnMatch ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
cache[key] = out;
|
|
|
|
return out;
|
|
}
|
|
|
|
function _normalizeType(type) {
|
|
const mapping = _typeToComponentMappings.find(mapping => mapping.match.test(type));
|
|
|
|
return mapping ? mapping.replace : type;
|
|
}
|
|
|
|
// --------------------------------------------
|
|
|
|
function _addMapping(mappings, match, replace, weight, continueOnMatch) {
|
|
if ( typeof match === 'string' ) {
|
|
match = new RegExp(`^${ escapeRegex(match) }$`, 'i');
|
|
}
|
|
|
|
mappings.push({
|
|
match,
|
|
replace,
|
|
weight,
|
|
continueOnMatch,
|
|
insertIndex: mappings.length,
|
|
});
|
|
|
|
// Re-sort the list by weight (highest first) and insert time (oldest first)
|
|
mappings.sort((a, b) => {
|
|
const pri = b.weight - a.weight;
|
|
|
|
if ( pri ) {
|
|
return pri;
|
|
}
|
|
|
|
return a.insertIndex - b.insertIndex;
|
|
});
|
|
}
|