Skip to content

Commit

Permalink
feat: add dashboard page (#28)
Browse files Browse the repository at this point in the history
* feat: add dashboard and its list page

* fix: signed in required

* fix: rollback local config

* fix: rollback local config

* fix: package resolved in yarn

* chore: define a JS file for each card, put them into a folder

* chore: avoid global CSS

* chore: replace npmmirror with yarnpkg

* chore: sort imports and replace strconv.Atoi with util.ParseInt

* chore: sort imports
  • Loading branch information
love98ooo authored May 28, 2024
1 parent e460132 commit 1dca747
Show file tree
Hide file tree
Showing 12 changed files with 708 additions and 0 deletions.
124 changes: 124 additions & 0 deletions controllers/metric.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright 2024 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// 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 controllers

import (
"errors"
"time"

"github.com/casbin/caswaf/object"
"github.com/casbin/caswaf/util"
)

func (c *ApiController) GetMetricsOverTime() {
if c.RequireSignedIn() {
return
}
rangeType := c.Input().Get("rangeType")
count := util.ParseInt(c.Input().Get("count"))
granularity := c.Input().Get("granularity")
timeType := granularity2TimeType(granularity)
startTime := time.Now().Add(time.Duration(-count) * rangeType2Duration(rangeType))
metrics, err := object.GetMetricsOverTime(startTime, timeType)
if err != nil {
c.ResponseError(err.Error())
return
}
var total int64
for _, metric := range *metrics {
total += metric.Count
}
c.ResponseOk(metrics, total)
}

func granularity2TimeType(rangeType string) string {
switch rangeType {
case "hour":
return "hour"
case "day":
return "day"
case "week":
return "day"
case "month":
return "month"
case "year":
return "month"
default:
return "month"
}
}

func (c *ApiController) GetMetrics() {
if c.RequireSignedIn() {
return
}

dtoType := c.Input().Get("type")
dataType, err := type2DataType(dtoType)
if err != nil {
c.ResponseError(err.Error())
return
}
rangeType := c.Input().Get("rangeType")
count := util.ParseInt(c.Input().Get("count"))
top, err := util.ParseIntWithError(c.Input().Get("top"))
// if top is not set or invalid, set it to the maximum value
if err != nil || top <= 0 {
top = int(^uint(0) >> 1)
}
startTime := time.Now().Add(time.Duration(-count) * rangeType2Duration(rangeType))
metrics, err := object.GetMetrics(dataType, startTime, top)
if err != nil {
c.ResponseError(err.Error())
return
}
var total int64
for _, metric := range *metrics {
total += metric.Count
}
c.ResponseOk(metrics, total)
}

func rangeType2Duration(rangeType string) time.Duration {
switch rangeType {
case "hour":
return time.Hour
case "day":
return 24 * time.Hour
case "week":
return 7 * 24 * time.Hour
case "month":
return 30 * 24 * time.Hour
case "year":
return 365 * 24 * time.Hour
default:
return time.Hour
}
}

func type2DataType(dataType string) (string, error) {
switch dataType {
case "site":
return "host", nil
case "path":
return "path", nil
case "ip":
return "client_ip", nil
case "userAgent":
return "user_agent", nil
default:
return "", errors.New("invalid data type")
}
}
48 changes: 48 additions & 0 deletions object/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package object

import (
"strconv"
"time"

"github.com/xorm-io/core"
)
Expand Down Expand Up @@ -95,3 +96,50 @@ func getRecord(owner string, id int64) (*Record, error) {
}
return nil, nil
}

type DataCount struct {
Data string `json:"data"`
Count int64 `json:"count"`
}

func GetMetrics(dataType string, startAt time.Time, top int) (*[]DataCount, error) {
var dataCounts []DataCount
err := ormer.Engine.Table("record").
Where("UNIX_TIMESTAMP(created_time) > ?", startAt.Unix()).
Select(dataType + " as data, COUNT(*) as count").
GroupBy("data").
Desc("count").
Limit(top).
Find(&dataCounts)
if err != nil {
return nil, err
}
return &dataCounts, nil
}

func GetMetricsOverTime(startAt time.Time, timeType string) (*[]DataCount, error) {
var dataCounts []DataCount
createdTime := "DATE_FORMAT(created_time, '" + timeType2Format(timeType) + "')"
err := ormer.Engine.Table("record").
Where("UNIX_TIMESTAMP(created_time) > ?", startAt.Unix()).
GroupBy(createdTime).
Select(createdTime + " as data, COUNT(*) as count").
Asc("data").
Find(&dataCounts)
if err != nil {
return nil, err
}
return &dataCounts, nil
}

func timeType2Format(timeType string) string {
switch timeType {
case "hour":
return "%Y-%m-%d %H"
case "day":
return "%Y-%m-%d"
case "month":
return "%Y-%m"
}
return "%Y-%m-%d %H"
}
2 changes: 2 additions & 0 deletions routers/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,6 @@ func initAPI() {
beego.Router("/api/update-record", &controllers.ApiController{}, "POST:UpdateRecord")
beego.Router("/api/add-record", &controllers.ApiController{}, "POST:AddRecord")

beego.Router("/api/get-metrics", &controllers.ApiController{}, "GET:GetMetrics")
beego.Router("/api/get-metrics-over-time", &controllers.ApiController{}, "GET:GetMetricsOverTime")
}
2 changes: 2 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"casdoor-js-sdk": "^0.2.7",
"copy-to-clipboard": "^3.3.3",
"craco-less": "2.0.0",
"echarts": "^5.5.0",
"echarts-for-react": "^3.0.2",
"eslint-plugin-unused-imports": "^2.0.0",
"file-saver": "^2.0.5",
"i18next": "^19.8.9",
Expand Down
10 changes: 10 additions & 0 deletions web/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import SigninPage from "./SigninPage";
import RecordListPage from "./RecordListPage";
import RecordEditPage from "./RecordEditPage";
import i18next from "i18next";
import DashboardPage from "./DashboardPage";
// import SelectLanguageBox from "./SelectLanguageBox";

const {Header, Footer} = Layout;

Expand Down Expand Up @@ -241,6 +243,13 @@ class App extends Component {
</Menu.Item>
);

res.push(
<Menu.Item key="/dashboard">
<Link to="/dashboard">
{i18next.t("general:Dashboard")}
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/sites">
<Link to="/sites">
Expand Down Expand Up @@ -323,6 +332,7 @@ class App extends Component {

<Route exact path="/records" render={(props) => this.renderSigninIfNotSignedIn(<RecordListPage account={this.state.account} {...props} />)} />
<Route exact path="/records/:owner/:id" render={(props) => this.renderSigninIfNotSignedIn(<RecordEditPage account={this.state.account} {...props} />)} />
<Route exact path="/dashboard" render={(props) => this.renderSigninIfNotSignedIn(<DashboardPage account={this.state.account} {...props} />)} />
</Switch>
</div>
);
Expand Down
Loading

0 comments on commit 1dca747

Please sign in to comment.