2023-12-04 11:41:30 -05:00
|
|
|
import { inject as service } from '@ember/service';
|
|
|
|
|
import { getOwner, setOwner } from '@ember/application';
|
|
|
|
|
import ArrayProxy from '@ember/array/proxy';
|
2023-11-10 11:32:21 -05:00
|
|
|
import { pluralize } from 'ember-inflector';
|
2023-11-22 12:57:37 -06:00
|
|
|
import { generateMQLExpression } from '../utils/mqlQuery';
|
2023-11-10 11:32:21 -05:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Not all types are yet supported by the client daemon so we'll
|
2023-11-22 12:57:37 -06:00
|
|
|
* just whitelist the ones we need to start with. Keys are the resource
|
|
|
|
|
* types and the values are the fields that are searchable by the client.
|
|
|
|
|
* @type {{session: string[], target: string[]}}
|
2023-11-10 11:32:21 -05:00
|
|
|
*/
|
2023-11-22 12:57:37 -06:00
|
|
|
const supportedTypes = {
|
|
|
|
|
target: ['id', 'name', 'description', 'address', 'scope_id'],
|
|
|
|
|
session: ['id', 'type', 'status', 'endpoint', 'scope_id', 'target_id'],
|
|
|
|
|
};
|
2023-11-10 11:32:21 -05:00
|
|
|
|
2023-12-04 11:41:30 -05:00
|
|
|
/**
|
|
|
|
|
* Takes an array and the current page and pagesize to calculate the correct
|
|
|
|
|
* number of results to return to the caller.
|
|
|
|
|
*
|
|
|
|
|
* @param array
|
|
|
|
|
* @param page
|
|
|
|
|
* @param pageSize
|
|
|
|
|
* @returns {[*]}
|
|
|
|
|
*/
|
|
|
|
|
const paginateResults = (array, page, pageSize) => {
|
|
|
|
|
const length = array?.length;
|
|
|
|
|
if (!array || length === 0) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
if (!page || !pageSize) {
|
|
|
|
|
return array;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const offset = (page - 1) * pageSize;
|
|
|
|
|
const start = Math.min(length - 1, offset);
|
|
|
|
|
const end = Math.min(length, offset + pageSize);
|
|
|
|
|
|
|
|
|
|
return array.slice(start, end);
|
|
|
|
|
};
|
|
|
|
|
|
2023-11-10 11:32:21 -05:00
|
|
|
/**
|
|
|
|
|
* Handler to sit in front of the API request layer
|
|
|
|
|
* so we can request from the daemon first
|
|
|
|
|
*/
|
2023-12-04 11:41:30 -05:00
|
|
|
export default class ClientDaemonHandler {
|
|
|
|
|
@service session;
|
|
|
|
|
@service ipc;
|
|
|
|
|
|
|
|
|
|
constructor(context) {
|
|
|
|
|
setOwner(this, getOwner(context));
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-10 11:32:21 -05:00
|
|
|
async request(context, next) {
|
|
|
|
|
switch (context.request.op) {
|
|
|
|
|
case 'query': {
|
|
|
|
|
const { store, data } = context.request;
|
2023-11-30 14:09:00 -05:00
|
|
|
const { type, query, options: { pushToStore = true } = {} } = data;
|
2023-11-10 11:32:21 -05:00
|
|
|
const isClientDaemonRunning = await this.ipc.invoke(
|
2023-11-22 12:57:37 -06:00
|
|
|
'isClientDaemonRunning',
|
2023-11-10 11:32:21 -05:00
|
|
|
);
|
|
|
|
|
|
2023-11-22 12:57:37 -06:00
|
|
|
if (
|
|
|
|
|
!Object.keys(supportedTypes).includes(type) ||
|
|
|
|
|
!isClientDaemonRunning
|
|
|
|
|
) {
|
2023-11-10 11:32:21 -05:00
|
|
|
return next(context.request);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Remove usages of recursive from callers and scope_id since
|
|
|
|
|
// all calls to daemon are recursive, scope_id can be part of search query instead
|
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
2023-11-30 14:09:00 -05:00
|
|
|
let { recursive, scope_id, page, pageSize, ...remainingQuery } = query;
|
2023-11-22 12:57:37 -06:00
|
|
|
let searchQuery = '';
|
|
|
|
|
if (remainingQuery.query) {
|
|
|
|
|
const { search, filters } = remainingQuery.query;
|
|
|
|
|
|
|
|
|
|
searchQuery = generateMQLExpression({
|
|
|
|
|
search: {
|
|
|
|
|
text: search,
|
|
|
|
|
fields: supportedTypes[type],
|
|
|
|
|
},
|
|
|
|
|
filters,
|
|
|
|
|
});
|
|
|
|
|
}
|
2023-11-10 11:32:21 -05:00
|
|
|
const auth_token_id = this.session.data?.authenticated?.id;
|
2023-11-22 12:57:37 -06:00
|
|
|
remainingQuery = {
|
|
|
|
|
...remainingQuery,
|
|
|
|
|
query: searchQuery,
|
2023-11-10 11:32:21 -05:00
|
|
|
auth_token_id,
|
|
|
|
|
resource: pluralize(type),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Currently returns with a singular top level field with resource name
|
|
|
|
|
// e.g. { targets: [...] } or { sessions: [..] }
|
|
|
|
|
// So this just unwraps to the array, or undefined
|
|
|
|
|
const [results] = Object.values(
|
2023-11-22 12:57:37 -06:00
|
|
|
await this.ipc.invoke('searchClientDaemon', remainingQuery),
|
2023-11-10 11:32:21 -05:00
|
|
|
);
|
2023-11-30 14:09:00 -05:00
|
|
|
const payload = { items: paginateResults(results, page, pageSize) };
|
2023-11-10 11:32:21 -05:00
|
|
|
|
|
|
|
|
const schema = store.modelFor(type);
|
|
|
|
|
const serializer = store.serializerFor(type);
|
|
|
|
|
const normalizedPayload = serializer.normalizeResponse(
|
|
|
|
|
store,
|
|
|
|
|
schema,
|
|
|
|
|
payload,
|
|
|
|
|
null,
|
2023-11-22 12:57:37 -06:00
|
|
|
'query',
|
2023-11-10 11:32:21 -05:00
|
|
|
);
|
|
|
|
|
|
2023-11-30 14:09:00 -05:00
|
|
|
// Return the raw data if we don't push to the store.
|
|
|
|
|
let records = normalizedPayload.data;
|
|
|
|
|
if (pushToStore) {
|
|
|
|
|
records = store.push(normalizedPayload);
|
|
|
|
|
}
|
2023-11-10 11:32:21 -05:00
|
|
|
|
2023-11-30 14:09:00 -05:00
|
|
|
// Use a proxy array to store the metadata so the store's `query`
|
|
|
|
|
// array requirement is satisfied.
|
|
|
|
|
return ArrayProxy.create({
|
|
|
|
|
content: records,
|
|
|
|
|
meta: { totalItems: results?.length ?? 0 },
|
|
|
|
|
});
|
2023-11-10 11:32:21 -05:00
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
return next(context.request);
|
|
|
|
|
}
|
2023-11-30 14:09:00 -05:00
|
|
|
}
|
2023-12-04 11:41:30 -05:00
|
|
|
}
|