Files
threshold-ui/addons/api/addon/services/sqlite.js
T
Lisbet Alvarez 3ba93927fe Update copyright headers (#3146)
* empty commit

* Add missing copyright headers

* chore: 🤖 update copywrite config file with correct year

* Add missing copyright headers

* empty commit (gh action merge checks are stuck)
2026-01-28 17:09:06 -08:00

217 lines
5.6 KiB
JavaScript

/**
* Copyright IBM Corp. 2021, 2026
* SPDX-License-Identifier: BUSL-1.1
*/
import Service from '@ember/service';
import { PWBHost } from 'promise-worker-bi';
export const formatDbName = (userId, clusterUrl) =>
`boundary-${userId}-${clusterUrl}`;
// A mapping of columns to their expected model attributes for supported models
// The JSON data column is not included here but is assumed to always be last.
export const modelMapping = {
target: {
id: 'id',
type: 'type',
name: 'name',
description: 'description',
address: 'address',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
alias: {
id: 'id',
type: 'type',
name: 'name',
description: 'description',
destination_id: 'destination_id',
value: 'value',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
group: {
id: 'id',
name: 'name',
description: 'description',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
role: {
id: 'id',
name: 'name',
description: 'description',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
user: {
id: 'id',
name: 'name',
description: 'description',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
'credential-store': {
id: 'id',
type: 'type',
name: 'name',
description: 'description',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
scope: {
id: 'id',
type: 'type',
name: 'name',
description: 'description',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
'auth-method': {
id: 'id',
type: 'type',
name: 'name',
description: 'description',
is_primary: 'is_primary',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
'host-catalog': {
id: 'id',
type: 'type',
name: 'name',
description: 'description',
plugin_name: 'plugin.name',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
'session-recording': {
id: 'id',
type: 'type',
state: 'state',
start_time: 'start_time',
end_time: 'end_time',
duration: 'duration',
scope_id: 'scope.scope_id',
user_id: 'create_time_values.user.id',
user_name: 'create_time_values.user.name',
target_id: 'create_time_values.target.id',
target_name: 'create_time_values.target.name',
target_scope_id: 'create_time_values.target.scope.id',
target_scope_name: 'create_time_values.target.scope.name',
target_scope_parent_scope_id:
'create_time_values.target.scope.parent_scope_id',
created_time: 'created_time',
},
session: {
id: 'id',
type: 'type',
status: 'status',
endpoint: 'endpoint',
target_id: 'target_id',
user_id: 'user_id',
scope_id: 'scope.scope_id',
created_time: 'created_time',
},
};
export default class SqliteDbService extends Service {
// =attributes
// We need a handle on the original web worker to properly clean it up.
webWorker;
// This will be the worker we interact with that is wrapped by PWBHost.
worker;
setup(dbName) {
if (this.worker) {
// If the worker already exists, we don't need to do anything.
return;
}
// In production, our JS files will be fingerprinted with an MD5 hash
// so we need a way to map the worker file name to be able to find it.
//
// Ember is automagically replacing any URL path with the fingerprinted
// asset, and this is done by broccoli-asset-rev.
//
// The problem is it's a bit finicky as seen in this issue
// https://github.com/ember-cli/broccoli-asset-rewrite/issues/66
// and will not work properly if it's part of a template string or interpolated.
// Therefore, we need the URL path in a string somewhere to be replaced properly.
this.webWorker = new Worker('/workers/sqlite-worker.js', {
type: 'module',
});
// We use PWBHost to simplify communication so we can just send and receive
// messages with promises rather than having to manually set up event listeners
this.worker = new PWBHost(this.webWorker);
this.worker.register(async ({ method }) => {
if (method === 'getDatabaseName') {
return dbName;
}
});
}
fetchResource({ sql, parameters }) {
return this.worker.postMessage({
method: 'fetchResource',
payload: { sql, parameters },
});
}
insertResource(resource, items) {
return this.worker.postMessage({
method: 'insertResource',
payload: { resource, items, modelMapping },
});
}
deleteResource(resource, ids) {
return this.worker.postMessage({
method: 'deleteResource',
payload: { resource, ids },
});
}
analyzeDatabase() {
return this.worker.postMessage({ method: 'analyzeDatabase' });
}
async downloadDatabase() {
const byteArray = await this.worker.postMessage({
method: 'downloadDatabase',
});
const blob = new Blob([byteArray.buffer], {
type: 'application/x-sqlite3',
});
// Create temporary elements to initialize the download
const a = window.document.createElement('a');
window.document.body.appendChild(a);
a.href = window.URL.createObjectURL(blob);
a.download = 'boundary.db';
a.addEventListener('click', function () {
setTimeout(function () {
window.URL.revokeObjectURL(a.href);
a.remove();
}, 500);
});
a.click();
}
async clearDatabase() {
await this.worker.postMessage({ method: 'clearDatabase' });
}
async deleteDatabase() {
await this.worker.postMessage({ method: 'deleteDatabase' });
}
willDestroy() {
this.webWorker?.terminate();
}
}