dashboard/plugins/steve/actions.js

408 lines
10 KiB
JavaScript

import https from 'https';
import cloneDeep from 'lodash/cloneDeep';
import merge from 'lodash/merge';
import { SCHEMA } from '@/config/types';
import { createYaml } from '@/utils/create-yaml';
import { SPOOFED_API_PREFIX, SPOOFED_PREFIX } from '@/store/type-map';
import { addParam } from '@/utils/url';
import { normalizeType } from './normalize';
import { proxyFor, SELF } from './resource-proxy';
export default {
async request({ dispatch, rootGetters }, opt) {
// Handle spoofed types instead of making an actual request
// Spoofing is handled here to ensure it's done for both yaml and form editing.
// It became apparent that this was the only place that both intersected
if (opt.url.includes(SPOOFED_PREFIX) || opt.url.includes(SPOOFED_API_PREFIX)) {
const [empty, scheme, type, ...rest] = opt.url.split('/'); // eslint-disable-line no-unused-vars
const id = rest.join('/'); // Cover case where id contains '/'
const isApi = scheme === SPOOFED_API_PREFIX;
const typemapGetter = id ? 'getSpoofedInstance' : 'getSpoofedInstances';
const schemas = await rootGetters['cluster/all'](SCHEMA);
const instance = await rootGetters[`type-map/${ typemapGetter }`](type, id);
const data = isApi ? createYaml(schemas, type, instance) : instance;
return id && !isApi ? data : { data };
}
// @TODO queue/defer duplicate requests
opt.depaginate = opt.depaginate !== false;
opt.url = opt.url.replace(/\/*$/g, '');
opt.httpsAgent = new https.Agent({ rejectUnauthorized: false });
return this.$axios(opt).then((res) => {
if ( opt.depaginate ) {
// @TODO but API never sends it
/*
return new Promise((resolve, reject) => {
const next = res.pagination.next;
if (!next ) [
return resolve();
}
dispatch('request')
});
*/
}
if ( opt.responseType ) {
return res;
} else {
return responseObject(res);
}
}).catch((err) => {
if ( !err || !err.response ) {
return Promise.reject(err);
}
const res = err.response;
// Go to the logout page for 401s, unless redirectUnauthorized specifically disables (for the login page)
if ( opt.redirectUnauthorized !== false && process.client && res.status === 401 ) {
dispatch('auth/logout', opt.logoutOnError, { root: true });
}
if ( typeof res.data !== 'undefined' ) {
return Promise.reject(responseObject(res));
}
return Promise.reject(err);
});
function responseObject(res) {
let out = res.data;
if ( typeof out !== 'object' ) {
out = { data: out };
}
Object.defineProperties(out, {
_status: { value: res.status },
_statusText: { value: res.statusText },
_headers: { value: res.headers },
_req: { value: res.request },
_url: { value: opt.url },
});
return out;
}
},
async loadSchemas(ctx, watch = true) {
const {
getters, dispatch, commit, rootGetters
} = ctx;
const res = await dispatch('findAll', { type: SCHEMA, opt: { url: 'schemas', load: false } });
const spoofedTypes = rootGetters['type-map/allSpoofedSchemas'] ;
res.data = res.data.concat(spoofedTypes);
res.data.forEach((schema) => {
schema._id = normalizeType(schema.id);
schema._group = normalizeType(schema.attributes?.group);
});
commit('loadAll', {
ctx,
type: SCHEMA,
data: res.data
});
if ( watch !== false ) {
dispatch('watch', {
type: SCHEMA,
revision: res.revision
});
}
const all = getters.all(SCHEMA);
return all;
},
async findAll(ctx, { type, opt }) {
const { getters, commit, dispatch } = ctx;
opt = opt || {};
type = getters.normalizeType(type);
if ( !getters.typeRegistered(type) ) {
commit('registerType', type);
}
if ( opt.force !== true && getters['haveAll'](type) ) {
return getters.all(type);
}
console.log(`Find All: [${ ctx.state.config.namespace }] ${ type }`); // eslint-disable-line no-console
opt = opt || {};
opt.url = getters.urlFor(type, null, opt);
const res = await dispatch('request', opt);
if ( opt.load === false ) {
return res;
}
commit('loadAll', {
ctx,
type,
data: res.data
});
if ( opt.watch !== false ) {
dispatch('watch', {
type,
revision: res.revision,
namespace: opt.watchNamespace
});
}
const all = getters.all(type);
return all;
},
async findMatching(ctx, { type, selector, opt }) {
const { getters, commit, dispatch } = ctx;
opt = opt || {};
console.log(`Find Matching: [${ ctx.state.config.namespace }] ${ type }`, selector); // eslint-disable-line no-console
type = getters.normalizeType(type);
if ( !getters.typeRegistered(type) ) {
commit('registerType', type);
}
if ( opt.force !== true && getters['haveSelector'](type, selector) ) {
return getters.matching( type, selector );
}
opt = opt || {};
opt.filter = opt.filter || {};
opt.filter['labelSelector'] = selector;
opt.url = getters.urlFor(type, null, opt);
const res = await dispatch('request', opt);
if ( opt.load === false ) {
return res.data;
}
commit('loadSelector', {
ctx,
type,
entries: res.data,
selector
});
if ( opt.watch !== false ) {
dispatch('watch', {
type,
selector,
revision: res.revision
});
}
return getters.matching( type, selector );
},
// opt:
// filter: Filter by fields, e.g. {field: value, anotherField: anotherValue} (default: none)
// limit: Number of reqords to return per page (default: 1000)
// sortBy: Sort by field
// sortOrder: asc or desc
// url: Use this specific URL instead of looking up the URL for the type/id. This should only be used for bootstraping schemas on startup.
// @TODO depaginate: If the response is paginated, retrieve all the pages. (default: true)
async find(ctx, { type, id, opt }) {
const { getters, dispatch } = ctx;
opt = opt || {};
type = normalizeType(type);
console.log(`Find: [${ ctx.state.config.namespace }] ${ type } ${ id }`); // eslint-disable-line no-console
let out;
if ( opt.force !== true ) {
out = getters.byId(type, id);
if ( out ) {
return out;
}
}
opt = opt || {};
opt.url = getters.urlFor(type, id, opt);
const res = await dispatch('request', opt);
await dispatch('load', { data: res });
if ( opt.watch !== false ) {
const watchMsg = {
type,
id,
revision: res?.metadata?.resourceVersion,
force: opt.forceWatch === true,
};
const idx = id.indexOf('/');
if ( idx > 0 ) {
watchMsg.namespace = id.substr(0, idx);
watchMsg.id = id.substr(idx + 1);
}
dispatch('watch', watchMsg);
}
out = getters.byId(type, id);
return out;
},
load(ctx, { data, existing }) {
const { getters, commit } = ctx;
let type = normalizeType(data.type);
if ( !getters.typeRegistered(type) ) {
commit('registerType', type);
}
if ( data.baseType && data.baseType !== data.type ) {
type = normalizeType(data.baseType);
if ( !getters.typeRegistered(type) ) {
commit('registerType', type);
}
}
const id = data?.id || existing?.id;
if ( !id ) {
console.warn('Attempting to load a resource with no id', data, existing); // eslint-disable-line no-console
return;
}
commit('load', {
ctx,
data,
existing
});
if ( type === SCHEMA ) {
commit('type-map/schemaChanged', null, { root: true });
}
return getters['byId'](type, id);
},
loadMulti(ctx, entries) {
const { commit } = ctx;
commit('loadMulti', {
entries,
ctx,
});
},
loadAll(ctx, { type, data }) {
const { commit } = ctx;
commit('loadAll', {
ctx,
type,
data
});
},
create(ctx, data) {
return proxyFor(ctx, data);
},
createPopulated(ctx, userData) {
const data = ctx.getters['defaultFor'](userData.type);
merge(data, userData);
return proxyFor(ctx, data);
},
clone(ctx, { resource } = {}) {
const copy = cloneDeep(resource[SELF]);
return proxyFor(ctx, copy, true);
},
promptRemove({ commit, state }, resources = []) {
commit('action-menu/togglePromptRemove', resources, { root: true });
},
assignTo({ commit, state }, resources = []) {
commit('action-menu/toggleAssignTo', resources, { root: true });
},
async resourceAction({ getters, dispatch }, {
resource, actionName, body, opt,
}) {
opt = opt || {};
if ( !opt.url ) {
opt.url = resource.actionLinkFor(actionName);
// opt.url = (resource.actions || resource.actionLinks)[actionName];
}
opt.method = 'post';
opt.data = body;
const res = await dispatch('request', opt);
if ( opt.load !== false && res.type === 'collection' ) {
await dispatch('loadMulti', res.data);
return res.data.map(x => getters.byId(x.type, x.id) || x);
} else if ( opt.load !== false && res.type && res.id ) {
return dispatch('load', { data: res });
} else {
return res;
}
},
promptUpdate({ commit, state }, resources = []) {
commit('action-menu/togglePromptUpdate', resources, { root: true });
},
async collectionAction({ getters, dispatch }, {
type, actionName, body, opt
}) {
opt = opt || {};
if ( !opt.url ) {
// Cheating, but cheaper than loading the whole collection...
const schema = getters['schemaFor'](type);
opt.url = addParam(schema.links.collection, 'action', actionName);
}
opt.method = 'post';
opt.data = body;
const res = await dispatch('request', opt);
if ( opt.load !== false && res.type === 'collection' ) {
await dispatch('loadMulti', res.data);
return res.data.map(x => getters.byId(x.type, x.id) || x);
} else if ( opt.load !== false && res.type && res.id ) {
return dispatch('load', { data: res });
} else {
return res;
}
},
};