Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
32989cb
NMS-19851: Configurable system-wide dashboard (milestones 1-2 + persi…
joseanesONMS Jun 2, 2026
ae53494
NMS-19851: Add first real dashboard panels (notifications, status ove…
joseanesONMS Jun 2, 2026
1b295a3
NMS-19851: Status Overview outages donut + News Feed panel
joseanesONMS Jun 2, 2026
9248f35
NMS-19851: Add Pending Situations and Service Outages panels
joseanesONMS Jun 2, 2026
3110b4b
NMS-19851: Add Regional Status map panel
joseanesONMS Jun 3, 2026
5099b1d
NMS-19851: Wider middle column in default dashboard layout
joseanesONMS Jun 3, 2026
f1ec789
NMS-19851: Add Availability (24h) panel
joseanesONMS Jun 3, 2026
ce031da
NMS-19851: Add Business Services and Applications (with pending alarm…
joseanesONMS Jun 3, 2026
aaf9c93
NMS-19851: Search widgets, original-layout order, toolbar tooltips + …
joseanesONMS Jun 3, 2026
4263de1
NMS-19851: Dashboard panel polish (map, donuts, panel height, double …
joseanesONMS Jun 3, 2026
fd21bbe
NMS-19851: Fix fullscreen, footer overlap, overlay font, donut resize
joseanesONMS Jun 3, 2026
0a16e9e
NMS-19851: Per-panel options dialog, fixed/auto height, Notes + HTML …
joseanesONMS Jun 3, 2026
d618d94
NMS-19851: Fix panel overlap from auto-height (manual vertical compac…
joseanesONMS Jun 3, 2026
7402835
NMS-19851: Fix fixed-panel height chain, internal scroll vs footer, H…
joseanesONMS Jun 3, 2026
ec43475
NMS-19851: Auto-height panels shrink to content (min-h=1, no reserved…
joseanesONMS Jun 3, 2026
d9dc29d
NMS-19851: Add Top N panel (rank entities by a chosen KPI over the ti…
joseanesONMS Jun 3, 2026
78a0520
NMS-19851: TopN measurements use a normal-resolution step (fix NaN)
joseanesONMS Jun 3, 2026
baf08a7
NMS-19851: Fix auto-height measurement + dark-mode panel theming
joseanesONMS Jun 3, 2026
0d69eb4
NMS-19851: Tighten panel padding + compact columns on mount (less whi…
joseanesONMS Jun 3, 2026
3765408
NMS-19851: Real compaction (fresh refs), severity-shading option, leg…
joseanesONMS Jun 3, 2026
83140aa
NMS-19851: Auto-height actually applies (always reassign) + service-t…
joseanesONMS Jun 3, 2026
f16b44f
NMS-19851: Pixel-perfect panel heights, panel/category deep-links, de…
joseanesONMS Jun 3, 2026
e07f093
NMS-19851: BSM title deep-link + readable donut legend in dark mode
joseanesONMS Jun 3, 2026
d4ac2d5
NMS-19851: Metric Chart panel — line chart of one entity/metric over …
joseanesONMS Jun 11, 2026
fe94443
NMS-19851: Hide Sample Panel from the Add Panel picker
joseanesONMS Jun 11, 2026
7b1c65b
NMS-19851: 'Reset to default' in edit mode — restore the factory layout
joseanesONMS Jun 11, 2026
955b686
NMS-19851: Status Overview donut slices deep-link in context (legacy …
joseanesONMS Jun 11, 2026
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
@@ -0,0 +1,116 @@
/*
* Licensed to The OpenNMS Group, Inc (TOG) under one or more
* contributor license agreements. See the LICENSE.md file
* distributed with this work for additional information
* regarding copyright ownership.
*
* TOG licenses this file to You under the GNU Affero General
* Public License Version 3 (the "License") or (at your option)
* any later version. You may not use this file except in
* compliance with the License. You may obtain a copy of the
* License at:
*
* https://www.gnu.org/licenses/agpl-3.0.txt
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the
* License.
*/
package org.opennms.web.rest.v2;

import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import org.codehaus.jackson.map.ObjectMapper;
import org.opennms.features.distributed.kvstore.api.JsonStore;
import org.opennms.netmgt.dao.api.ServiceTypeDao;
import org.opennms.netmgt.model.OnmsServiceType;
import org.opennms.web.rest.v2.api.DashboardRestApi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
* Implementation of {@link DashboardRestApi}. Persists the single system-wide
* dashboard layout JSON document in the {@link JsonStore} (the same key-value
* store used by e.g. ResourceRestService), keyed by a fixed context/key.
*/
@Service
public class DashboardRestService implements DashboardRestApi {
private static final Logger LOG = LoggerFactory.getLogger(DashboardRestService.class);

// One document for the whole system. Future per-user / named dashboards would
// vary the key while keeping the same store.
private static final String CONTEXT = "dashboard";
private static final String KEY = "system";

@Autowired
private JsonStore jsonStore;

@Autowired
private ServiceTypeDao serviceTypeDao;

private final ObjectMapper objectMapper = new ObjectMapper();

@Override
public Response getSystemLayout() {
final Optional<String> stored = jsonStore.get(KEY, CONTEXT);
if (stored.isEmpty()) {
return Response.status(Status.NOT_FOUND).build();
}
try {
final Map<?, ?> layout = objectMapper.readValue(stored.get(), Map.class);
return Response.ok(layout).build();
} catch (final Exception e) {
LOG.error("Failed to parse stored system dashboard layout", e);
return Response.serverError().build();
}
}

@Override
public Response updateSystemLayout(final Map<String, Object> layout) {
if (layout == null) {
return Response.status(Status.BAD_REQUEST).build();
}
try {
final String json = objectMapper.writeValueAsString(layout);
jsonStore.put(KEY, json, CONTEXT);
return Response.noContent().build();
} catch (final Exception e) {
LOG.error("Failed to store system dashboard layout", e);
return Response.serverError().build();
}
}

@Override
@Transactional(readOnly = true)
public Response getServiceTypes() {
try {
final List<Map<String, Object>> types = serviceTypeDao.findAll().stream()
.sorted(Comparator.comparing(OnmsServiceType::getName, String.CASE_INSENSITIVE_ORDER))
.map(t -> {
final Map<String, Object> m = new LinkedHashMap<>();
m.put("id", t.getId());
m.put("name", t.getName());
return m;
})
.collect(Collectors.toList());
return Response.ok(types).build();
} catch (final Exception e) {
LOG.error("Failed to list service types", e);
return Response.serverError().build();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Licensed to The OpenNMS Group, Inc (TOG) under one or more
* contributor license agreements. See the LICENSE.md file
* distributed with this work for additional information
* regarding copyright ownership.
*
* TOG licenses this file to You under the GNU Affero General
* Public License Version 3 (the "License") or (at your option)
* any later version. You may not use this file except in
* compliance with the License. You may obtain a copy of the
* License at:
*
* https://www.gnu.org/licenses/agpl-3.0.txt
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the
* License.
*/
package org.opennms.web.rest.v2.api;

import java.util.Map;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;

/**
* REST API for the configurable system-wide dashboard layout (NMS-19851).
*
* A single JSON layout document is stored for the whole system. The document is
* opaque to the backend (the UI owns its shape); it is persisted verbatim.
*/
@Path("dashboard")
@Tag(name = "Dashboard", description = "System-wide dashboard layout API V2")
public interface DashboardRestApi {

@GET
@Path("system")
@Produces(MediaType.APPLICATION_JSON)
@Operation(
summary = "Get the system-wide dashboard layout.",
description = "Returns the stored system-wide dashboard layout document, or 404 if none has been saved yet.",
operationId = "getSystemDashboardLayout"
)
Response getSystemLayout();

@PUT
@Path("system")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Operation(
summary = "Save the system-wide dashboard layout.",
description = "Replaces the stored system-wide dashboard layout document.",
operationId = "updateSystemDashboardLayout"
)
Response updateSystemLayout(Map<String, Object> layout);

@GET
@Path("service-types")
@Produces(MediaType.APPLICATION_JSON)
@Operation(
summary = "List monitored service types (id + name).",
description = "Used by the dashboard Quick Search 'Providing service' control.",
operationId = "getDashboardServiceTypes"
)
Response getServiceTypes();
}
2 changes: 1 addition & 1 deletion opennms-webapp/src/main/webapp/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@
<init-param>
<description>Sets the header value.</description>
<param-name>value</param-name>
<param-value>accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()</param-value>
<param-value>accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(self), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()</param-value>
</init-param>
</filter>

Expand Down
3 changes: 3 additions & 0 deletions opennms-webapp/src/main/webapp/includes/quicksearch-box.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,8 @@
</div>
</div>
</form>
<div class="form-group" style="margin-top:0.75rem;">
<a class="btn btn-light btn-block" style="border:1px solid #ccc;" href="ui/#/dashboard">Try the new Dashboard (beta) &raquo;</a>
</div>
</div>
</div>
3 changes: 2 additions & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@
"@featherds/toggle-button": "^0.12.43",
"@featherds/tooltip": "0.12.43",
"@featherds/utils": "0.12.43",
"@primevue/themes": "^4.5.4",
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-regular-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/vue-fontawesome": "^3.0.6",
"@primevue/themes": "^4.5.4",
"@vueuse/core": "^9.13.0",
"ace-builds": "^1.32.6",
"axios": "^1.15.0",
Expand All @@ -81,6 +81,7 @@
"date-fns-tz": "^3.2.0",
"dotenv": "^17.2.3",
"fast-xml-parser": "^5.5.11",
"grid-layout-plus": "^1.1.1",
"ip-regex": "^5.0.0",
"is-ip": "^5.0.1",
"is-valid-domain": "^0.1.6",
Expand Down
Loading
Loading