mirror of https://github.com/rancher/dashboard.git
206 lines
6.3 KiB
JavaScript
206 lines
6.3 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
const querystring = require('querystring');
|
|
|
|
// When we receive a request to this URL we will reset the session to replay again from the HAR file
|
|
// This allows the user to refresh the browser and replay the HAR file again
|
|
const RESET_URL = '/api/v1/namespaces/cattle-ui-plugin-system/services/http:ui-plugin-operator:80/proxy/index.json';
|
|
|
|
const EXCLUDE_QS = 'exclude';
|
|
|
|
const LEGACY_UI_PLUGIN_INDEX = '/api/v1/namespaces/cattle-ui-plugin-system/services/http:ui-plugin-operator:80/proxy/index.json';
|
|
const NEW_UI_PLUGIN_INDEX = '/v1/uiplugins';
|
|
|
|
/**
|
|
* Load the network requests/responses from the har file
|
|
* @param name name/path of the file
|
|
* @param port port that the local dev server is running on
|
|
* @returns har file data
|
|
*/
|
|
function loadFile(name, port, dashboard) {
|
|
const newBase = `https://127.0.0.1:${ port }`;
|
|
const data = {};
|
|
|
|
console.log(`Loading HAR file: ${ name }`); // eslint-disable-line no-console
|
|
|
|
const rawData = fs.readFileSync(name);
|
|
const har = JSON.parse(rawData);
|
|
let base = '';
|
|
let uri = '';
|
|
|
|
if (har?.log?.pages) {
|
|
const page = har.log.pages.find((page) => page.title.includes('/dashboard/'));
|
|
|
|
if (page) {
|
|
const parts = page.title.split('/dashboard');
|
|
|
|
base = parts[0];
|
|
uri = parts[1];
|
|
}
|
|
}
|
|
|
|
if (har?.log?.entries) {
|
|
console.log('Network requests:'); // eslint-disable-line no-console
|
|
|
|
har.log.entries.forEach((r) => {
|
|
const mimeType = r.response.content.mimeType;
|
|
|
|
// Only cache json responses
|
|
if (mimeType === 'application/json' || mimeType === 'text/plain') {
|
|
let url = r.request.url;
|
|
|
|
if (url.startsWith(base)) {
|
|
url = url.substr(base.length);
|
|
}
|
|
|
|
console.log(` ${ r.request.method } ${ decodeURIComponent(url) }`); // eslint-disable-line no-console
|
|
|
|
data[url] = data[url] || {};
|
|
data[url][r.request.method] = data[url][r.request.method] || [];
|
|
|
|
const item = {
|
|
status: r.response.status,
|
|
statusText: r.response.statusText,
|
|
headers: r.response.headers,
|
|
content: r.response.content
|
|
};
|
|
|
|
if (item.content.text) {
|
|
item.content.text = item.content.text.replaceAll(base, newBase);
|
|
data[url][r.request.method].push(item);
|
|
} else {
|
|
console.log(' Warning: Omitting this response as there is no content - UI may not work as expected'); // eslint-disable-line no-console
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
console.log('Page:'); // eslint-disable-line no-console
|
|
console.log(` ${ newBase }${ dashboard }${ uri }`); // eslint-disable-line no-console
|
|
console.log(''); // eslint-disable-line no-console
|
|
|
|
return data;
|
|
}
|
|
|
|
function exportToFiles(data, folder) {
|
|
console.log(`Exporting request data to ${ folder }`); // eslint-disable-line no-console
|
|
|
|
Object.keys(data).forEach((r) => {
|
|
const out = path.join(folder, `.${ r }`);
|
|
const dir = path.dirname(out);
|
|
|
|
console.log(r); // eslint-disable-line no-console
|
|
|
|
Object.keys(data[r]).forEach((method) => {
|
|
const name = `${ path.basename(out) }.${ method.toLowerCase() }.json`;
|
|
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
|
|
data[r][method].forEach((request) => {
|
|
const formatted = JSON.stringify(JSON.parse(request.content.text), null, 2);
|
|
|
|
fs.writeFileSync(path.join(dir, name), formatted);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function harProxy(responses, folder) {
|
|
let session = JSON.parse(JSON.stringify(responses));
|
|
|
|
return (req, res, next) => {
|
|
if (req.originalUrl === RESET_URL) {
|
|
session = JSON.parse(JSON.stringify(responses));
|
|
console.log('>>>>>>>> Reset session replay from har file'); // eslint-disable-line no-console
|
|
}
|
|
|
|
const url = decodeURIComponent(req.originalUrl);
|
|
let playback = session[req.originalUrl];
|
|
|
|
// Handle case where HAR file was created with older UI Extension API that used the operator
|
|
if (!playback && req.originalUrl.includes(NEW_UI_PLUGIN_INDEX)) {
|
|
// Look for new URl for UI plugins
|
|
playback = session[LEGACY_UI_PLUGIN_INDEX];
|
|
}
|
|
|
|
// If it did not match, try without the metadata excludes query string that was adding in 2.8.0
|
|
// This might allow HAR captures with Rancher < 2.8.0 to be replayed on >= 2.8.0
|
|
if (!playback && req.originalUrl.includes('?')) {
|
|
const urlParts = req.originalUrl.split('?');
|
|
|
|
if (urlParts.length > 1) {
|
|
const queryString = urlParts[1];
|
|
const qs = querystring.parse(queryString);
|
|
|
|
delete qs[EXCLUDE_QS];
|
|
|
|
const newQs = querystring.stringify(qs);
|
|
const newUrl = newQs.length ? `${ urlParts[0] }?${ newQs }` : urlParts[0];
|
|
|
|
playback = session[newUrl];
|
|
}
|
|
}
|
|
|
|
if (playback && playback[req.method] && playback[req.method].length) {
|
|
const resp = playback[req.method][0];
|
|
|
|
if (playback[req.method].length > 1) {
|
|
playback[req.method].shift();
|
|
}
|
|
|
|
const body = resp.content?.text || '';
|
|
|
|
res.type(resp.content.mimeType);
|
|
res.status(resp.status);
|
|
res.send(Buffer.from(body));
|
|
res.end();
|
|
|
|
const char = !!resp.used ? '*' : ' ';
|
|
|
|
console.log(`${ req.method }${ char } ${ resp.status } ${ url }`); // eslint-disable-line no-console
|
|
|
|
resp.used = true;
|
|
|
|
return;
|
|
}
|
|
|
|
if (req.originalUrl.startsWith('/v1/') || req.originalUrl.startsWith('/v3/') || req.originalUrl.startsWith('/k8s/')) {
|
|
// If we have been configured with a folder, look for a file with the contents to use for the request
|
|
if (folder) {
|
|
// Remove query string
|
|
const name = req.originalUrl.split('?')[0];
|
|
const requestFile = path.join(folder, `.${ name }.${ req.method.toLowerCase() }.json`);
|
|
|
|
if (fs.existsSync(requestFile)) {
|
|
const data = fs.readFileSync(requestFile);
|
|
|
|
console.log(`${ req.method }f 200 ${ url }`); // eslint-disable-line no-console
|
|
|
|
res.type('application/json');
|
|
res.status(200);
|
|
res.send(data);
|
|
|
|
return res.end();
|
|
}
|
|
}
|
|
|
|
// Fallback to sending a 404 response
|
|
res.status(404);
|
|
res.send('Not Found');
|
|
|
|
console.log(`${ req.method }? 404 ${ url }`); // eslint-disable-line no-console
|
|
|
|
return res.end();
|
|
}
|
|
|
|
// Continue on to serve up other resources
|
|
return next();
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
loadFile,
|
|
harProxy,
|
|
exportToFiles,
|
|
};
|