diff --git a/docker/server/Dockerfile b/docker/server/Dockerfile index 9d90885ff..6c94bfd49 100644 --- a/docker/server/Dockerfile +++ b/docker/server/Dockerfile @@ -3,9 +3,10 @@ ARG BASE_IMAGE=node:20.11.1-slim FROM $BASE_IMAGE LABEL author="NGA" -ARG SERVER_VERSION=6.5.1-beta +ARG SERVER_VERSION=6.5.2 ARG ARC_VERSION=NONE ARG SFTP_VERSION=NONE +ARG MSI_VERSION=NONE USER root @@ -22,8 +23,9 @@ WORKDIR ${MAGE_HOME} RUN ls -l \ && npm i --omit dev @ngageoint/mage.service@${SERVER_VERSION} \ && npm i --omit dev @ngageoint/mage.web-app@${SERVER_VERSION} \ - && if [ "$ARC_VERSION" != "NONE" ]; then npm i --omit dev @ngageoint/mage.arcgis.service@${ARC_VERSION} @ngageoint/mage.arcgis.web-app@${ARC_VERSION}; fi\ - && if [ "$SFTP_VERSION" != "NONE" ]; then npm i --omit dev @ngageoint/mage.sftp.service@${SFTP_VERSION} @ngageoint/mage.sftp.web@${SFTP_VERSION}; fi \ + && if [ "$ARC_VERSION" != "NONE" ]; then npm i --omit dev --legacy-peer-deps @ngageoint/mage.arcgis.service@${ARC_VERSION} @ngageoint/mage.arcgis.web-app@${ARC_VERSION}; fi\ + && if [ "$SFTP_VERSION" != "NONE" ]; then npm i --omit dev --legacy-peer-deps @ngageoint/mage.sftp.service@${SFTP_VERSION} @ngageoint/mage.sftp.web@${SFTP_VERSION}; fi \ + && if [ "$MSI_VERSION" != "NONE" ]; then npm i --omit dev --legacy-peer-deps @ngageoint/mage.nga-msi@${MSI_VERSION}; fi \ && ln -s ./node_modules/.bin/mage.service VOLUME /var/lib/mage @@ -32,5 +34,6 @@ EXPOSE 4242 ENTRYPOINT [ "./mage.service", \ "--plugin", "@ngageoint/mage.arcgis.service", \ "--plugin", "@ngageoint/mage.sftp.service", \ + "--plugin", "@ngageoint/mage.nga-msi", \ "--web-plugin", "@ngageoint/mage.arcgis.web-app", \ "--web-plugin", "@ngageoint/mage.sftp.web"] diff --git a/instance/config.js b/instance/config.js index fdc7ce8d2..cdda9cc5d 100644 --- a/instance/config.js +++ b/instance/config.js @@ -36,7 +36,8 @@ module.exports = { plugins: { servicePlugins: [ '@ngageoint/mage.arcgis.service', - '@ngageoint/mage.sftp.service' + '@ngageoint/mage.sftp.service', + '@ngageoint/mage.nga-msi' ], webUIPlugins: [ '@ngageoint/mage.arcgis.web-app', diff --git a/instance/package.json b/instance/package.json index 185940456..97f3e8326 100644 --- a/instance/package.json +++ b/instance/package.json @@ -1,6 +1,6 @@ { "name": "@ngageoint/mage.dev-instance", - "version": "6.5.1-beta", + "version": "6.5.3", "description": "Assemble a Mage Server deployment from the core service, the web-app, and selected plugins. This is primarily a development tool because the dependencies point to relative directories instead of production packages. This can however serve as a starting point to create a production Mage instance package.json.", "scripts": { "start": "npm run start:dev", @@ -28,6 +28,7 @@ "@ngageoint/mage.arcgis.web-app": "../plugins/arcgis/web-app/dist/main", "@ngageoint/mage.sftp.web": "../plugins/sftp/web/dist/main", "@ngageoint/mage.sftp.service": "../plugins/sftp/service", + "@ngageoint/mage.nga-msi": "../plugins/nga-msi", "@ngageoint/mage.service": "../service", "@ngageoint/mage.web-app": "../web-app/dist" } diff --git a/package-lock.json b/package-lock.json index 0230b1c9f..d6908509a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ngageoint/mage.project", - "version": "6.5.1-beta", + "version": "6.5.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ngageoint/mage.project", - "version": "6.5.1-beta", + "version": "6.5.3", "hasInstallScript": true, "dependencies": { "p-limit": "^7.1.1" @@ -2101,4 +2101,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 84c588b84..e26494d49 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,13 @@ "name": "@ngageoint/mage.project", "description": "This is the root package definition for the mage-server monorepo.", "private": true, - "version": "6.5.1-beta", + "version": "6.5.3", "files": [], "scripts": { - "postinstall": "npm-run-all service:ci web-app:ci arcgis:ci sftp:ci", + "postinstall": "npm-run-all service:ci web-app:ci arcgis:ci sftp:ci nga-msi:ci", "install:resolve": "npm-run-all service:install web-app:install", "build": "npm-run-all service:build web-app:build instance:build", - "build-all": "npm-run-all build arcgis:build sftp:build", + "build-all": "npm-run-all build arcgis:build sftp:build nga-msi:build", "pack-all": "npm-run-all service:pack web-app:pack image.service:pack nga-msi:pack", "service:install": "npm install --prefix service", "service:ci": "npm ci --prefix service", @@ -39,8 +39,7 @@ "image.service:ci": "npm ci --prefix plugins/image/service", "image.service:build": "npm run build --prefix plugins/image/service", "image.service:pack": "npm pack ./plugins/image/service", - "nga-msi:install": "npm install --prefix plugins/nga-msi", - "nga-msi:ci": "npm ci --prefix plugins/nga-msi", + "nga-msi:ci": "cd plugins/nga-msi && npm ci && npm link ../../service", "nga-msi:build": "npm run build --prefix plugins/nga-msi", "nga-msi:pack": "npm pack ./plugins/nga-msi", "start": "npm run instance:start", diff --git a/plugins/nga-msi/package-lock.json b/plugins/nga-msi/package-lock.json index da8ff6adb..658a217e2 100644 --- a/plugins/nga-msi/package-lock.json +++ b/plugins/nga-msi/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ngageoint/mage.nga-msi", - "version": "1.0.5", + "version": "1.0.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ngageoint/mage.nga-msi", - "version": "1.0.5", + "version": "1.0.6", "license": "MIT", "dependencies": { "axios": "0.27.2" @@ -20,7 +20,7 @@ "typescript": "5.8.2" }, "peerDependencies": { - "@ngageoint/mage.service": "6.3.0-beta.7" + "@ngageoint/mage.service": ">=6.3.0 || 6.5.0-beta.1" } }, "node_modules/@ampproject/remapping": { @@ -896,9 +896,9 @@ } }, "node_modules/@ngageoint/mage.service": { - "version": "6.3.0-beta.7", - "resolved": "https://registry.npmjs.org/@ngageoint/mage.service/-/mage.service-6.3.0-beta.7.tgz", - "integrity": "sha512-hauwJfO7gg/+Gh+bfmoj1HbB0VFJB+/X+igAZVehidV9V39cwI/KlIJKUiie/6qZqK5bsMDhJ66N638qcfK2lA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@ngageoint/mage.service/-/mage.service-6.3.0.tgz", + "integrity": "sha512-70TfpGHIhQ5TlGZ8NrjMLGCw4p6Su2jtDLc9SfqC6c0GYFPW9Z3yTFtDyiTYFbQSu1qrWvMIrZ4t4TJGNZDdUQ==", "hasShrinkwrap": true, "peer": true, "dependencies": { diff --git a/plugins/nga-msi/package.json b/plugins/nga-msi/package.json index a449a8e64..a3a6e1e07 100644 --- a/plugins/nga-msi/package.json +++ b/plugins/nga-msi/package.json @@ -1,6 +1,6 @@ { "name": "@ngageoint/mage.nga-msi", - "version": "1.0.5", + "version": "1.0.6", "description": "The NGA-MSI package is a Mage server plugin that provides feeds from National Geospatial-Intelligence Agency's Maritime Safety Information API.", "main": "lib/index.js", "files": [ @@ -49,6 +49,6 @@ "typescript": "5.8.2" }, "peerDependencies": { - "@ngageoint/mage.service": "6.3.0-beta.7" + "@ngageoint/mage.service": ">=6.3.0 || 6.5.0-beta.1" } } \ No newline at end of file diff --git a/plugins/nga-msi/tsconfig.json b/plugins/nga-msi/tsconfig.json index 60c36ca2a..a7a699c2b 100644 --- a/plugins/nga-msi/tsconfig.json +++ b/plugins/nga-msi/tsconfig.json @@ -1,21 +1,21 @@ { - "include": [ - ], - "exclude": [ - ], - "references": [ - ], + "include": [], + "exclude": [], + "references": [], "compilerOptions": { /* Basic Options */ - "target": "ES2016", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - "lib": [ "ES2016" ], /* Specify library files to be included in the compilation. */ - "allowJs": true, /* Allow javascript files to be compiled. */ + "target": "ES2016", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + "lib": [ + "ES2016", + "DOM" + ], /* Specify library files to be included in the compilation. */ + "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - "declaration": true, /* Generates corresponding '.d.ts' file. */ - "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - "sourceMap": true, /* Generates corresponding '.map' file. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ // "outDir": "./lib", /* Redirect output structure to the directory. */ // "rootDir": ".", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ @@ -26,7 +26,7 @@ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ - "strict": true, /* Enable all strict type-checking options. */ + "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ @@ -34,32 +34,28 @@ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - /* Additional Checks */ // "noUnusedLocals": true, /* Report errors on unused locals. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - /* Module Resolution Options */ - "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - /* Source Map Options */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ } -} +} \ No newline at end of file diff --git a/service/npm-shrinkwrap.json b/service/npm-shrinkwrap.json index 1f63e2f96..a0543b8f7 100644 --- a/service/npm-shrinkwrap.json +++ b/service/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "@ngageoint/mage.service", - "version": "6.5.1-beta", + "version": "6.5.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ngageoint/mage.service", - "version": "6.5.1-beta", + "version": "6.5.3", "dependencies": { "@ngageoint/geopackage": "4.2.6", "@ngageoint/mongodb-migrations": "1.0.0", @@ -12829,4 +12829,4 @@ } } } -} +} \ No newline at end of file diff --git a/service/package.json b/service/package.json index 1649bb046..58aa96991 100644 --- a/service/package.json +++ b/service/package.json @@ -1,6 +1,6 @@ { "name": "@ngageoint/mage.service", - "version": "6.5.1-beta", + "version": "6.5.3", "displayName": "Mage Service", "description": "Mage is a geospatial situational awareness and data collection platform. The Mage Service is the ReST service API that the Mage client apps use to interact with Mage data.", "keywords": [ diff --git a/service/src/api/form.js b/service/src/api/form.js index 31097a938..a99e31211 100644 --- a/service/src/api/form.js +++ b/service/src/api/form.js @@ -32,22 +32,22 @@ function compareDisplayName(a, b) { } function getUserFields(form) { - return form.fields.filter(function(field) { + return form.fields.filter(function (field) { if (field.archived) return false; - return form.userFields.some(function(memberField) { + return form.userFields.some(function (memberField) { return memberField === field.name; }); }); } -Form.prototype.populateUserFields = function(callback) { +Form.prototype.populateUserFields = function (callback) { var event = this._event; var form = this._form; var forms = form ? [form] : event.forms; var formsUserFields = []; - forms.forEach(function(form) { + forms.forEach(function (form) { var userFields = getUserFields(form); if (userFields.length) { formsUserFields.push(userFields); @@ -67,7 +67,7 @@ Form.prototype.populateUserFields = function(callback) { clean-up. */ const teamIds = (event.teamIds || []).map(x => x._id ? x._id : x) - Team.getTeams({ teamIds }, function(err, teams) { + Team.getTeams({ teamIds }, function (err, teams) { if (err) { console.error(err); return callback(err); @@ -76,8 +76,8 @@ Form.prototype.populateUserFields = function(callback) { var choices = []; var users = {}; - teams.forEach(function(team) { - team.userIds.forEach(function(user) { + teams.forEach(function (team) { + team.userIds.forEach(function (user) { users[user.displayName] = user.displayName; }); }); @@ -93,12 +93,12 @@ Form.prototype.populateUserFields = function(callback) { } // Update the choices for user field - formsUserFields.forEach(function(userFields) { - userFields.forEach(function(userField) { + formsUserFields.forEach(function (userFields) { + userFields.forEach(function (userField) { userField.choices = choices.slice(); if (!userField.required && userField.type === 'dropdown') { - userField.choices.unshift({id: 0, value: 0, title: ""}); + userField.choices.unshift({ id: 0, value: 0, title: "" }); } }); }); @@ -107,15 +107,15 @@ Form.prototype.populateUserFields = function(callback) { }); }; -Form.prototype.export = function(formId, callback) { +Form.prototype.export = function (formId, callback) { var iconBasePath = new api.Icon(this._event._id).getBasePath(); var formBasePath = path.join(iconBasePath, formId.toString()); var archive = archiver('zip'); archive.directory(formBasePath, 'form/icons'); - archive.append("", {name: 'icons/', prefix: 'form'}); + archive.append("", { name: 'icons/', prefix: 'form' }); - var forms = this._event.forms.filter(function(form) { + var forms = this._event.forms.filter(function (form) { return form._id === formId; }); @@ -125,7 +125,7 @@ Form.prototype.export = function(formId, callback) { return callback(err); } - archive.append(JSON.stringify(forms[0]), {name: "form/form.json"}); + archive.append(JSON.stringify(forms[0]), { name: "form/form.json" }); archive.finalize(); callback(null, { @@ -134,7 +134,7 @@ Form.prototype.export = function(formId, callback) { }); }; -Form.prototype.validate = function(file, callback) { +Form.prototype.validate = function (file, callback) { let archiveError = new Error('Form archive file is invalid, please choose a valid file.'); archiveError.status = 400; @@ -169,7 +169,7 @@ Form.prototype.validate = function(file, callback) { callback(null, form); }; -Form.prototype.importIcons = function(file, form, callback) { +Form.prototype.importIcons = function (file, form, callback) { var event = this._event; var zip = new Zip(file.path); @@ -177,11 +177,19 @@ Form.prototype.importIcons = function(file, form, callback) { if (iconsEntry) { var iconPath = path.join(new api.Icon(event._id).getBasePath(), form._id.toString()) + path.sep; - zip.extractEntryTo(iconsEntry, iconPath, false, false); + // Extract all entries within form/icons/ + zip.getEntries().forEach(function (entry) { + if (entry.entryName.startsWith('form/icons/') && !entry.isDirectory) { + var relativePath = entry.entryName.substring('form/icons/'.length); + var targetDir = path.join(iconPath, path.dirname(relativePath)); + var fileName = path.basename(relativePath); + zip.extractEntryTo(entry, targetDir, false, true, false, fileName); + } + }); // for each file in each directory var walker = walk.walk(iconPath); - walker.on("file", function(filePath, stat, next) { + walker.on("file", function (filePath, stat, next) { var primary = null; var variant = null; var regex = new RegExp(iconPath + path.sep + "+(.*)"); @@ -193,11 +201,11 @@ Form.prototype.importIcons = function(file, form, callback) { variant = variants.shift(); } - new api.Icon(event._id, form._id, primary, variant).add({name: stat.name}, function(err) { + new api.Icon(event._id, form._id, primary, variant).add({ name: stat.name }, function (err) { next(err); }); }); - walker.on("end", function() { + walker.on("end", function () { callback(null); }); } else { @@ -262,7 +270,7 @@ function cleanForm(form) { return form.userFields.includes(field.name); }); - form.userFields = [...userFields.reduce((fields, field) => { + form.userFields = [...userFields.reduce((fields, field) => { if (form.userFields.includes(field.name)) { fields.add(fieldName(field.id)); } diff --git a/service/src/routes/events.ts b/service/src/routes/events.ts index a39ee6350..62f89d865 100644 --- a/service/src/routes/events.ts +++ b/service/src/routes/events.ts @@ -211,16 +211,16 @@ function EventRoutes(app: express.Application, security: { authentication: authe if (req.parameters!.userId) { filter.userId = req.parameters!.userId } - const limit = req.query!.limit || 20 - const start = req.query!.start || 0 - EventModel.getEvents({ access: req.access, filter: filter, populate: req.parameters!.populate, projection: req.parameters!.projection, limit: limit, start: start }, (err, events, totalCount) => { + const pageSize = parseInt(String(req.query.page_size)) || parseInt(String(req.query.limit)) || 20 + const page = parseInt(String(req.query.page)) || parseInt(String(req.query.start)) || 0 + EventModel.getEvents({ access: req.access, filter: filter, populate: req.parameters!.populate, projection: req.parameters!.projection, limit: pageSize, start: page }, (err, events, totalCount) => { if (err) { return next(err); } if (req.query.includePagination) { res.json({ - pageSize: limit, - pageIndex: start, + pageSize: pageSize, + page: page, items: events!.map((event) => { return event.toObject({ access: req.access!, projection: req.parameters!.projection }); }), diff --git a/web-app/admin/src/ng1/admin/events/events.component.js b/web-app/admin/src/ng1/admin/events/events.component.js index e6f9fb537..834c42f39 100644 --- a/web-app/admin/src/ng1/admin/events/events.component.js +++ b/web-app/admin/src/ng1/admin/events/events.component.js @@ -9,10 +9,12 @@ class AdminEventsController { this.UserService = UserService; this.events = []; + this.totalEvents = 0; this.filter = 'active'; // possible values all, active, complete this.page = 0; this.itemsPerPage = 10; this.eventSearch = ''; + this.searchTimeout = null; this.projection = { name: true, @@ -20,57 +22,70 @@ class AdminEventsController { acl: true, complete: true }; - - // For some reason angular is not calling into filter function with correct context - this.filterEvents = this._filterEvents.bind(this); - this.filterComplete = this._filterComplete.bind(this); } $onInit() { - this.Event.query( - { - state: 'all', - populate: false, - projection: JSON.stringify(this.projection) - }, - (events) => { - this.events = events; - } - ); + this.loadEvents(); + } + + loadEvents() { + const params = { + state: this.filter, + populate: false, + projection: JSON.stringify(this.projection), + page: this.page, + page_size: this.itemsPerPage, + includePagination: true + }; + + if (this.eventSearch && this.eventSearch.trim()) { + params.term = this.eventSearch.trim(); + } + + this.Event.queryWithPagination(params, (response) => { + this.events = response.items || []; + this.totalEvents = response.totalCount || 0; + }); } handleSearchChange() { - const filtered = this.events.filter((event) => this._filterEvents(event)); + if (this.searchTimeout) { + clearTimeout(this.searchTimeout); + } + this.searchTimeout = setTimeout(() => { + this.page = 0; + this.loadEvents(); + }, 300); + } + + handleFilterChange() { + this.page = 0; + this.loadEvents(); + } - // If there are filtered results, reset to page 0, else keep the current page - this.page = filtered.length > 0 ? 0 : this.page; + handlePageChange() { + this.loadEvents(); } - _filterEvents(event) { - const searchTerm = this.eventSearch.trim().toLowerCase(); - if (!searchTerm) { - return true; + nextPage() { + if ((this.page + 1) * this.itemsPerPage < this.totalEvents) { + this.page++; + this.loadEvents(); } - const nameMatches = event.name.toLowerCase().includes(searchTerm); - const descriptionMatches = - event.description && event.description.toLowerCase().includes(searchTerm); - return nameMatches || descriptionMatches; - } - - _filterComplete(event) { - switch (this.filter) { - case 'all': - return true; - case 'active': - return !event.complete; - case 'complete': - return event.complete; + } + + previousPage() { + if (this.page > 0) { + this.page--; + this.loadEvents(); } } reset() { this.page = 0; this.eventSearch = ''; + this.filter = 'active'; + this.loadEvents(); } newEvent() { diff --git a/web-app/admin/src/ng1/admin/events/events.html b/web-app/admin/src/ng1/admin/events/events.html index a90a72029..0a4c6cd38 100644 --- a/web-app/admin/src/ng1/admin/events/events.html +++ b/web-app/admin/src/ng1/admin/events/events.html @@ -9,39 +9,18 @@
@@ -49,13 +28,8 @@
- +

@@ -73,11 +47,7 @@
@@ -87,10 +57,7 @@
-
+
@@ -100,18 +67,12 @@
{{e.description}}
- -
@@ -122,20 +83,10 @@ @@ -144,4 +95,4 @@
-
+
\ No newline at end of file diff --git a/web-app/admin/src/ng1/factories/event.resource.js b/web-app/admin/src/ng1/factories/event.resource.js index 971e28991..3ef952298 100644 --- a/web-app/admin/src/ng1/factories/event.resource.js +++ b/web-app/admin/src/ng1/factories/event.resource.js @@ -1,7 +1,7 @@ function Event($rootScope, $resource) { const Event = $resource('/api/events/:id', { id: '@id' - },{ + }, { get: { method: 'GET', responseType: 'json' @@ -22,6 +22,11 @@ function Event($rootScope, $resource) { isArray: true, responseType: 'json' }, + queryWithPagination: { + method: 'GET', + responseType: 'json', + isArray: false + }, count: { method: 'GET', url: '/api/events/count', @@ -101,7 +106,7 @@ function Event($rootScope, $resource) { } }); - Event.prototype.$save = function(success, error) { + Event.prototype.$save = function (success, error) { if (this.id) { this.$update(success, error); } else { @@ -118,7 +123,7 @@ function EventAccess($resource) { const EventAccess = $resource('/api/events/:eventId/acl', { eventId: '@eventId', userId: '@userId' - },{ + }, { update: { method: 'PUT', headers: { diff --git a/web-app/package-lock.json b/web-app/package-lock.json index e556f6e37..9934b5815 100644 --- a/web-app/package-lock.json +++ b/web-app/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ngageoint/mage.web-app", - "version": "6.5.1-beta", + "version": "6.5.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ngageoint/mage.web-app", - "version": "6.5.1-beta", + "version": "6.5.3", "license": "Apache-2.0", "dependencies": { "@ajsf/core": "0.8.0", @@ -17632,4 +17632,4 @@ "license": "MIT" } } -} +} \ No newline at end of file diff --git a/web-app/package.json b/web-app/package.json index 5b3e6533a..d3f339695 100644 --- a/web-app/package.json +++ b/web-app/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@ngageoint/mage.web-app", - "version": "6.5.1-beta", + "version": "6.5.3", "description": "The Mage web-app is the UI for interacting with the Mage service in a web browser.", "keywords": [ "NGA", diff --git a/web-app/projects/core-lib/package-lock.json b/web-app/projects/core-lib/package-lock.json index f5f5a74af..d4a2cc876 100644 --- a/web-app/projects/core-lib/package-lock.json +++ b/web-app/projects/core-lib/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ngageoint/mage.web-core-lib", - "version": "6.5.1-beta", + "version": "6.5.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ngageoint/mage.web-core-lib", - "version": "6.5.1-beta", + "version": "6.5.3", "dependencies": { "@rollup/plugin-commonjs": "25.0.8", "@rollup/plugin-node-resolve": "15.3.1", @@ -11086,4 +11086,4 @@ } } } -} +} \ No newline at end of file diff --git a/web-app/projects/core-lib/package.json b/web-app/projects/core-lib/package.json index 393ff23f0..87ef35b63 100644 --- a/web-app/projects/core-lib/package.json +++ b/web-app/projects/core-lib/package.json @@ -1,6 +1,6 @@ { "name": "@ngageoint/mage.web-core-lib", - "version": "6.5.1-beta", + "version": "6.5.3", "repository": { "type": "git", "url": "https://github.com/ngageoint/mage-server.git" diff --git a/web-app/src/app/event/event.service.ts b/web-app/src/app/event/event.service.ts index 4e4e626f7..9f30b846e 100644 --- a/web-app/src/app/event/event.service.ts +++ b/web-app/src/app/event/event.service.ts @@ -57,7 +57,7 @@ export class EventService { private locationService: LocationService, private observationService: ObservationService, private localStorageService: LocalStorageService - ) {} + ) { } init() { this.filterService.addListener(this); @@ -82,7 +82,19 @@ export class EventService { query(options?: any): Observable { options = options || {}; - return this.httpClient.get("/api/events/", options); + let params = new HttpParams(); + + for (const key of Object.keys(options)) { + if (options[key] !== undefined && options[key] !== null) { + params = params.set(key, String(options[key])); + } + } + + if (!options.limit && !options.page_size) { + params = params.set('limit', '100'); + } + + return this.httpClient.get("/api/events/", { params }); } addFeed(eventId: string, feed: any): Observable { @@ -541,7 +553,7 @@ export class EventService { map(res => res.items) ); } - + isUserInEvent(user, event): boolean { if (!event) return false; diff --git a/web-app/src/app/filter/filter.component.ts b/web-app/src/app/filter/filter.component.ts index fc58c720b..a5d30d66b 100644 --- a/web-app/src/app/filter/filter.component.ts +++ b/web-app/src/app/filter/filter.component.ts @@ -3,7 +3,7 @@ import { MatDialogRef } from "@angular/material/dialog"; import { FilterService } from "./filter.service"; import { EventService } from "../event/event.service"; import { FormControl } from "@angular/forms"; -import { Observable, firstValueFrom, map, startWith, take } from "rxjs"; +import { Observable, firstValueFrom, map, startWith, debounceTime, switchMap, of } from "rxjs"; import { COMMA, ENTER } from "@angular/cdk/keycodes"; import { MatAutocompleteSelectedEvent } from "@angular/material/autocomplete"; import { LocalStorageService } from "../http/local-storage.service"; @@ -61,7 +61,7 @@ export class FilterComponent implements OnInit { private eventService: EventService, private filterService: FilterService, private localStorageService: LocalStorageService - ) {} + ) { } async ngOnInit() { const event: Event = this.filterService.getEvent(); @@ -125,7 +125,7 @@ export class FilterComponent implements OnInit { return []; } } - + /** * Resets Filter When Event is Selected * @param {Event[]} events List of Events @@ -139,8 +139,15 @@ export class FilterComponent implements OnInit { this.filteredEvents = this.eventControl.valueChanges.pipe( startWith(""), - map((value) => (typeof value === "string" ? value : value.name)), - map((name) => (name ? this.filterEvent(name) : this.events.slice())) + debounceTime(300), + switchMap((value) => { + const searchTerm = typeof value === "string" ? value : value?.name || ""; + if (searchTerm && searchTerm.trim()) { + return this.eventService.query({ term: searchTerm.trim(), limit: 100 }); + } else { + return of(this.events); + } + }) ); this.filteredTeams = this.teamControl.valueChanges.pipe( @@ -237,14 +244,14 @@ export class FilterComponent implements OnInit { if (this.eventUsers.length > 0) this.userControl.enable({ emitEvent: false }); else this.userControl.disable({ emitEvent: false }); - + if (newEvent.forms.length > 0) this.formControl.enable({ emitEvent: false }); else this.formControl.disable({ emitEvent: false }); if (this.eventControl.value.teams.length > 0) this.teamControl.enable({ emitEvent: false }); else this.teamControl.disable({ emitEvent: false }); - if (this.events.length > 0) this.eventControl.enable({ emitEvent: false }); + if (this.events.length > 0) this.eventControl.enable({ emitEvent: false }); else this.eventControl.disable({ emitEvent: false }); this.eventService.query().subscribe(async (events: Event[]) => { @@ -259,7 +266,7 @@ export class FilterComponent implements OnInit { private filterEvent(name: string): Event[] { const filterValue = name.toLowerCase(); return this.events.filter( - (option) => option.name.toLowerCase().indexOf(filterValue) === 0 + (option) => option.name.toLowerCase().includes(filterValue) ); } diff --git a/web-app/src/app/home/home.component.html b/web-app/src/app/home/home.component.html index 4f786b594..744098596 100644 --- a/web-app/src/app/home/home.component.html +++ b/web-app/src/app/home/home.component.html @@ -5,7 +5,7 @@
- +