Skip to content
Open
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
0c56710
NMS-19851: Configurable system-wide dashboard (milestones 1-2 + persi…
joseanes Jun 2, 2026
27c1081
NMS-19851: Add first real dashboard panels (notifications, status ove…
joseanes Jun 2, 2026
de07c97
NMS-19851: Status Overview outages donut + News Feed panel
joseanes Jun 2, 2026
b50ad4f
NMS-19851: Add Pending Situations and Service Outages panels
joseanes Jun 2, 2026
521df8d
NMS-19851: Add Regional Status map panel
joseanes Jun 3, 2026
cfe412a
NMS-19851: Wider middle column in default dashboard layout
joseanes Jun 3, 2026
15b6e13
NMS-19851: Add Availability (24h) panel
joseanes Jun 3, 2026
9737c90
NMS-19851: Add Business Services and Applications (with pending alarm…
joseanes Jun 3, 2026
b3d7003
NMS-19851: Search widgets, original-layout order, toolbar tooltips + …
joseanes Jun 3, 2026
0765ca1
NMS-19851: Dashboard panel polish (map, donuts, panel height, double …
joseanes Jun 3, 2026
c43981f
NMS-19851: Fix fullscreen, footer overlap, overlay font, donut resize
joseanes Jun 3, 2026
599e987
NMS-19851: Per-panel options dialog, fixed/auto height, Notes + HTML …
joseanes Jun 3, 2026
277be70
NMS-19851: Fix panel overlap from auto-height (manual vertical compac…
joseanes Jun 3, 2026
661f17d
NMS-19851: Fix fixed-panel height chain, internal scroll vs footer, H…
joseanes Jun 3, 2026
df9f5f3
NMS-19851: Auto-height panels shrink to content (min-h=1, no reserved…
joseanes Jun 3, 2026
3781b28
NMS-19851: Add Top N panel (rank entities by a chosen KPI over the ti…
joseanes Jun 3, 2026
75bb9e8
NMS-19851: TopN measurements use a normal-resolution step (fix NaN)
joseanes Jun 3, 2026
2d92007
NMS-19851: Fix auto-height measurement + dark-mode panel theming
joseanes Jun 3, 2026
5ec81b0
NMS-19851: Tighten panel padding + compact columns on mount (less whi…
joseanes Jun 3, 2026
adbd09e
NMS-19851: Real compaction (fresh refs), severity-shading option, leg…
joseanes Jun 3, 2026
1f83f06
NMS-19851: Auto-height actually applies (always reassign) + service-t…
joseanes Jun 3, 2026
849d146
NMS-19851: Pixel-perfect panel heights, panel/category deep-links, de…
joseanes Jun 3, 2026
33c3c46
NMS-19851: BSM title deep-link + readable donut legend in dark mode
joseanes Jun 3, 2026
216d521
NMS-19851: Metric Chart panel — line chart of one entity/metric over …
joseanes Jun 11, 2026
3cd06b4
NMS-19851: Hide Sample Panel from the Add Panel picker
joseanes Jun 11, 2026
7eebbaf
NMS-19851: 'Reset to default' in edit mode — restore the factory layout
joseanes Jun 11, 2026
bee063e
NMS-19851: Status Overview donut slices deep-link in context (legacy …
joseanes 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