Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"screenshot": true,
"fullscreen": true,
"help": true,
"showAttributionsOnMap": true,
"logourl": "",
"helpurl": "https://nasa-ammos.github.io/MMGIS/",
"aboutModalContent": "Welcome to the **MMGIS Reference Mission**.\n\nThis mission demonstrates the full capabilities of the Multi-Mission Geographic Information System."
Expand Down Expand Up @@ -3731,6 +3732,48 @@
},
"description": "EMIT L2B Methane Enhancement Data 60 m V002 - STAC COG collection. Requires WITH_STAC=true and WITH_TITILER_PGSTAC=true."
},
{
"name": "USGS SF DEM \u2014 Custom Color Table (GDAL)",
"uuid": "fa3b9734-e223-40e1-a253-7bc9e63968a3",
"type": "tile",
"visibility": false,
"sourceType": "COG",
"url": "Data/DEMs/USGS_13_n38w123_20250826_SFHill.tif",
"tileformat": "tms",
"controlled": false,
"initialOpacity": 1,
"minZoom": 0,
"maxNativeZoom": 30,
"maxZoom": 30,
"demparser": "rgba",
"throughTileServer": true,
"cogResampling": "nearest",
"cogBands": [
1
],
"cogExpressionEditable": false,
"cogTransform": true,
"cogMin": 0,
"cogMax": 800,
"cogUnits": "m",
"cogColormapJson": "[[[-5,5],[70,130,180,255]],[[5,50],[90,170,120,255]],[[50,120],[150,200,110,255]],[[120,220],[220,220,120,255]],[[220,350],[210,170,90,255]],[[350,500],[180,120,70,255]],[[500,700],[150,90,60,255]],[[700,900],[240,240,240,255]]]",
"style": {
"brightness": 1,
"contrast": 1,
"saturation": 1,
"blend": "none"
},
"time": {
"enabled": false,
"type": "requery",
"compositeTile": false,
"refreshIntervalEnabled": false
},
"variables": {
"legendOrientation": "vertical"
},
"description": "Demonstrates the Custom Color Table (GDAL) feature: the SF DEM colored by an interval color table (cogColormapJson) passed to TiTiler's colormap parameter, producing discrete elevation bands (blue sea level \u2192 green \u2192 tan \u2192 white peaks) with an auto-generated discrete legend and no rescale. Served as a direct COG via TiTiler \u2014 requires WITH_TITILER=true only (no STAC stack)."
},
{
"name": "USGS SF DEM \u2014 Elevation",
"uuid": "e57468ef-b6f9-473f-973a-59d9dacfe016",
Expand Down
2 changes: 1 addition & 1 deletion configure/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "configure",
"version": "5.4.1-20260916",
"version": "5.4.2-20260922",
"homepage": "./configure/build",
"private": true,
"dependencies": {
Expand Down
26 changes: 25 additions & 1 deletion configure/src/core/Maker.js
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,22 @@ const getComponent = (
const ewfVal = getIn(configuration, com.enableWhenField.field, com.enableWhenField.default || "");
disabled = disabled || ewfVal !== com.enableWhenField.value;
}
if (com.disableWhen) {
// Disables the component when ALL conditions match. Each condition
// references a sibling field: {field, notEmpty: true} or {field, equals: <value>}
const conditions = Array.isArray(com.disableWhen)
? com.disableWhen
: [com.disableWhen];
const allMet = conditions.every((cond) => {
const condVal = getIn(directConf, cond.field, null);
if (cond.notEmpty === true)
return condVal != null && String(condVal).trim() !== "";
if (Object.prototype.hasOwnProperty.call(cond, "equals"))
return condVal === cond.equals;
return false;
});
disabled = disabled || allMet;
}
const isRequired = isFieldRequired(com, layer, configuration);
const fieldValue = value != null ? value : configured("");
const hasError = isRequired && (fieldValue === "" || fieldValue == null);
Expand Down Expand Up @@ -1629,7 +1645,15 @@ const getComponent = (
</Typography>
</>
) : (
<Tooltip title={com.description || ""} placement="top" arrow>
<Tooltip
title={
disabled
? `${com.description || ""}\n\nNote: ${disabledMessage}`.trim()
: com.description || ""
}
placement="top"
arrow
>
{inner}
</Tooltip>
)}
Expand Down
3 changes: 2 additions & 1 deletion configure/src/core/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ export const isNumeric = (value) => {
};

export const isUrlAbsolute = (url) => {
const r = new RegExp("^(?:[a-z]+:)?//", "i");
// RFC 3986 scheme (letters, digits, + - .) so s3://, gs://, etc. count
const r = new RegExp("^(?:[a-z][a-z0-9+.-]*:)?//", "i");
return r.test(url);
};

Expand Down
12 changes: 12 additions & 0 deletions configure/src/metaconfigs/tab-ui-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,18 @@
}
]
},
{
"components": [
{
"field": "look.showAttributionsOnMap",
"name": "Show Attributions on Map",
"description": "Always show a collapsible dropdown of the visible layers' attributions on the map (top-right, next to the home button). Attributions are always listed in the About modal regardless of this setting.",
"type": "checkbox",
"width": 4,
"defaultChecked": false
}
]
},
{
"name": "Default Tool",
"components": [
Expand Down
4 changes: 4 additions & 0 deletions docs/pages/Configure/Tabs/Look/Look_Tab.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,7 @@ A URL to a new logo if the default MMGIS is unwanted. The logo should be reasona
#### Help URL

The URL to take users to in they click the help icon in the bottom left. Help above should be checked on.

#### Show Attributions on Map

If checked, a collapsible dropdown listing the attributions of all currently visible layers is always shown on the map, to the left of the home/zoom controls. It is hidden entirely when no visible layer has an attribution. Attributions are always listed in the About modal regardless of this setting.
1 change: 1 addition & 0 deletions docs/pages/Database/DrawTool/DrawTool.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ The `user_files` table stores an entry for each file a user created via the Draw
- `template`: A JSON defining rules that each feature's metadata must comply to. Templates can be set on files at creation or through an existing file's information modal.
- `publicity_type`: If the file is public, how public? Read-only, list out other users who can draw and edit in it, all users can draw and edit in it.
- `public_editors`: If the file is public and the publicity type is "list-editors", this is the list of usernames who can draw and edit in this file.
- `mission`: The name of the mission the file was created in. Files are only listed in the Draw Tool of their mission. `NULL` for files created before mission scoping existed; whether those legacy files are listed is controlled per mission by the Draw Tool's `showNullMissionFiles` variable (default `true`). Lead files (`is_master`) are shown in every mission unless the Draw Tool's `showMasterFiles` variable is `false`. Both variables are read server-side from the mission's saved configuration, so clients cannot override them. The file's mission is shown in its info modal; leads can move a file to another existing mission from there. Renaming a mission renames its files' `mission`; deleting a mission sets its files' `mission` back to `NULL`.

### file_histories

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mmgis",
"version": "5.4.1-20260916",
"version": "5.4.2-20260922",
"description": "A web-based mapping and localization solution for science operation on planetary missions.",
"homepage": "build",
"repository": {
Expand Down
23 changes: 18 additions & 5 deletions plugins/core/backend/Config/routes/configs.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const logger = require("../../../../../API/logger");
const Config = require("../models/config");
const config_template = require("../../../../../API/templates/config_template");
const userModel = require("../../Users/models/user");
const Userfiles = require("../../Draw/models/userfiles").Userfiles;
const User = userModel.User;
const { UserDefaults } = require("../../Users/models/userdefaults");
const missionTemplates = require("../../Utils/missionTemplates");
Expand Down Expand Up @@ -1418,10 +1419,17 @@ if (fullAccess)
return null;
});

// Draw files are scoped by mission name too.
const userfilesUpdate = Userfiles.update(
{ mission: newName },
{ where: { mission: missionName }, transaction: t }
);

return Promise.all([
...configUpdates,
permissionUpdate,
defaultsUpdate,
userfilesUpdate,
]);
})
.then(() => {
Expand Down Expand Up @@ -1528,11 +1536,16 @@ if (fullAccess)
return;
}

Config.destroy({
where: {
mission: missionName,
},
})
sequelize
.transaction((t) =>
Promise.all([
Config.destroy({ where: { mission: missionName }, transaction: t }),
Userfiles.update(
{ mission: null },
{ where: { mission: missionName }, transaction: t }
),
Comment on lines +1543 to +1546

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Deleted mission drawings reappear everywhere

Deleting a mission sets its drawings' mission to null. getfiles includes null-mission drawings by default, so they appear in every remaining mission.

Learn more

Draw files are retained after mission deletion, but clearing their mission changes their meaning. getfiles treats null as a legacy, unscoped file and includes it whenever the mission has not explicitly disabled showNullMissionFiles. A normal deletion therefore broadens the file's display scope to all remaining missions.

Example: A drawing made in mission A has mission: 'A'. Delete A while missions B and C use default Draw settings; that drawing now appears in both B and C.

Recommended fix: Preserve a non-null tombstone mission association for retained files or explicitly archive/delete associated drawings when destroying a mission. Keep null reserved for genuinely legacy unscoped drawings.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +1543 to +1546

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟥 Deleting a mission exposes its drawings elsewhere

Destroying a mission changes its drawings' mission to null. Other missions include null-mission drawings by default, exposing formerly scoped public files.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

])
)
.then((mission) => {
logger(
"info",
Expand Down
27 changes: 27 additions & 0 deletions plugins/core/backend/Draw/models/userfiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ const attributes = {
unique: false,
allowNull: true,
},
mission: {
type: Sequelize.STRING,
unique: false,
allowNull: true,
defaultValue: null,
},
};

const options = {
Expand Down Expand Up @@ -201,6 +207,27 @@ const up = async () => {
);
return null;
});

// mission column
for (const table of ["user_files", "user_files_tests"]) {
await sequelize
.query(
`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS mission varchar(255) NULL;`
)
.then(() => {
return null;
})
.catch((err) => {
logger(
"error",
`Failed to adding ${table}.mission column. DB tables may be out of sync!`,
"user_files",
null,
err
);
return null;
});
}
};

// export User model for use in other files.
Expand Down
4 changes: 2 additions & 2 deletions plugins/core/backend/Draw/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ let setup = {
//Once the server starts
onceStarted: (s) => {},
//Once all tables sync
onceSynced: (s) => {
onceSynced: async (s) => {
if (typeof file_histories.up === "function") {
file_histories.up();
}
if (typeof ufiles.up === "function") {
ufiles.up();
await ufiles.up();
Comment on lines +41 to +46

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Draw requests race the mission migration

On upgrade, onceSynced starts the mission-column migration without blocking requests. setups.synced discards its promise, so early Draw requests fail before the column exists.

Learn more

The Draw model now queries mission on normal file reads and writes. setups.synced invokes lifecycle hooks without awaiting their return values, while server startup opens the HTTP listener independently of the Sequelize sync/migration chain. Thus await ufiles.up() only orders statements inside the Draw hook; it does not prevent HTTP requests from seeing the old schema.

Example: Restart an upgraded installation with an existing user_files table lacking mission. A user opens Draw immediately; getfiles queries the new model before ALTER TABLE finishes and receives a database error.

Recommended fix: Await all onceSynced promises and complete migrations before accepting file requests. Ensure migration failures block readiness rather than being silently logged and ignored.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

}
routeFiles.makeMasterFiles([
"roi",
Expand Down
Loading
Loading