mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-11 11:52:08 +00:00
System metrics (#7700)
* add metric ui * Update statistic-metrics.js * handle metrics * optimize ui * update * optimize ui * update ui --------- Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
This commit is contained in:
parent
1134469495
commit
6b51e54596
@ -18,6 +18,7 @@ import StatisticStorage from './statistic/statistic-storage';
|
|||||||
import StatisticTraffic from './statistic/statistic-traffic';
|
import StatisticTraffic from './statistic/statistic-traffic';
|
||||||
import StatisticUsers from './statistic/statistic-users';
|
import StatisticUsers from './statistic/statistic-users';
|
||||||
import StatisticReport from './statistic/statistic-reports';
|
import StatisticReport from './statistic/statistic-reports';
|
||||||
|
import StatisticMetrics from './statistic/statistic-metrics';
|
||||||
|
|
||||||
import DesktopDevices from './devices/desktop-devices';
|
import DesktopDevices from './devices/desktop-devices';
|
||||||
import MobileDevices from './devices/mobile-devices';
|
import MobileDevices from './devices/mobile-devices';
|
||||||
@ -112,7 +113,7 @@ class SysAdmin extends React.Component {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
tab: 'statistic',
|
tab: 'statistic',
|
||||||
urlPartList: ['statistics/file', 'statistics/storage', 'statistics/user', 'statistics/traffic', 'statistics/reports']
|
urlPartList: ['statistics/file', 'statistics/storage', 'statistics/user', 'statistics/traffic', 'statistics/reports', 'statistics/metrics']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tab: 'users',
|
tab: 'users',
|
||||||
@ -222,6 +223,7 @@ class SysAdmin extends React.Component {
|
|||||||
<StatisticUsers path={siteRoot + 'sys/statistics/user'} {...commonProps} />
|
<StatisticUsers path={siteRoot + 'sys/statistics/user'} {...commonProps} />
|
||||||
<StatisticTraffic path={siteRoot + 'sys/statistics/traffic'} {...commonProps} />
|
<StatisticTraffic path={siteRoot + 'sys/statistics/traffic'} {...commonProps} />
|
||||||
<StatisticReport path={siteRoot + 'sys/statistics/reports'} {...commonProps} />
|
<StatisticReport path={siteRoot + 'sys/statistics/reports'} {...commonProps} />
|
||||||
|
<StatisticMetrics path={siteRoot + 'sys/statistics/metrics'} {...commonProps} />
|
||||||
<DesktopDevices path={siteRoot + 'sys/desktop-devices'} {...commonProps} />
|
<DesktopDevices path={siteRoot + 'sys/desktop-devices'} {...commonProps} />
|
||||||
<MobileDevices path={siteRoot + 'sys/mobile-devices'} {...commonProps} />
|
<MobileDevices path={siteRoot + 'sys/mobile-devices'} {...commonProps} />
|
||||||
<DeviceErrors path={siteRoot + 'sys/device-errors'} {...commonProps} />
|
<DeviceErrors path={siteRoot + 'sys/device-errors'} {...commonProps} />
|
||||||
|
300
frontend/src/pages/sys-admin/statistic/statistic-metrics.js
Normal file
300
frontend/src/pages/sys-admin/statistic/statistic-metrics.js
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { systemAdminAPI } from '../../../utils/system-admin-api';
|
||||||
|
import MainPanelTopbar from '../main-panel-topbar';
|
||||||
|
import StatisticNav from './statistic-nav';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import { Tooltip } from 'reactstrap';
|
||||||
|
|
||||||
|
class ComponentMetricsTable extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
hoveredRow: null,
|
||||||
|
tooltipOpen: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleTooltip = (id) => {
|
||||||
|
this.setState(prevState => ({
|
||||||
|
tooltipOpen: prevState.tooltipOpen === id ? null : id
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
handleRowHover = (id) => {
|
||||||
|
this.setState({ hoveredRow: id });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleRowLeave = () => {
|
||||||
|
this.setState({ hoveredRow: null });
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { componentName, metrics } = this.props;
|
||||||
|
const { hoveredRow, tooltipOpen } = this.state;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<tr className="component-header">
|
||||||
|
<td colSpan="4">
|
||||||
|
<div className="component-title">
|
||||||
|
<span>{componentName}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{metrics.map((metric) => (
|
||||||
|
metric.data_points.map((point, pointIndex) => {
|
||||||
|
const rowId = `${metric.name}-${point.labels.node}-${pointIndex}`;
|
||||||
|
return (
|
||||||
|
<tr key={rowId} className="metric-row">
|
||||||
|
<td
|
||||||
|
onMouseEnter={() => this.handleRowHover(rowId)}
|
||||||
|
onMouseLeave={this.handleRowLeave}
|
||||||
|
>
|
||||||
|
<div className="metric-info">
|
||||||
|
<div className="metric-name">
|
||||||
|
{metric.name.substring(metric.name.indexOf('_') + 1)}
|
||||||
|
{metric.help && (
|
||||||
|
<>
|
||||||
|
<i
|
||||||
|
className="sf3-font-help sf3-font"
|
||||||
|
id={rowId}
|
||||||
|
aria-hidden="true"
|
||||||
|
style={{
|
||||||
|
visibility: hoveredRow === rowId ? 'visible' : 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
</i>
|
||||||
|
<Tooltip
|
||||||
|
placement='right'
|
||||||
|
isOpen={tooltipOpen === rowId && hoveredRow === rowId}
|
||||||
|
toggle={() => this.toggleTooltip(rowId)}
|
||||||
|
target={rowId}
|
||||||
|
delay={{ show: 200, hide: 0 }}
|
||||||
|
fade={true}
|
||||||
|
className="metric-tooltip"
|
||||||
|
hideArrow={true}
|
||||||
|
>
|
||||||
|
{metric.help}
|
||||||
|
</Tooltip>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>{point.labels.node}</td>
|
||||||
|
<td className="metric-value">{point.value}</td>
|
||||||
|
<td>
|
||||||
|
<span className="collected-time">
|
||||||
|
{dayjs(point.labels.collected_at).format('YYYY-MM-DD HH:mm:ss')}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StatisticMetrics extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
metrics: [],
|
||||||
|
loading: true,
|
||||||
|
error: null,
|
||||||
|
groupedMetrics: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.getMetrics();
|
||||||
|
}
|
||||||
|
|
||||||
|
groupMetricsByComponent = (metrics) => {
|
||||||
|
const groups = {};
|
||||||
|
metrics.forEach(metric => {
|
||||||
|
if (metric.data_points && metric.data_points.length > 0) {
|
||||||
|
metric.data_points.forEach(point => {
|
||||||
|
const component = point.labels.component || 'Other';
|
||||||
|
if (!groups[component]) {
|
||||||
|
groups[component] = [];
|
||||||
|
}
|
||||||
|
const existingMetric = groups[component].find(m => m.name === metric.name);
|
||||||
|
if (existingMetric) {
|
||||||
|
existingMetric.data_points.push(point);
|
||||||
|
} else {
|
||||||
|
groups[component].push({
|
||||||
|
...metric,
|
||||||
|
data_points: [point]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return groups;
|
||||||
|
};
|
||||||
|
|
||||||
|
getMetrics = async () => {
|
||||||
|
this.setState({ loading: true });
|
||||||
|
try {
|
||||||
|
const res = await systemAdminAPI.sysAdminStatisticMetrics();
|
||||||
|
const groupedMetrics = this.groupMetricsByComponent(res.data.metrics);
|
||||||
|
this.setState({
|
||||||
|
metrics: res.data.metrics,
|
||||||
|
groupedMetrics,
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.setState({
|
||||||
|
error: 'Failed to get metric data',
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { groupedMetrics, loading, error } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MainPanelTopbar {...this.props} />
|
||||||
|
<div className="">
|
||||||
|
<StatisticNav currentItem="metricsStatistic" />
|
||||||
|
<div className="cur-metrics-content">
|
||||||
|
{loading ? (
|
||||||
|
<div className="loading-icon loading-tip"></div>
|
||||||
|
) : error ? (
|
||||||
|
<div className="error text-danger">{error}</div>
|
||||||
|
) : (
|
||||||
|
<div className="metrics-container">
|
||||||
|
<div className="card">
|
||||||
|
<div className="card-body">
|
||||||
|
<table className="table table-striped mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="40%">{gettext('Metrics')}</th>
|
||||||
|
<th width="20%">{gettext('Node')}</th>
|
||||||
|
<th width="15%">{gettext('Value')}</th>
|
||||||
|
<th width="25%">{gettext('Collected time')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{Object.entries(groupedMetrics).map(([component, metrics]) => (
|
||||||
|
<ComponentMetricsTable
|
||||||
|
key={component}
|
||||||
|
componentName={component}
|
||||||
|
metrics={metrics}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = `
|
||||||
|
<style>
|
||||||
|
.cur-metrics-content {
|
||||||
|
padding: 10px 16px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cur-metrics-content .card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-metrics-card .card-header {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 16px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-name {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metrics-container {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-tip {
|
||||||
|
margin: 100px auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-header {
|
||||||
|
background-color: #fafafa !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-header td {
|
||||||
|
padding-top: 24px !important;
|
||||||
|
padding-bottom: 2px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-title {
|
||||||
|
color: #212529;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-row td {
|
||||||
|
padding: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metrics-container .table {
|
||||||
|
margin-bottom: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metrics-container .table td {
|
||||||
|
vertical-align: middle;
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metrics-container .table th {
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf3-font-help {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin-left: 4px;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.head.insertAdjacentHTML('beforeend', style);
|
||||||
|
|
||||||
|
export default StatisticMetrics;
|
@ -17,6 +17,7 @@ class Nav extends React.Component {
|
|||||||
{ name: 'usersStatistic', urlPart: 'statistics/user', text: gettext('Users') },
|
{ name: 'usersStatistic', urlPart: 'statistics/user', text: gettext('Users') },
|
||||||
{ name: 'trafficStatistic', urlPart: 'statistics/traffic', text: gettext('Traffic') },
|
{ name: 'trafficStatistic', urlPart: 'statistics/traffic', text: gettext('Traffic') },
|
||||||
{ name: 'reportsStatistic', urlPart: 'statistics/reports', text: gettext('Reports') },
|
{ name: 'reportsStatistic', urlPart: 'statistics/reports', text: gettext('Reports') },
|
||||||
|
{ name: 'metricsStatistic', urlPart: 'statistics/metrics', text: gettext('Metrics') },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1236,6 +1236,11 @@ class SystemAdminAPI {
|
|||||||
return this.req.post(url, formData);
|
return this.req.post(url, formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sysAdminStatisticMetrics() {
|
||||||
|
const url = this.server + '/api/v2.1/admin/statistics/system-metrics/';
|
||||||
|
return this.req.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
sysAdminStatisticFiles(startTime, endTime, groupBy) {
|
sysAdminStatisticFiles(startTime, endTime, groupBy) {
|
||||||
const url = this.server + '/api/v2.1/admin/statistics/file-operations/';
|
const url = this.server + '/api/v2.1/admin/statistics/file-operations/';
|
||||||
let params = {
|
let params = {
|
||||||
|
@ -28,6 +28,7 @@ from seahub.base.templatetags.seahub_tags import email2nickname, \
|
|||||||
from seahub.api2.authentication import TokenAuthentication
|
from seahub.api2.authentication import TokenAuthentication
|
||||||
from seahub.api2.throttling import UserRateThrottle
|
from seahub.api2.throttling import UserRateThrottle
|
||||||
from seahub.api2.utils import api_error
|
from seahub.api2.utils import api_error
|
||||||
|
from seahub.api2.endpoints.utils import get_seafevents_metrics
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -474,3 +475,109 @@ class SystemUserStorageExcelView(APIView):
|
|||||||
wb.save(response)
|
wb.save(response)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def parse_prometheus_metrics(metrics_raw):
|
||||||
|
"""
|
||||||
|
Parse prometheus metrics and format metric names
|
||||||
|
"""
|
||||||
|
formatted_metrics_dict = {}
|
||||||
|
|
||||||
|
def ensure_metric_exists(raw_name):
|
||||||
|
"""
|
||||||
|
Ensure metric entry exists in formatted metrics dict
|
||||||
|
"""
|
||||||
|
if raw_name not in formatted_metrics_dict:
|
||||||
|
formatted_metrics_dict[raw_name] = {
|
||||||
|
'name': raw_name,
|
||||||
|
'help': '',
|
||||||
|
'type': '',
|
||||||
|
'data_points': []
|
||||||
|
}
|
||||||
|
return raw_name
|
||||||
|
|
||||||
|
def parse_labels(line):
|
||||||
|
"""
|
||||||
|
Parse labels from metric line
|
||||||
|
"""
|
||||||
|
labels = {}
|
||||||
|
if '{' in line and '}' in line:
|
||||||
|
labels_str = line[line.index('{')+1:line.index('}')]
|
||||||
|
for label in labels_str.split(','):
|
||||||
|
key, value = [part.strip() for part in label.split('=', 1)]
|
||||||
|
labels[key] = value.strip('"')
|
||||||
|
return labels
|
||||||
|
|
||||||
|
def parse_metric_line(line):
|
||||||
|
"""
|
||||||
|
Parse a single metric line
|
||||||
|
"""
|
||||||
|
if '{' in line:
|
||||||
|
name_part = line.split('{')[0]
|
||||||
|
value_part = line.split('}')[1].strip()
|
||||||
|
else:
|
||||||
|
name_part, value_part = line.split(' ', 1)
|
||||||
|
|
||||||
|
return (
|
||||||
|
name_part.strip(),
|
||||||
|
parse_labels(line),
|
||||||
|
float(value_part)
|
||||||
|
)
|
||||||
|
|
||||||
|
for line in metrics_raw.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line.startswith('# HELP'):
|
||||||
|
parts = line.split(' ', 3)
|
||||||
|
if len(parts) > 3:
|
||||||
|
metric_name, help_text = parts[2], parts[3]
|
||||||
|
ensure_metric_exists(metric_name)
|
||||||
|
formatted_metrics_dict[metric_name]['help'] = help_text
|
||||||
|
|
||||||
|
elif line.startswith('# TYPE'):
|
||||||
|
parts = line.split(' ')
|
||||||
|
if len(parts) > 3:
|
||||||
|
metric_name, metric_type = parts[2], parts[3]
|
||||||
|
ensure_metric_exists(metric_name)
|
||||||
|
formatted_metrics_dict[metric_name]['type'] = metric_type
|
||||||
|
|
||||||
|
elif not line.startswith('#'):
|
||||||
|
# handle metric data
|
||||||
|
parsed_data = parse_metric_line(line)
|
||||||
|
if parsed_data:
|
||||||
|
metric_name, labels, value = parsed_data
|
||||||
|
ensure_metric_exists(metric_name)
|
||||||
|
formatted_metrics_dict[metric_name]['data_points'].append({
|
||||||
|
'labels': labels,
|
||||||
|
'value': value
|
||||||
|
})
|
||||||
|
# check data
|
||||||
|
result = []
|
||||||
|
for metric in formatted_metrics_dict.values():
|
||||||
|
if metric['name'] and metric['type']:
|
||||||
|
result.append(metric)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
class SystemMetricsView(APIView):
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
permission_classes = (IsAdminUser,)
|
||||||
|
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
if not request.user.admin_permissions.can_view_statistic():
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = get_seafevents_metrics()
|
||||||
|
metrics_raw = res.content.decode('utf-8')
|
||||||
|
metrics_data = parse_prometheus_metrics(metrics_raw)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
error_msg = 'Internal Server Error'
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
|
return Response({ 'metrics': metrics_data })
|
||||||
|
@ -141,7 +141,7 @@ from seahub.api2.endpoints.admin.web_settings import AdminWebSettings
|
|||||||
from seahub.api2.endpoints.admin.statistics import (
|
from seahub.api2.endpoints.admin.statistics import (
|
||||||
FileOperationsView, TotalStorageView, ActiveUsersView, SystemTrafficView, \
|
FileOperationsView, TotalStorageView, ActiveUsersView, SystemTrafficView, \
|
||||||
SystemUserTrafficExcelView, SystemUserStorageExcelView, SystemUserTrafficView, \
|
SystemUserTrafficExcelView, SystemUserStorageExcelView, SystemUserTrafficView, \
|
||||||
SystemOrgTrafficView
|
SystemOrgTrafficView, SystemMetricsView
|
||||||
)
|
)
|
||||||
from seahub.api2.endpoints.admin.devices import AdminDevices
|
from seahub.api2.endpoints.admin.devices import AdminDevices
|
||||||
from seahub.api2.endpoints.admin.device_errors import AdminDeviceErrors
|
from seahub.api2.endpoints.admin.device_errors import AdminDeviceErrors
|
||||||
@ -627,6 +627,7 @@ urlpatterns = [
|
|||||||
re_path(r'^api/v2.1/admin/statistics/system-org-traffic/$', SystemOrgTrafficView.as_view(), name='api-v2.1-admin-statistics-system-org-traffic'),
|
re_path(r'^api/v2.1/admin/statistics/system-org-traffic/$', SystemOrgTrafficView.as_view(), name='api-v2.1-admin-statistics-system-org-traffic'),
|
||||||
re_path(r'^api/v2.1/admin/statistics/system-user-traffic/excel/$', SystemUserTrafficExcelView.as_view(), name='api-v2.1-admin-statistics-system-user-traffic-excel'),
|
re_path(r'^api/v2.1/admin/statistics/system-user-traffic/excel/$', SystemUserTrafficExcelView.as_view(), name='api-v2.1-admin-statistics-system-user-traffic-excel'),
|
||||||
re_path(r'^api/v2.1/admin/statistics/system-user-storage/excel/$', SystemUserStorageExcelView.as_view(), name='api-v2.1-admin-statistics-system-user-storage-excel'),
|
re_path(r'^api/v2.1/admin/statistics/system-user-storage/excel/$', SystemUserStorageExcelView.as_view(), name='api-v2.1-admin-statistics-system-user-storage-excel'),
|
||||||
|
re_path(r'^api/v2.1/admin/statistics/system-metrics/$', SystemMetricsView.as_view(), name='api-v2.1-admin-statistics-system-metrics'),
|
||||||
## admin::users
|
## admin::users
|
||||||
re_path(r'^api/v2.1/admin/users/$', AdminUsers.as_view(), name='api-v2.1-admin-users'),
|
re_path(r'^api/v2.1/admin/users/$', AdminUsers.as_view(), name='api-v2.1-admin-users'),
|
||||||
re_path(r'^api/v2.1/admin/ldap-users/$', AdminLDAPUsers.as_view(), name='api-v2.1-admin-ldap-users'),
|
re_path(r'^api/v2.1/admin/ldap-users/$', AdminLDAPUsers.as_view(), name='api-v2.1-admin-ldap-users'),
|
||||||
@ -841,6 +842,7 @@ urlpatterns = [
|
|||||||
path('sys/statistics/user/', sysadmin_react_fake_view, name="sys_statistics_user"),
|
path('sys/statistics/user/', sysadmin_react_fake_view, name="sys_statistics_user"),
|
||||||
path('sys/statistics/traffic/', sysadmin_react_fake_view, name="sys_statistics_traffic"),
|
path('sys/statistics/traffic/', sysadmin_react_fake_view, name="sys_statistics_traffic"),
|
||||||
path('sys/statistics/reports/', sysadmin_react_fake_view, name="sys_statistics_reports"),
|
path('sys/statistics/reports/', sysadmin_react_fake_view, name="sys_statistics_reports"),
|
||||||
|
path('sys/statistics/metrics/', sysadmin_react_fake_view, name="sys_statistics_metrics"),
|
||||||
path('sys/desktop-devices/', sysadmin_react_fake_view, name="sys_desktop_devices"),
|
path('sys/desktop-devices/', sysadmin_react_fake_view, name="sys_desktop_devices"),
|
||||||
path('sys/mobile-devices/', sysadmin_react_fake_view, name="sys_mobile_devices"),
|
path('sys/mobile-devices/', sysadmin_react_fake_view, name="sys_mobile_devices"),
|
||||||
path('sys/device-errors/', sysadmin_react_fake_view, name="sys_device_errors"),
|
path('sys/device-errors/', sysadmin_react_fake_view, name="sys_device_errors"),
|
||||||
|
Loading…
Reference in New Issue
Block a user