mirror of
https://github.com/nasa/openmct.git
synced 2024-12-23 23:12:23 +00:00
Add script to lock object sub-tree and fix object locking bugs (#7855)
* Script for locking an object tree * Show lock button if locked * Do not allow properties editing of locked objects * Remove package-lock.json * Added p-debounce * Allow duplication of locked objects * Better user feedback * Add semaphores to prevent file handle exhaustion * Leverage official Apache Couch library - nano. Clean up dependencies. Default to environment variables for couch config. Simplify batching mechanism to make it synchronouse * Added lock user attribution * Remove unused code * Modify open script for adding auth design doc * Added script for creating auth design doc * Add css class for disallow unlock * Add user attribution to lock button * Fix import * Typo * User it was locked by, not current user. Wow. * Closes #7877 - Front-end sanding and shimming: displays <span> instead of button when domainObject.disallowUnlock. * Fixed bug where lock is shown even if object is not locked --------- Co-authored-by: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov> Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
This commit is contained in:
parent
c43ef64733
commit
703186adf1
94
package-lock.json
generated
94
package-lock.json
generated
@ -66,6 +66,7 @@
|
|||||||
"moment": "2.30.1",
|
"moment": "2.30.1",
|
||||||
"moment-duration-format": "2.3.2",
|
"moment-duration-format": "2.3.2",
|
||||||
"moment-timezone": "0.5.41",
|
"moment-timezone": "0.5.41",
|
||||||
|
"nano": "10.1.4",
|
||||||
"npm-run-all2": "6.1.2",
|
"npm-run-all2": "6.1.2",
|
||||||
"nyc": "15.1.0",
|
"nyc": "15.1.0",
|
||||||
"painterro": "1.2.87",
|
"painterro": "1.2.87",
|
||||||
@ -2463,6 +2464,12 @@
|
|||||||
"@mdn/browser-compat-data": "^5.2.34"
|
"@mdn/browser-compat-data": "^5.2.34"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/axe-core": {
|
"node_modules/axe-core": {
|
||||||
"version": "4.8.4",
|
"version": "4.8.4",
|
||||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.8.4.tgz",
|
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.8.4.tgz",
|
||||||
@ -2472,6 +2479,17 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.7.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
|
||||||
|
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/babel-loader": {
|
"node_modules/babel-loader": {
|
||||||
"version": "9.1.0",
|
"version": "9.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.0.tgz",
|
||||||
@ -3012,6 +3030,18 @@
|
|||||||
"node": ">=0.1.90"
|
"node": ">=0.1.90"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/comma-separated-values": {
|
"node_modules/comma-separated-values": {
|
||||||
"version": "3.6.4",
|
"version": "3.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/comma-separated-values/-/comma-separated-values-3.6.4.tgz",
|
"resolved": "https://registry.npmjs.org/comma-separated-values/-/comma-separated-values-3.6.4.tgz",
|
||||||
@ -4102,6 +4132,15 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
@ -5755,6 +5794,20 @@
|
|||||||
"node": ">=8.0.0"
|
"node": ">=8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/forwarded": {
|
"node_modules/forwarded": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||||
@ -7896,6 +7949,35 @@
|
|||||||
"multicast-dns": "cli.js"
|
"multicast-dns": "cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nano": {
|
||||||
|
"version": "10.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/nano/-/nano-10.1.4.tgz",
|
||||||
|
"integrity": "sha512-bJOFIPLExIbF6mljnfExXX9Cub4W0puhDjVMp+qV40xl/DBvgKao7St4+6/GB6EoHZap7eFnrnx4mnp5KYgwJA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.7.4",
|
||||||
|
"node-abort-controller": "^3.1.1",
|
||||||
|
"qs": "^6.13.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nano/node_modules/qs": {
|
||||||
|
"version": "6.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||||
|
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"side-channel": "^1.0.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.7",
|
"version": "3.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||||
@ -7935,6 +8017,12 @@
|
|||||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/node-abort-controller": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/node-fetch": {
|
"node_modules/node-fetch": {
|
||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
@ -9185,6 +9273,12 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/pump": {
|
"node_modules/pump": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||||
|
@ -69,6 +69,7 @@
|
|||||||
"moment": "2.30.1",
|
"moment": "2.30.1",
|
||||||
"moment-duration-format": "2.3.2",
|
"moment-duration-format": "2.3.2",
|
||||||
"moment-timezone": "0.5.41",
|
"moment-timezone": "0.5.41",
|
||||||
|
"nano": "10.1.4",
|
||||||
"npm-run-all2": "6.1.2",
|
"npm-run-all2": "6.1.2",
|
||||||
"nyc": "15.1.0",
|
"nyc": "15.1.0",
|
||||||
"painterro": "1.2.87",
|
"painterro": "1.2.87",
|
||||||
|
@ -109,8 +109,9 @@ class DuplicateAction {
|
|||||||
let currentParentKeystring = this.openmct.objects.makeKeyString(currentParent.identifier);
|
let currentParentKeystring = this.openmct.objects.makeKeyString(currentParent.identifier);
|
||||||
let parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.identifier);
|
let parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.identifier);
|
||||||
let objectKeystring = this.openmct.objects.makeKeyString(this.object.identifier);
|
let objectKeystring = this.openmct.objects.makeKeyString(this.object.identifier);
|
||||||
|
const isLocked = parentCandidate.locked === true;
|
||||||
|
|
||||||
if (!this.openmct.objects.isPersistable(parentCandidate.identifier)) {
|
if (isLocked || !this.openmct.objects.isPersistable(parentCandidate.identifier)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,10 +140,9 @@ class DuplicateAction {
|
|||||||
const parentType = parent && this.openmct.types.get(parent.type);
|
const parentType = parent && this.openmct.types.get(parent.type);
|
||||||
const child = objectPath[0];
|
const child = objectPath[0];
|
||||||
const childType = child && this.openmct.types.get(child.type);
|
const childType = child && this.openmct.types.get(child.type);
|
||||||
const locked = child.locked ? child.locked : parent && parent.locked;
|
|
||||||
const isPersistable = this.openmct.objects.isPersistable(child.identifier);
|
const isPersistable = this.openmct.objects.isPersistable(child.identifier);
|
||||||
|
|
||||||
if (locked || !isPersistable) {
|
if (!isPersistable) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ class EditPropertiesAction extends PropertiesAction {
|
|||||||
const definition = this._getTypeDefinition(object.type);
|
const definition = this._getTypeDefinition(object.type);
|
||||||
const persistable = this.openmct.objects.isPersistable(object.identifier);
|
const persistable = this.openmct.objects.isPersistable(object.identifier);
|
||||||
|
|
||||||
return persistable && definition && definition.creatable;
|
return persistable && definition && definition.creatable && !object.locked;
|
||||||
}
|
}
|
||||||
|
|
||||||
invoke(objectPath) {
|
invoke(objectPath) {
|
||||||
|
@ -96,6 +96,8 @@ export default {
|
|||||||
const createdTimestamp = this.domainObject.created;
|
const createdTimestamp = this.domainObject.created;
|
||||||
const createdBy = this.domainObject.createdBy ? this.domainObject.createdBy : UNKNOWN_USER;
|
const createdBy = this.domainObject.createdBy ? this.domainObject.createdBy : UNKNOWN_USER;
|
||||||
const modifiedBy = this.domainObject.modifiedBy ? this.domainObject.modifiedBy : UNKNOWN_USER;
|
const modifiedBy = this.domainObject.modifiedBy ? this.domainObject.modifiedBy : UNKNOWN_USER;
|
||||||
|
const locked = this.domainObject.locked;
|
||||||
|
const lockedBy = this.domainObject.lockedBy ?? UNKNOWN_USER;
|
||||||
const modifiedTimestamp = this.domainObject.modified
|
const modifiedTimestamp = this.domainObject.modified
|
||||||
? this.domainObject.modified
|
? this.domainObject.modified
|
||||||
: this.domainObject.created;
|
: this.domainObject.created;
|
||||||
@ -148,6 +150,13 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (locked === true) {
|
||||||
|
details.push({
|
||||||
|
name: 'Locked By',
|
||||||
|
value: lockedBy
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (version) {
|
if (version) {
|
||||||
details.push({
|
details.push({
|
||||||
name: 'Version',
|
name: 'Version',
|
||||||
|
191
src/plugins/persistence/couch/scripts/lockObjects.mjs
Normal file
191
src/plugins/persistence/couch/scripts/lockObjects.mjs
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import http from 'http';
|
||||||
|
import nano from 'nano';
|
||||||
|
import { parseArgs } from 'util';
|
||||||
|
|
||||||
|
const COUCH_URL = process.env.OPENMCT_COUCH_URL || 'http://127.0.0.1:5984';
|
||||||
|
const COUCH_DB_NAME = process.env.OPENMCT_DATABASE_NAME || 'openmct';
|
||||||
|
|
||||||
|
const {
|
||||||
|
values: { couchUrl, database, lock, unlock, startObjectKeystring, user, pass }
|
||||||
|
} = parseArgs({
|
||||||
|
options: {
|
||||||
|
couchUrl: {
|
||||||
|
type: 'string',
|
||||||
|
default: COUCH_URL
|
||||||
|
},
|
||||||
|
database: {
|
||||||
|
type: 'string',
|
||||||
|
short: 'd',
|
||||||
|
default: COUCH_DB_NAME
|
||||||
|
},
|
||||||
|
lock: {
|
||||||
|
type: 'boolean',
|
||||||
|
short: 'l'
|
||||||
|
},
|
||||||
|
unlock: {
|
||||||
|
type: 'boolean',
|
||||||
|
short: 'u'
|
||||||
|
},
|
||||||
|
startObjectKeystring: {
|
||||||
|
type: 'string',
|
||||||
|
short: 'o',
|
||||||
|
default: 'mine'
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
pass: {
|
||||||
|
type: 'string'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const BATCH_SIZE = 100;
|
||||||
|
const SOCKET_POOL_SIZE = 100;
|
||||||
|
|
||||||
|
const locked = lock === true;
|
||||||
|
console.info(`Connecting to ${couchUrl}/${database}`);
|
||||||
|
console.info(`${locked ? 'Locking' : 'Unlocking'} all children of ${startObjectKeystring}`);
|
||||||
|
|
||||||
|
const poolingAgent = new http.Agent({
|
||||||
|
keepAlive: true,
|
||||||
|
maxSockets: SOCKET_POOL_SIZE
|
||||||
|
});
|
||||||
|
|
||||||
|
const db = nano({
|
||||||
|
url: couchUrl,
|
||||||
|
requestDefaults: {
|
||||||
|
agent: poolingAgent
|
||||||
|
}
|
||||||
|
}).use(database);
|
||||||
|
db.auth(user, pass);
|
||||||
|
|
||||||
|
if (!unlock && !lock) {
|
||||||
|
throw new Error('Either -l or -u option is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const startObjectIdentifier = keystringToIdentifier(startObjectKeystring);
|
||||||
|
const documentBatch = [];
|
||||||
|
const alreadySeen = new Set();
|
||||||
|
let updatedDocumentCount = 0;
|
||||||
|
|
||||||
|
await processObjectTreeFrom(startObjectIdentifier);
|
||||||
|
//Persist final batch
|
||||||
|
await persistBatch();
|
||||||
|
console.log(`Processed ${updatedDocumentCount} documents`);
|
||||||
|
|
||||||
|
function processObjectTreeFrom(parentObjectIdentifier) {
|
||||||
|
//1. Fetch document for identifier;
|
||||||
|
return fetchDocument(parentObjectIdentifier)
|
||||||
|
.then(async (document) => {
|
||||||
|
if (document !== undefined) {
|
||||||
|
if (!alreadySeen.has(document._id)) {
|
||||||
|
alreadySeen.add(document._id);
|
||||||
|
//2. Lock or unlock object
|
||||||
|
document.model.locked = locked;
|
||||||
|
document.model.disallowUnlock = locked;
|
||||||
|
|
||||||
|
if (locked) {
|
||||||
|
document.model.lockedBy = 'script';
|
||||||
|
} else {
|
||||||
|
delete document.model.lockedBy;
|
||||||
|
}
|
||||||
|
//3. Push document to a batch
|
||||||
|
documentBatch.push(document);
|
||||||
|
//4. Persist batch if necessary, reporting failures
|
||||||
|
await persistBatchIfNeeded();
|
||||||
|
//5. Repeat for each composee
|
||||||
|
const composition = document.model.composition || [];
|
||||||
|
return Promise.all(
|
||||||
|
composition.map((composee) => {
|
||||||
|
return processObjectTreeFrom(composee);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(`Error ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchDocument(identifierOrKeystring) {
|
||||||
|
let keystring;
|
||||||
|
if (typeof identifierOrKeystring === 'object') {
|
||||||
|
keystring = identifierToKeystring(identifierOrKeystring);
|
||||||
|
} else {
|
||||||
|
keystring = identifierOrKeystring;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const document = await db.get(keystring);
|
||||||
|
|
||||||
|
return document;
|
||||||
|
} catch (error) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function persistBatchIfNeeded() {
|
||||||
|
if (documentBatch.length >= BATCH_SIZE) {
|
||||||
|
return persistBatch();
|
||||||
|
} else {
|
||||||
|
//Noop - batch is not big enough yet
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function persistBatch() {
|
||||||
|
try {
|
||||||
|
const localBatch = [].concat(documentBatch);
|
||||||
|
|
||||||
|
//Immediately clear the shared batch array. This asynchronous process is non-blocking, and
|
||||||
|
//we don't want to try and persist the same batch multiple times while we are waiting for
|
||||||
|
//the subsequent bulk operation to complete.
|
||||||
|
updatedDocumentCount += documentBatch.length;
|
||||||
|
|
||||||
|
documentBatch.splice(0, documentBatch.length);
|
||||||
|
const response = await db.bulk({ docs: localBatch });
|
||||||
|
|
||||||
|
if (response instanceof Array) {
|
||||||
|
response.forEach((r) => {
|
||||||
|
console.info(JSON.stringify(r));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.info(JSON.stringify(response));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Array) {
|
||||||
|
error.forEach((e) => console.error(JSON.stringify(e)));
|
||||||
|
} else {
|
||||||
|
console.error(`${error.statusCode} - ${error.reason}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function keystringToIdentifier(keystring) {
|
||||||
|
const tokens = keystring.split(':');
|
||||||
|
if (tokens.length === 2) {
|
||||||
|
return {
|
||||||
|
namespace: tokens[0],
|
||||||
|
key: tokens[1]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
namespace: '',
|
||||||
|
key: tokens[0]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function identifierToKeystring(identifier) {
|
||||||
|
if (typeof identifier === 'string') {
|
||||||
|
return identifier;
|
||||||
|
} else if (typeof identifier === 'object') {
|
||||||
|
if (identifier.namespace) {
|
||||||
|
return `${identifier.namespace}:${identifier.key}`;
|
||||||
|
} else {
|
||||||
|
return identifier.key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -160,6 +160,24 @@ add_index_and_views() {
|
|||||||
echo "Unable to create annotation_keystring_index"
|
echo "Unable to create annotation_keystring_index"
|
||||||
echo $response
|
echo $response
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Add auth database for locked objects
|
||||||
|
response=$(curl --silent --user "${CURL_USERPASS_ARG}" --request PUT "$COUCH_BASE_LOCAL"/"$OPENMCT_DATABASE_NAME"/_design/auth \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data '{
|
||||||
|
"_id": "_design/auth",
|
||||||
|
"language": "javascript",
|
||||||
|
"validate_doc_update": "function (newDoc, oldDoc, userCtx) { if (userCtx.roles.indexOf('\''_admin'\'') !== -1) { return; } else if (oldDoc === null) { return; } else if (oldDoc.model.type === '\''timer'\'' || oldDoc.model.type === '\''notebook'\'' || oldDoc.model.type === '\''restricted-notebook'\'') { if (oldDoc.model.name !== newDoc.model.name) { throw ({ forbidden: '\''Read-only object'\'' }); } else { return; } } else if (oldDoc.model.locked === true && oldDoc.model.disallowUnlock === true) { throw ({ forbidden: '\''Read-only object'\'' }); } else { return; }}"
|
||||||
|
}')
|
||||||
|
|
||||||
|
if [[ $response =~ "\"ok\":true" ]]; then
|
||||||
|
echo "Successfully created _design/auth design document for locked objects"
|
||||||
|
elif [[ $response =~ "\"error\":\"conflict\"" ]]; then
|
||||||
|
echo "_design/auth already exists, skipping creation"
|
||||||
|
else
|
||||||
|
echo "Unable to create _design/auth"
|
||||||
|
echo $response
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main script execution
|
# Main script execution
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
ref="objectName"
|
ref="objectName"
|
||||||
class="l-browse-bar__object-name c-object-label__name"
|
class="l-browse-bar__object-name c-object-label__name"
|
||||||
:class="{ 'c-input-inline': isPersistable }"
|
:class="{ 'c-input-inline': isPersistable }"
|
||||||
:contenteditable="isPersistable"
|
:contenteditable="isNameEditable"
|
||||||
@blur="updateName"
|
@blur="updateName"
|
||||||
@keydown.enter.prevent
|
@keydown.enter.prevent
|
||||||
@keyup.enter.prevent="updateNameOnEnterKeyPress"
|
@keyup.enter.prevent="updateNameOnEnterKeyPress"
|
||||||
@ -78,7 +78,7 @@
|
|||||||
></button>
|
></button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
v-if="isViewEditable & !isEditing"
|
v-if="shouldShowLock"
|
||||||
:aria-label="lockedOrUnlockedTitle"
|
:aria-label="lockedOrUnlockedTitle"
|
||||||
:title="lockedOrUnlockedTitle"
|
:title="lockedOrUnlockedTitle"
|
||||||
:class="{
|
:class="{
|
||||||
@ -88,6 +88,13 @@
|
|||||||
@click="toggleLock(!domainObject.locked)"
|
@click="toggleLock(!domainObject.locked)"
|
||||||
></button>
|
></button>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-else-if="domainObject?.locked"
|
||||||
|
class="icon-lock"
|
||||||
|
aria-label="Locked for editing, cannot be unlocked."
|
||||||
|
title="Locked for editing, cannot be unlocked."
|
||||||
|
></span>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
v-if="isViewEditable && !isEditing && !domainObject.locked"
|
v-if="isViewEditable && !isEditing && !domainObject.locked"
|
||||||
class="l-browse-bar__actions__edit c-button c-button--major icon-pencil"
|
class="l-browse-bar__actions__edit c-button c-button--major icon-pencil"
|
||||||
@ -180,6 +187,18 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
isNameEditable() {
|
||||||
|
return this.isPersistable && !this.domainObject.locked;
|
||||||
|
},
|
||||||
|
shouldShowLock() {
|
||||||
|
if (this.domainObject === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.domainObject.disallowUnlock) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.domainObject.locked || (this.isViewEditable && !this.isEditing);
|
||||||
|
},
|
||||||
statusClass() {
|
statusClass() {
|
||||||
return this.status ? `is-status--${this.status}` : '';
|
return this.status ? `is-status--${this.status}` : '';
|
||||||
},
|
},
|
||||||
@ -253,11 +272,19 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
lockedOrUnlockedTitle() {
|
lockedOrUnlockedTitle() {
|
||||||
|
let title;
|
||||||
if (this.domainObject.locked) {
|
if (this.domainObject.locked) {
|
||||||
return 'Locked for editing - click to unlock.';
|
if (this.domainObject.lockedBy !== undefined) {
|
||||||
|
title = `Locked for editing by ${this.domainObject.lockedBy}. `;
|
||||||
} else {
|
} else {
|
||||||
return 'Unlocked for editing - click to lock.';
|
title = 'Locked for editing. ';
|
||||||
}
|
}
|
||||||
|
title += 'Click to unlock.';
|
||||||
|
} else {
|
||||||
|
title = 'Unlocked for editing, click to lock.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return title;
|
||||||
},
|
},
|
||||||
domainObjectName() {
|
domainObjectName() {
|
||||||
return this.domainObject?.name ?? '';
|
return this.domainObject?.name ?? '';
|
||||||
@ -288,7 +315,6 @@ export default {
|
|||||||
document.addEventListener('click', this.closeViewAndSaveMenu);
|
document.addEventListener('click', this.closeViewAndSaveMenu);
|
||||||
this.promptUserbeforeNavigatingAway = this.promptUserbeforeNavigatingAway.bind(this);
|
this.promptUserbeforeNavigatingAway = this.promptUserbeforeNavigatingAway.bind(this);
|
||||||
window.addEventListener('beforeunload', this.promptUserbeforeNavigatingAway);
|
window.addEventListener('beforeunload', this.promptUserbeforeNavigatingAway);
|
||||||
|
|
||||||
this.openmct.editor.on('isEditing', (isEditing) => {
|
this.openmct.editor.on('isEditing', (isEditing) => {
|
||||||
this.isEditing = isEditing;
|
this.isEditing = isEditing;
|
||||||
});
|
});
|
||||||
@ -421,8 +447,27 @@ export default {
|
|||||||
this.actionCollection.off('update', this.updateActionItems);
|
this.actionCollection.off('update', this.updateActionItems);
|
||||||
delete this.actionCollection;
|
delete this.actionCollection;
|
||||||
},
|
},
|
||||||
toggleLock(flag) {
|
async toggleLock(flag) {
|
||||||
|
if (!this.domainObject.disallowUnlock) {
|
||||||
|
const wasTransactionActive = this.openmct.objects.isTransactionActive();
|
||||||
|
let transaction;
|
||||||
|
|
||||||
|
if (!wasTransactionActive) {
|
||||||
|
transaction = this.openmct.objects.startTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
this.openmct.objects.mutate(this.domainObject, 'locked', flag);
|
this.openmct.objects.mutate(this.domainObject, 'locked', flag);
|
||||||
|
const user = await this.openmct.user.getCurrentUser();
|
||||||
|
|
||||||
|
if (user !== undefined) {
|
||||||
|
this.openmct.objects.mutate(this.domainObject, 'lockedBy', user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wasTransactionActive) {
|
||||||
|
await transaction.commit();
|
||||||
|
this.openmct.objects.endTransaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setStatus(status) {
|
setStatus(status) {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
|
Loading…
Reference in New Issue
Block a user