mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-25 13:44:05 -08:00
Adapt UI for Prometheus Agent (#9851)
* Adapt UI for Prometheus Agent UI is not my strongest skill, but I'd like to have something minimal for the initial release of the agent. Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu> * Address review comments Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu> * Add tests Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu> * Add tests, serve only current mode paths Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu> * Update js style, add agent test Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu>
This commit is contained in:
parent
0d20ee46fe
commit
c78fcd29ba
|
@ -15,10 +15,11 @@
|
|||
It will render a "Consoles" link in the navbar when it is non-empty.
|
||||
- PROMETHEUS_AGENT_MODE is replaced by a boolean indicating if Prometheus is running in agent mode.
|
||||
It true, it will disable querying capacities in the UI and generally adapt the UI to the agent mode.
|
||||
It has to be represented as a string, because booleans can be mangled to !1 in production builds.
|
||||
-->
|
||||
<script>
|
||||
const GLOBAL_CONSOLES_LINK='CONSOLES_LINK_PLACEHOLDER';
|
||||
const GLOBAL_PROMETHEUS_AGENT_MODE=PROMETHEUS_AGENT_MODE_PLACEHOLDER;
|
||||
const GLOBAL_AGENT_MODE='AGENT_MODE_PLACEHOLDER';
|
||||
</script>
|
||||
|
||||
<!--
|
||||
|
|
|
@ -5,6 +5,7 @@ import Navigation from './Navbar';
|
|||
import { Container } from 'reactstrap';
|
||||
import { Route } from 'react-router-dom';
|
||||
import {
|
||||
AgentPage,
|
||||
AlertsPage,
|
||||
ConfigPage,
|
||||
FlagsPage,
|
||||
|
@ -24,6 +25,7 @@ describe('App', () => {
|
|||
});
|
||||
it('routes', () => {
|
||||
[
|
||||
AgentPage,
|
||||
AlertsPage,
|
||||
ConfigPage,
|
||||
FlagsPage,
|
||||
|
@ -37,7 +39,7 @@ describe('App', () => {
|
|||
const c = app.find(component);
|
||||
expect(c).toHaveLength(1);
|
||||
});
|
||||
expect(app.find(Route)).toHaveLength(9);
|
||||
expect(app.find(Route)).toHaveLength(10);
|
||||
expect(app.find(Container)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Container } from 'reactstrap';
|
|||
|
||||
import { BrowserRouter as Router, Redirect, Switch, Route } from 'react-router-dom';
|
||||
import {
|
||||
AgentPage,
|
||||
AlertsPage,
|
||||
ConfigPage,
|
||||
FlagsPage,
|
||||
|
@ -22,14 +23,16 @@ import useMedia from './hooks/useMedia';
|
|||
|
||||
interface AppProps {
|
||||
consolesLink: string | null;
|
||||
agentMode: boolean;
|
||||
}
|
||||
|
||||
const App: FC<AppProps> = ({ consolesLink }) => {
|
||||
const App: FC<AppProps> = ({ consolesLink, agentMode }) => {
|
||||
// This dynamically/generically determines the pathPrefix by stripping the first known
|
||||
// endpoint suffix from the window location path. It works out of the box for both direct
|
||||
// hosting and reverse proxy deployments with no additional configurations required.
|
||||
let basePath = window.location.pathname;
|
||||
const paths = [
|
||||
'/agent',
|
||||
'/graph',
|
||||
'/alerts',
|
||||
'/status',
|
||||
|
@ -70,14 +73,17 @@ const App: FC<AppProps> = ({ consolesLink }) => {
|
|||
<Theme />
|
||||
<PathPrefixContext.Provider value={basePath}>
|
||||
<Router basename={basePath}>
|
||||
<Navigation consolesLink={consolesLink} />
|
||||
<Navigation consolesLink={consolesLink} agentMode={agentMode} />
|
||||
<Container fluid style={{ paddingTop: 70 }}>
|
||||
<Switch>
|
||||
<Redirect exact from="/" to={`graph`} />
|
||||
<Redirect exact from="/" to={agentMode ? '/agent' : '/graph'} />
|
||||
{/*
|
||||
NOTE: Any route added here needs to also be added to the list of
|
||||
React-handled router paths ("reactRouterPaths") in /web/web.go.
|
||||
*/}
|
||||
<Route path="/agent">
|
||||
<AgentPage />
|
||||
</Route>
|
||||
<Route path="/graph">
|
||||
<PanelListPage />
|
||||
</Route>
|
||||
|
|
|
@ -17,17 +17,18 @@ import { ThemeToggle } from './Theme';
|
|||
|
||||
interface NavbarProps {
|
||||
consolesLink: string | null;
|
||||
agentMode: boolean;
|
||||
}
|
||||
|
||||
const Navigation: FC<NavbarProps> = ({ consolesLink }) => {
|
||||
const Navigation: FC<NavbarProps> = ({ consolesLink, agentMode }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const toggle = () => setIsOpen(!isOpen);
|
||||
const pathPrefix = usePathPrefix();
|
||||
return (
|
||||
<Navbar className="mb-3" dark color="dark" expand="md" fixed="top">
|
||||
<NavbarToggler onClick={toggle} className="mr-2" />
|
||||
<Link className="pt-0 navbar-brand" to="/graph">
|
||||
Prometheus
|
||||
<Link className="pt-0 navbar-brand" to={agentMode ? '/agent' : '/graph'}>
|
||||
Prometheus{agentMode && ' Agent'}
|
||||
</Link>
|
||||
<Collapse isOpen={isOpen} navbar style={{ justifyContent: 'space-between' }}>
|
||||
<Nav className="ml-0" navbar>
|
||||
|
@ -36,16 +37,20 @@ const Navigation: FC<NavbarProps> = ({ consolesLink }) => {
|
|||
<NavLink href={consolesLink}>Consoles</NavLink>
|
||||
</NavItem>
|
||||
)}
|
||||
<NavItem>
|
||||
<NavLink tag={Link} to="/alerts">
|
||||
Alerts
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink tag={Link} to="/graph">
|
||||
Graph
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
{!agentMode && (
|
||||
<>
|
||||
<NavItem>
|
||||
<NavLink tag={Link} to="/alerts">
|
||||
Alerts
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink tag={Link} to="/graph">
|
||||
Graph
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
</>
|
||||
)}
|
||||
<UncontrolledDropdown nav inNavbar>
|
||||
<DropdownToggle nav caret>
|
||||
Status
|
||||
|
@ -54,18 +59,22 @@ const Navigation: FC<NavbarProps> = ({ consolesLink }) => {
|
|||
<DropdownItem tag={Link} to="/status">
|
||||
Runtime & Build Information
|
||||
</DropdownItem>
|
||||
<DropdownItem tag={Link} to="/tsdb-status">
|
||||
TSDB Status
|
||||
</DropdownItem>
|
||||
{!agentMode && (
|
||||
<DropdownItem tag={Link} to="/tsdb-status">
|
||||
TSDB Status
|
||||
</DropdownItem>
|
||||
)}
|
||||
<DropdownItem tag={Link} to="/flags">
|
||||
Command-Line Flags
|
||||
</DropdownItem>
|
||||
<DropdownItem tag={Link} to="/config">
|
||||
Configuration
|
||||
</DropdownItem>
|
||||
<DropdownItem tag={Link} to="/rules">
|
||||
Rules
|
||||
</DropdownItem>
|
||||
{!agentMode && (
|
||||
<DropdownItem tag={Link} to="/rules">
|
||||
Rules
|
||||
</DropdownItem>
|
||||
)}
|
||||
<DropdownItem tag={Link} to="/targets">
|
||||
Targets
|
||||
</DropdownItem>
|
||||
|
@ -77,9 +86,11 @@ const Navigation: FC<NavbarProps> = ({ consolesLink }) => {
|
|||
<NavItem>
|
||||
<NavLink href="https://prometheus.io/docs/prometheus/latest/getting_started/">Help</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink href={`${pathPrefix}/classic/graph${window.location.search}`}>Classic UI</NavLink>
|
||||
</NavItem>
|
||||
{!agentMode && (
|
||||
<NavItem>
|
||||
<NavLink href={`${pathPrefix}/classic/graph${window.location.search}`}>Classic UI</NavLink>
|
||||
</NavItem>
|
||||
)}
|
||||
</Nav>
|
||||
</Collapse>
|
||||
<ThemeToggle />
|
||||
|
|
|
@ -10,8 +10,10 @@ import { isPresent } from './utils';
|
|||
|
||||
// Declared/defined in public/index.html, value replaced by Prometheus when serving bundle.
|
||||
declare const GLOBAL_CONSOLES_LINK: string;
|
||||
declare const GLOBAL_AGENT_MODE: string;
|
||||
|
||||
let consolesLink: string | null = GLOBAL_CONSOLES_LINK;
|
||||
const agentMode: string | null = GLOBAL_AGENT_MODE;
|
||||
|
||||
if (
|
||||
GLOBAL_CONSOLES_LINK === 'CONSOLES_LINK_PLACEHOLDER' ||
|
||||
|
@ -21,4 +23,4 @@ if (
|
|||
consolesLink = null;
|
||||
}
|
||||
|
||||
ReactDOM.render(<App consolesLink={consolesLink} />, document.getElementById('root'));
|
||||
ReactDOM.render(<App consolesLink={consolesLink} agentMode={agentMode === 'true'} />, document.getElementById('root'));
|
||||
|
|
16
web/ui/react-app/src/pages/agent/Agent.tsx
Normal file
16
web/ui/react-app/src/pages/agent/Agent.tsx
Normal file
|
@ -0,0 +1,16 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
const Agent: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<h2>Prometheus Agent</h2>
|
||||
<p>
|
||||
This Prometheus instance is running in <strong>agent mode</strong>. In this mode, Prometheus is only used to scrape
|
||||
discovered targets and forward them to remote write endpoints.
|
||||
</p>
|
||||
<p>Some features are not available in this mode, such as querying and alerting.</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Agent;
|
|
@ -1,3 +1,4 @@
|
|||
import Agent from './agent/Agent';
|
||||
import Alerts from './alerts/Alerts';
|
||||
import Config from './config/Config';
|
||||
import Flags from './flags/Flags';
|
||||
|
@ -9,6 +10,7 @@ import PanelList from './graph/PanelList';
|
|||
import TSDBStatus from './tsdbStatus/TSDBStatus';
|
||||
import { withStartingIndicator } from '../components/withStartingIndicator';
|
||||
|
||||
const AgentPage = withStartingIndicator(Agent);
|
||||
const AlertsPage = withStartingIndicator(Alerts);
|
||||
const ConfigPage = withStartingIndicator(Config);
|
||||
const FlagsPage = withStartingIndicator(Flags);
|
||||
|
@ -21,6 +23,7 @@ const PanelListPage = withStartingIndicator(PanelList);
|
|||
|
||||
// prettier-ignore
|
||||
export {
|
||||
AgentPage,
|
||||
AlertsPage,
|
||||
ConfigPage,
|
||||
FlagsPage,
|
||||
|
|
36
web/web.go
36
web/web.go
|
@ -71,18 +71,27 @@ import (
|
|||
|
||||
// Paths that are handled by the React / Reach router that should all be served the main React app's index.html.
|
||||
var reactRouterPaths = []string{
|
||||
"/alerts",
|
||||
"/config",
|
||||
"/flags",
|
||||
"/graph",
|
||||
"/rules",
|
||||
"/service-discovery",
|
||||
"/status",
|
||||
"/targets",
|
||||
"/tsdb-status",
|
||||
"/starting",
|
||||
}
|
||||
|
||||
// Paths that are handled by the React router when the Agent mode is set.
|
||||
var reactRouterAgentPaths = []string{
|
||||
"/agent",
|
||||
}
|
||||
|
||||
// Paths that are handled by the React router when the Agent mode is not set.
|
||||
var reactRouterServerPaths = []string{
|
||||
"/alerts",
|
||||
"/graph",
|
||||
"/rules",
|
||||
"/tsdb-status",
|
||||
}
|
||||
|
||||
// withStackTrace logs the stack trace in case the request panics. The function
|
||||
// will re-raise the error which will then be handled by the net/http package.
|
||||
// It is needed because the go-kit log package doesn't manage properly the
|
||||
|
@ -346,10 +355,15 @@ func New(logger log.Logger, o *Options) *Handler {
|
|||
router = router.WithPrefix(o.RoutePrefix)
|
||||
}
|
||||
|
||||
homePage := "/graph"
|
||||
if o.IsAgent {
|
||||
homePage = "/agent"
|
||||
}
|
||||
|
||||
readyf := h.testReady
|
||||
|
||||
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, path.Join(o.ExternalURL.Path, "/graph"), http.StatusFound)
|
||||
http.Redirect(w, r, path.Join(o.ExternalURL.Path, homePage), http.StatusFound)
|
||||
})
|
||||
router.Get("/classic/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, path.Join(o.ExternalURL.Path, "/classic/graph"), http.StatusFound)
|
||||
|
@ -409,7 +423,7 @@ func New(logger log.Logger, o *Options) *Handler {
|
|||
}
|
||||
replacedIdx := bytes.ReplaceAll(idx, []byte("CONSOLES_LINK_PLACEHOLDER"), []byte(h.consolesPath()))
|
||||
replacedIdx = bytes.ReplaceAll(replacedIdx, []byte("TITLE_PLACEHOLDER"), []byte(h.options.PageTitle))
|
||||
replacedIdx = bytes.ReplaceAll(replacedIdx, []byte("PROMETHEUS_AGENT_MODE_PLACEHOLDER"), []byte(strconv.FormatBool(h.options.IsAgent)))
|
||||
replacedIdx = bytes.ReplaceAll(replacedIdx, []byte("AGENT_MODE_PLACEHOLDER"), []byte(strconv.FormatBool(h.options.IsAgent)))
|
||||
w.Write(replacedIdx)
|
||||
}
|
||||
|
||||
|
@ -418,6 +432,16 @@ func New(logger log.Logger, o *Options) *Handler {
|
|||
router.Get(p, serveReactApp)
|
||||
}
|
||||
|
||||
if h.options.IsAgent {
|
||||
for _, p := range reactRouterAgentPaths {
|
||||
router.Get(p, serveReactApp)
|
||||
}
|
||||
} else {
|
||||
for _, p := range reactRouterServerPaths {
|
||||
router.Get(p, serveReactApp)
|
||||
}
|
||||
}
|
||||
|
||||
// The favicon and manifest are bundled as part of the React app, but we want to serve
|
||||
// them on the root.
|
||||
for _, p := range []string{"/favicon.ico", "/manifest.json"} {
|
||||
|
|
|
@ -585,6 +585,8 @@ func TestAgentAPIEndPoints(t *testing.T) {
|
|||
"/query",
|
||||
"/query_range",
|
||||
"/query_exemplars",
|
||||
"/graph",
|
||||
"/rules",
|
||||
} {
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", baseURL+u, nil)
|
||||
|
@ -595,6 +597,7 @@ func TestAgentAPIEndPoints(t *testing.T) {
|
|||
|
||||
// Test for available endpoints in the Agent mode.
|
||||
for _, u := range []string{
|
||||
"/agent",
|
||||
"/targets",
|
||||
"/status",
|
||||
} {
|
||||
|
|
Loading…
Reference in a new issue