[release] v0.12.0-unstable2

This commit is contained in:
Yann Stepienik 2023-10-26 15:40:07 +01:00
parent af30f256f1
commit 6ba22dc4cb
9 changed files with 326 additions and 58 deletions

View file

@ -4,6 +4,7 @@ import * as _config from './config';
import * as _docker from './docker';
import * as _market from './market';
import * as _constellation from './constellation';
import * as _metrics from './metrics';
import * as authDemo from './authentication.demo';
import * as usersDemo from './users.demo';
@ -214,6 +215,7 @@ let config = _config;
let docker = _docker;
let market = _market;
let constellation = _constellation;
let metrics = _metrics;
if(isDemo) {
auth = authDemo;
@ -242,5 +244,6 @@ export {
isOnline,
checkHost,
getDNS,
metrics,
uploadBackground
};

View file

@ -0,0 +1,14 @@
import wrap from './wrap';
function get() {
return wrap(fetch('/cosmos/api/metrics', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
}))
}
export {
get,
};

View file

@ -144,4 +144,8 @@
.pulsing {
animation: pulsing 2s ease-in-out infinite;
}
.force-light > * {
color: black !important;
}

View file

@ -0,0 +1,221 @@
import { useEffect, useState } from 'react';
// material-ui
import {
Avatar,
AvatarGroup,
Box,
Button,
Grid,
List,
ListItemAvatar,
ListItemButton,
ListItemSecondaryAction,
ListItemText,
MenuItem,
Stack,
TextField,
Typography,
Alert
} from '@mui/material';
import MainCard from '../../../components/MainCard';
// material-ui
import { useTheme } from '@mui/material/styles';
// third-party
import ReactApexChart from 'react-apexcharts';
function formatDate(now, time) {
// use as UTC
// now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are 0-based
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
return time ? `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` : `${year}-${month}-${day}`;
}
function toUTC(date, time) {
let now = new Date(date);
now.setMinutes(now.getMinutes() + now.getTimezoneOffset());
return formatDate(now, time);
}
const PlotComponent = ({ data, defaultSlot = 'day' }) => {
const [slot, setSlot] = useState(defaultSlot);
const theme = useTheme();
// chart options
const areaChartOptions = {
chart: {
height: 450,
type: 'area',
toolbar: {
show: false
}
},
dataLabels: {
enabled: false
},
stroke: {
curve: 'smooth',
width: 2
},
grid: {
strokeDashArray: 0
}
};
let hourlyDates = [];
for(let i = 0; i < 48; i++) {
let now = new Date();
now.setHours(now.getHours() - i);
now.setMinutes(0);
now.setSeconds(0);
// get as YYYY-MM-DD HH:MM:SS
hourlyDates.unshift(formatDate(now, true));
}
let dailyDates = [];
for(let i = 0; i < 30; i++) {
let now = new Date();
now.setDate(now.getDate() - i);
dailyDates.unshift(formatDate(now));
}
const { primary, secondary } = theme.palette.text;
const line = theme.palette.divider;
const [options, setOptions] = useState(areaChartOptions);
useEffect(() => {
setOptions((prevState) => ({
...prevState,
colors: [theme.palette.primary.main, theme.palette.secondary.main],
xaxis: {
categories:
slot === 'hourly'
? hourlyDates.map((date) => date.split(' ')[1])
: dailyDates,
labels: {
style: {
fontSize: '11px',
}
},
axisBorder: {
show: true,
color: line
},
tickAmount: slot === 'hourly' ? hourlyDates.length : dailyDates.length,
},
yaxis: [{
labels: {
style: {
colors: [secondary]
}
},
title: {
text: data[0].Label,
}
},
{
opposite: true,
labels: {
style: {
colors: [secondary]
}
},
title: {
text: data[1].Label,
}
}
],
grid: {
borderColor: line
},
tooltip: {
theme: 'light'
}
}));
}, [primary, secondary, line, theme, slot]);
let dataSeries = [];
data.forEach((serie) => {
dataSeries.push({
name: serie.Label,
dataDaily: dailyDates.map((date) => {
let k = "day_" + toUTC(date);
if (k in serie.ValuesAggl) {
return serie.ValuesAggl[k].Value;
} else {
console.log(k)
return 0;
}
}),
dataHourly: hourlyDates.map((date) => {
let k = "hour_" + toUTC(date, true);
if (k in serie.ValuesAggl) {
return serie.ValuesAggl[k].Value;
} else {
return 0;
}
}),
});
});
const [series, setSeries] = useState(dataSeries.map((serie) => {
return {
name: serie.name,
data: slot === 'hourly' ? serie.dataHourly : serie.dataDaily
}
}));
useEffect(() => {
setSeries(dataSeries.map((serie) => {
return {
name: serie.name,
data: slot === 'hourly' ? serie.dataHourly : serie.dataDaily
}
}));
}, [slot, data]);
return <Grid item xs={12} md={7} lg={8}>
<Grid container alignItems="center" justifyContent="space-between">
<Grid item>
<Typography variant="h5">Server Resources</Typography>
</Grid>
<Grid item>
<Stack direction="row" alignItems="center" spacing={0}>
<Button
size="small"
onClick={() => setSlot('hourly')}
color={slot === 'hourly' ? 'primary' : 'secondary'}
variant={slot === 'hourly' ? 'outlined' : 'text'}
>
Hourly
</Button>
<Button
size="small"
onClick={() => setSlot('daily')}
color={slot === 'daily' ? 'primary' : 'secondary'}
variant={slot === 'daily' ? 'outlined' : 'text'}
>
Daily
</Button>
</Stack>
</Grid>
</Grid>
<MainCard content={false} sx={{ mt: 1.5 }}>
<Box sx={{ pt: 1, pr: 2 }} className="force-light">
<ReactApexChart options={options} series={series} type="area" height={450} />;
</Box>
</MainCard>
</Grid>
}
export default PlotComponent;

View file

@ -38,6 +38,7 @@ import IsLoggedIn from '../../isLoggedIn';
import * as API from '../../api';
import AnimateButton from '../../components/@extended/AnimateButton';
import PlotComponent from './components/plot';
// avatar style
const avatarSX = {
@ -79,8 +80,20 @@ const DashboardDefault = () => {
const [slot, setSlot] = useState('week');
const [coStatus, setCoStatus] = useState(null);
const [metrics, setMetrics] = useState(null);
const [isCreatingDB, setIsCreatingDB] = useState(false);
const refreshMetrics = () => {
API.metrics.get().then((res) => {
let finalMetrics = {};
res.data.forEach((metric) => {
finalMetrics[metric.Key] = metric;
});
setMetrics(finalMetrics);
setTimeout(refreshMetrics, 10000);
});
};
const refreshStatus = () => {
API.getStatus().then((res) => {
setCoStatus(res.data);
@ -89,15 +102,9 @@ const DashboardDefault = () => {
useEffect(() => {
refreshStatus();
refreshMetrics();
}, []);
const setupDB = () => {
setIsCreatingDB(true);
API.docker.newDB().then((res) => {
refreshStatus();
});
}
return (
<>
<IsLoggedIn />
@ -145,7 +152,7 @@ const DashboardDefault = () => {
<Alert severity="info">Dashboard implementation currently in progress! If you want to voice your opinion on where Cosmos is going, please join us on Discord!</Alert>
</Stack>
</div>
<div style={{filter: 'blur(10px)', marginTop: '30px', pointerEvents: 'none'}}>
{metrics && <div style={{marginTop: '30px'}}>
<Grid container rowSpacing={4.5} columnSpacing={2.75}>
{/* row 1 */}
<Grid item xs={12} sx={{ mb: -2.25 }}>
@ -167,38 +174,9 @@ const DashboardDefault = () => {
<Grid item md={8} sx={{ display: { sm: 'none', md: 'block', lg: 'none' } }} />
{/* row 2 */}
<Grid item xs={12} md={7} lg={8}>
<Grid container alignItems="center" justifyContent="space-between">
<Grid item>
<Typography variant="h5">Unique Visitor</Typography>
</Grid>
<Grid item>
<Stack direction="row" alignItems="center" spacing={0}>
<Button
size="small"
onClick={() => setSlot('month')}
color={slot === 'month' ? 'primary' : 'secondary'}
variant={slot === 'month' ? 'outlined' : 'text'}
>
Month
</Button>
<Button
size="small"
onClick={() => setSlot('week')}
color={slot === 'week' ? 'primary' : 'secondary'}
variant={slot === 'week' ? 'outlined' : 'text'}
>
Week
</Button>
</Stack>
</Grid>
</Grid>
<MainCard content={false} sx={{ mt: 1.5 }}>
<Box sx={{ pt: 1, pr: 2 }}>
<IncomeAreaChart slot={slot} />
</Box>
</MainCard>
</Grid>
<PlotComponent data={[metrics["cosmos.system.cpu.0"], metrics["cosmos.system.ram"]]}/>
<Grid item xs={12} md={5} lg={4}>
<Grid container alignItems="center" justifyContent="space-between">
<Grid item>
@ -413,7 +391,7 @@ const DashboardDefault = () => {
</MainCard>
</Grid>
</Grid>
</div>
</div>}
</>
);
};

View file

@ -1,6 +1,6 @@
{
"name": "cosmos-server",
"version": "0.12.0-unstable",
"version": "0.12.0-unstable2",
"description": "",
"main": "test-server.js",
"bugs": {

View file

@ -10,6 +10,7 @@ import (
"github.com/azukaar/cosmos-server/src/authorizationserver"
"github.com/azukaar/cosmos-server/src/market"
"github.com/azukaar/cosmos-server/src/constellation"
"github.com/azukaar/cosmos-server/src/metrics"
"github.com/gorilla/mux"
"strconv"
"time"
@ -339,6 +340,8 @@ func InitServer() *mux.Router {
srapi.HandleFunc("/api/constellation/logs", constellation.API_GetLogs)
srapi.HandleFunc("/api/constellation/block", constellation.DeviceBlock)
srapi.HandleFunc("/api/metrics", metrics.API_GetMetrics)
if(!config.HTTPConfig.AcceptAllInsecureHostname) {
srapi.Use(utils.EnsureHostname)
}

View file

@ -74,8 +74,8 @@ func AggloMetrics() {
}
// if hourly pool does not exist, create it
if _, ok := metric.ValuesAggl["hour_" + hourlyPool.String()]; !ok {
metric.ValuesAggl["hour_" + hourlyPool.String()] = DataDefDBEntry{
if _, ok := metric.ValuesAggl["hour_" + hourlyPool.UTC().Format("2006-01-02 15:04:05")]; !ok {
metric.ValuesAggl["hour_" + hourlyPool.UTC().Format("2006-01-02 15:04:05")] = DataDefDBEntry{
Date: hourlyPool,
Value: 0,
Processed: false,
@ -86,8 +86,8 @@ func AggloMetrics() {
}
// if daily pool does not exist, create it
if _, ok := metric.ValuesAggl["day_" + dailyPool.String()]; !ok {
metric.ValuesAggl["day_" + dailyPool.String()] = DataDefDBEntry{
if _, ok := metric.ValuesAggl["day_" + dailyPool.UTC().Format("2006-01-02")]; !ok {
metric.ValuesAggl["day_" + dailyPool.UTC().Format("2006-01-02")] = DataDefDBEntry{
Date: dailyPool,
Value: 0,
Processed: false,
@ -100,33 +100,33 @@ func AggloMetrics() {
for valInd, value := range values {
// if not processed
if !value.Processed {
valueHourlyPool := ModuloTime(value.Date, time.Hour)
valueDailyPool := ModuloTime(value.Date, 24 * time.Hour)
valueHourlyPool := ModuloTime(value.Date, time.Hour).UTC().Format("2006-01-02 15:04:05")
valueDailyPool := ModuloTime(value.Date, 24 * time.Hour).UTC().Format("2006-01-02")
if _, ok := metric.ValuesAggl["hour_" + valueHourlyPool.String()]; ok {
currentPool := metric.ValuesAggl["hour_" + valueHourlyPool.String()]
if _, ok := metric.ValuesAggl["hour_" + valueHourlyPool]; ok {
currentPool := metric.ValuesAggl["hour_" + valueHourlyPool]
currentPool.Value = MergeMetric(metric.AggloType, currentPool.Value, value.Value, currentPool.AvgIndex)
if metric.AggloType == "avg" {
currentPool.AvgIndex++
}
metric.ValuesAggl["hour_" + valueHourlyPool.String()] = currentPool
metric.ValuesAggl["hour_" + valueHourlyPool] = currentPool
} else {
utils.Warn("Metrics: Agglomeration - Pool not found : " + "hour_" + valueHourlyPool.String())
utils.Warn("Metrics: Agglomeration - Pool not found : " + "hour_" + valueHourlyPool)
}
if _, ok := metric.ValuesAggl["day_" + valueDailyPool.String()]; ok {
currentPool := metric.ValuesAggl["day_" + valueDailyPool.String()]
if _, ok := metric.ValuesAggl["day_" + valueDailyPool]; ok {
currentPool := metric.ValuesAggl["day_" + valueDailyPool]
currentPool.Value = MergeMetric(metric.AggloType, currentPool.Value, value.Value, currentPool.AvgIndex)
if metric.AggloType == "avg" {
currentPool.AvgIndex++
}
metric.ValuesAggl["day_" + valueDailyPool.String()] = currentPool
metric.ValuesAggl["day_" + valueDailyPool] = currentPool
} else {
utils.Warn("Metrics: Agglomeration - Pool not found: " + "day_" + valueDailyPool.String())
utils.Warn("Metrics: Agglomeration - Pool not found: " + "day_" + valueDailyPool)
}
values[valInd].Processed = true
@ -171,7 +171,7 @@ func AggloMetrics() {
func InitAggl() {
go func() {
s := gocron.NewScheduler()
s.Every(1).Hour().At("00.00").From(gocron.NextTick()).Do(AggloMetrics)
s.Every(1).Hour().From(gocron.NextTick()).Do(AggloMetrics)
// s.Every(3).Minute().From(gocron.NextTick()).Do(AggloMetrics)
s.Start()

45
src/metrics/api.go Normal file
View file

@ -0,0 +1,45 @@
package metrics
import (
"net/http"
"encoding/json"
"github.com/azukaar/cosmos-server/src/utils"
)
func API_GetMetrics(w http.ResponseWriter, req *http.Request) {
if utils.AdminOnly(w, req) != nil {
return
}
if(req.Method == "GET") {
c, errCo := utils.GetCollection(utils.GetRootAppId(), "metrics")
if errCo != nil {
utils.Error("Metrics - Database Connect", errCo)
return
}
// get all metrics from database
var metrics []DataDefDB
cursor, err := c.Find(nil, map[string]interface{}{})
if err != nil {
utils.Error("Metrics: Error fetching metrics", err)
return
}
defer cursor.Close(nil)
if err = cursor.All(nil, &metrics); err != nil {
utils.Error("Metrics: Error decoding metrics", err)
return
}
json.NewEncoder(w).Encode(map[string]interface{}{
"status": "OK",
"data": metrics,
})
} else {
utils.Error("SettingGet: Method not allowed" + req.Method, nil)
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
return
}
}