feat: add UI to view logs

Signed-off-by: killer <1533063601@qq.com>
This commit is contained in:
killer 2021-07-07 14:59:03 +08:00 committed by Yang Luo
parent 6ae8e537b9
commit 21b36bbb47
14 changed files with 1090 additions and 33 deletions

View File

@ -181,6 +181,12 @@ func (c *ApiController) Login() {
} else {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
resp = c.HandleLoggedIn(application, user, &form)
record := util.Records(c.Ctx)
record.Organization = application.Organization
record.Username = user.Name
object.AddRecord(record)
}
} else if form.Provider != "" {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
@ -252,6 +258,12 @@ func (c *ApiController) Login() {
//}
resp = c.HandleLoggedIn(application, user, &form)
record := util.Records(c.Ctx)
record.Organization = application.Organization
record.Username = user.Name
object.AddRecord(record)
} else {
// Sign up via OAuth
if !application.EnableSignUp {
@ -294,6 +306,12 @@ func (c *ApiController) Login() {
object.LinkUserAccount(user, provider.Type, userInfo.Id)
resp = c.HandleLoggedIn(application, user, &form)
record := util.Records(c.Ctx)
record.Organization = application.Organization
record.Username = user.Name
object.AddRecord(record)
}
//resp = &Response{Status: "ok", Msg: "", Data: res}
} else { // form.Method != "signup"

46
controllers/record.go Normal file
View File

@ -0,0 +1,46 @@
// Copyright 2021 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 (
"encoding/json"
"github.com/casdoor/casdoor/object"
)
// @Title GetRecords
// @Description get all records
// @Success 200 {array} object.Records The Response object
// @router /get-records [get]
func (c *ApiController) GetRecords() {
c.Data["json"] = object.GetRecords()
c.ServeJSON()
}
// @Title GetRecordsByFilter
// @Description get records by filter
// @Param body body object.Records true "filter Record message"
// @Success 200 {array} object.Records The Response object
// @router /get-records-filter [post]
func (c *ApiController) GetRecordsByFilter() {
var record object.Records
err := json.Unmarshal(c.Ctx.Input.RequestBody, &record)
if err != nil {
panic(err)
}
c.Data["json"] = object.GetRecordsByField(&record)
c.ServeJSON()
}

View File

@ -48,6 +48,7 @@ func main() {
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoLoginFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
beego.BConfig.WebConfig.Session.SessionProvider = "file"

View File

@ -133,4 +133,8 @@ func (a *Adapter) createTable() {
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Records))
if err != nil {
panic(err)
}
}

65
object/record.go Normal file
View File

@ -0,0 +1,65 @@
// Copyright 2021 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 object
import (
"github.com/casdoor/casdoor/util"
)
type Records struct {
Id int `xorm:"int notnull pk autoincr" json:"id"`
Record util.Record `xorm:"extends"`
}
func AddRecord(record *util.Record) bool {
records := new(Records)
records.Record = *record
affected, err := adapter.Engine.Insert(records)
if err != nil {
panic(err)
}
return affected != 0
}
func GetRecordCount() int {
count, err := adapter.Engine.Count(&Records{})
if err != nil {
panic(err)
}
return int(count)
}
func GetRecords() []*Records {
records := []*Records{}
err := adapter.Engine.Desc("id").Find(&records)
if err != nil {
panic(err)
}
return records
}
func GetRecordsByField(record *Records) []*Records {
records := []*Records{}
err := adapter.Engine.Find(&records, record)
if err != nil {
panic(err)
}
return records
}

70
routers/record.go Normal file
View File

@ -0,0 +1,70 @@
// Copyright 2021 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 routers
import (
"strings"
"github.com/astaxie/beego/context"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
func getUser(ctx *context.Context) (username string) {
defer func() {
if r := recover(); r != nil {
username = getUserByClientIdSecret(ctx)
}
}()
username = ctx.Input.Session("username").(string)
if username == "" {
username = getUserByClientIdSecret(ctx)
}
return
}
func getUserByClientIdSecret(ctx *context.Context) string {
requestUri := ctx.Request.RequestURI
clientId := parseQuery(requestUri, "clientId")
clientSecret := parseQuery(requestUri, "clientSecret")
if len(clientId) == 0 || len(clientSecret) == 0 {
return ""
}
app := object.GetApplicationByClientId(clientId)
if app == nil || app.ClientSecret != clientSecret {
return ""
}
return app.Organization+"/"+app.Name
}
func RecordMessage(ctx *context.Context) {
if ctx.Request.URL.Path != "/api/login" {
user := getUser(ctx)
userinfo := strings.Split(user,"/")
if user == "" {
userinfo = append(userinfo,"")
}
record := util.Records(ctx)
record.Organization = userinfo[0]
record.Username = userinfo[1]
object.AddRecord(record)
}
}

View File

@ -84,4 +84,8 @@ func initAPI() {
beego.Router("/api/add-token", &controllers.ApiController{}, "POST:AddToken")
beego.Router("/api/delete-token", &controllers.ApiController{}, "POST:DeleteToken")
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
beego.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords")
beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")
}

View File

@ -362,6 +362,65 @@
}
}
},
"/api/get-default-application": {
"get": {
"tags": [
"api"
],
"description": "get the detail of the default application",
"operationId": "ApiController.GetDefaultApplication",
"parameters": [
{
"in": "query",
"name": "owner",
"description": "The owner of the application.",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.Application"
}
}
}
}
},
"/api/get-email-and-phone": {
"post": {
"tags": [
"api"
],
"description": "get email and phone by username",
"operationId": "ApiController.GetEmailAndPhone",
"parameters": [
{
"in": "formData",
"name": "username",
"description": "The username of the user",
"required": true,
"type": "string"
},
{
"in": "formData",
"name": "organization",
"description": "The organization of the user",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/get-global-users": {
"get": {
"tags": [
@ -492,6 +551,57 @@
}
}
},
"/api/get-records": {
"get": {
"tags": [
"api"
],
"description": "get all records",
"operationId": "ApiController.GetRecords",
"responses": {
"200": {
"description": "The Response object",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Records"
}
}
}
}
}
},
"/api/get-records-filter": {
"post": {
"tags": [
"api"
],
"description": "get records by filter",
"operationId": "ApiController.GetRecordsByFilter",
"parameters": [
{
"in": "body",
"name": "body",
"description": "filter Record message",
"required": true,
"schema": {
"$ref": "#/definitions/object.Records"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Records"
}
}
}
}
}
},
"/api/get-token": {
"get": {
"tags": [
@ -700,18 +810,65 @@
}
}
},
"/api/register": {
"/api/set-password": {
"post": {
"tags": [
"api"
],
"description": "register a new user",
"operationId": "ApiController.Register",
"description": "set password",
"operationId": "ApiController.SetPassword",
"parameters": [
{
"in": "formData",
"name": "userOwner",
"description": "The owner of the user",
"required": true,
"type": "string"
},
{
"in": "formData",
"name": "userName",
"description": "The name of the user",
"required": true,
"type": "string"
},
{
"in": "formData",
"name": "oldPassword",
"description": "The old password of the user",
"required": true,
"type": "string"
},
{
"in": "formData",
"name": "newPassword",
"description": "The new password of the user",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/signup": {
"post": {
"tags": [
"api"
],
"description": "sign up a new user",
"operationId": "ApiController.Signup",
"parameters": [
{
"in": "formData",
"name": "username",
"description": "The username to register",
"description": "The username to sign up",
"required": true,
"type": "string"
},
@ -965,7 +1122,7 @@
"tags": [
"api"
],
"description": "register a new user",
"description": "upload avatar",
"operationId": "ApiController.UploadAvatar",
"parameters": [
{
@ -995,7 +1152,11 @@
}
},
"definitions": {
"1471.0xc0003bd890.false": {
"1671.0xc00044ab10.false": {
"title": "false",
"type": "object"
},
"1705.0xc00044ab40.false": {
"title": "false",
"type": "object"
},
@ -1008,7 +1169,10 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/1471.0xc0003bd890.false"
"$ref": "#/definitions/1671.0xc00044ab10.false"
},
"data2": {
"$ref": "#/definitions/1705.0xc00044ab40.false"
},
"msg": {
"type": "string"
@ -1023,7 +1187,10 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/1471.0xc0003bd890.false"
"$ref": "#/definitions/1671.0xc00044ab10.false"
},
"data2": {
"$ref": "#/definitions/1705.0xc00044ab40.false"
},
"msg": {
"type": "string"
@ -1037,6 +1204,9 @@
"title": "Application",
"type": "object",
"properties": {
"affiliationUrl": {
"type": "string"
},
"clientId": {
"type": "string"
},
@ -1062,6 +1232,9 @@
"type": "integer",
"format": "int64"
},
"forgetUrl": {
"type": "string"
},
"homepageUrl": {
"type": "string"
},
@ -1074,19 +1247,16 @@
"organization": {
"type": "string"
},
"organizationObj": {
"$ref": "#/definitions/object.Organization"
},
"owner": {
"type": "string"
},
"providerObjs": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Provider"
}
},
"providers": {
"type": "array",
"items": {
"type": "string"
"$ref": "#/definitions/object.ProviderItem"
}
},
"redirectUris": {
@ -1094,6 +1264,18 @@
"items": {
"type": "string"
}
},
"signinUrl": {
"type": "string"
},
"signupItems": {
"type": "array",
"items": {
"$ref": "#/definitions/object.SignupItem"
}
},
"signupUrl": {
"type": "string"
}
}
},
@ -1104,15 +1286,30 @@
"createdTime": {
"type": "string"
},
"defaultAvatar": {
"type": "string"
},
"displayName": {
"type": "string"
},
"favicon": {
"type": "string"
},
"name": {
"type": "string"
},
"owner": {
"type": "string"
},
"passwordSalt": {
"type": "string"
},
"passwordType": {
"type": "string"
},
"phonePrefix": {
"type": "string"
},
"websiteUrl": {
"type": "string"
}
@ -1122,32 +1319,121 @@
"title": "Provider",
"type": "object",
"properties": {
"appId": {
"type": "string"
},
"category": {
"type": "string"
},
"clientId": {
"type": "string"
},
"clientSecret": {
"type": "string"
},
"content": {
"type": "string"
},
"createdTime": {
"type": "string"
},
"displayName": {
"type": "string"
},
"host": {
"type": "string"
},
"name": {
"type": "string"
},
"owner": {
"type": "string"
},
"port": {
"type": "integer",
"format": "int64"
},
"providerUrl": {
"type": "string"
},
"regionId": {
"type": "string"
},
"signName": {
"type": "string"
},
"templateCode": {
"type": "string"
},
"title": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"object.ProviderItem": {
"title": "ProviderItem",
"type": "object",
"properties": {
"alertType": {
"type": "string"
},
"canSignIn": {
"type": "boolean"
},
"canSignUp": {
"type": "boolean"
},
"canUnlink": {
"type": "boolean"
},
"name": {
"type": "string"
},
"prompted": {
"type": "boolean"
},
"provider": {
"$ref": "#/definitions/object.Provider"
}
}
},
"object.Records": {
"title": "Records",
"type": "object",
"properties": {
"Record": {
"$ref": "#/definitions/util.Record"
},
"id": {
"type": "integer",
"format": "int64"
}
}
},
"object.SignupItem": {
"title": "SignupItem",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"prompted": {
"type": "boolean"
},
"required": {
"type": "boolean"
},
"rule": {
"type": "string"
},
"visible": {
"type": "boolean"
}
}
},
"object.Token": {
"title": "Token",
"type": "object",
@ -1171,6 +1457,9 @@
"name": {
"type": "string"
},
"organization": {
"type": "string"
},
"owner": {
"type": "string"
},
@ -1179,6 +1468,9 @@
},
"tokenType": {
"type": "string"
},
"user": {
"type": "string"
}
}
},
@ -1205,6 +1497,12 @@
"title": "User",
"type": "object",
"properties": {
"address": {
"type": "array",
"items": {
"type": "string"
}
},
"affiliation": {
"type": "string"
},
@ -1214,27 +1512,45 @@
"createdTime": {
"type": "string"
},
"dingtalk": {
"type": "string"
},
"displayName": {
"type": "string"
},
"email": {
"type": "string"
},
"facebook": {
"type": "string"
},
"gitee": {
"type": "string"
},
"github": {
"type": "string"
},
"google": {
"type": "string"
},
"hash": {
"type": "string"
},
"id": {
"type": "string"
},
"isAdmin": {
"type": "boolean"
},
"isForbidden": {
"type": "boolean"
},
"isGlobalAdmin": {
"type": "boolean"
},
"language": {
"type": "string"
},
"name": {
"type": "string"
},
@ -1244,17 +1560,65 @@
"password": {
"type": "string"
},
"passwordType": {
"type": "string"
},
"phone": {
"type": "string"
},
"preHash": {
"type": "string"
},
"properties": {
"additionalProperties": {
"type": "string"
}
},
"qq": {
"type": "string"
},
"score": {
"type": "integer",
"format": "int64"
},
"signupApplication": {
"type": "string"
},
"tag": {
"type": "string"
},
"type": {
"type": "string"
},
"updatedTime": {
"type": "string"
},
"wechat": {
"type": "string"
},
"weibo": {
"type": "string"
}
}
},
"util.Record": {
"title": "Record",
"type": "object",
"properties": {
"ip": {
"type": "string"
},
"organization": {
"type": "string"
},
"requestTime": {
"type": "string"
},
"requestUri": {
"type": "string"
},
"urlpath": {
"type": "string"
},
"username": {
"type": "string"
}
}
}

View File

@ -234,6 +234,45 @@ paths:
type: array
items:
$ref: '#/definitions/object.Application'
/api/get-default-application:
get:
tags:
- api
description: get the detail of the default application
operationId: ApiController.GetDefaultApplication
parameters:
- in: query
name: owner
description: The owner of the application.
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/object.Application'
/api/get-email-and-phone:
post:
tags:
- api
description: get email and phone by username
operationId: ApiController.GetEmailAndPhone
parameters:
- in: formData
name: username
description: The username of the user
required: true
type: string
- in: formData
name: organization
description: The organization of the user
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/get-global-users:
get:
tags:
@ -319,6 +358,39 @@ paths:
type: array
items:
$ref: '#/definitions/object.Provider'
/api/get-records:
get:
tags:
- api
description: get all records
operationId: ApiController.GetRecords
responses:
"200":
description: The Response object
schema:
type: array
items:
$ref: '#/definitions/object.Records'
/api/get-records-filter:
post:
tags:
- api
description: get records by filter
operationId: ApiController.GetRecordsByFilter
parameters:
- in: body
name: body
description: filter Record message
required: true
schema:
$ref: '#/definitions/object.Records'
responses:
"200":
description: The Response object
schema:
type: array
items:
$ref: '#/definitions/object.Records'
/api/get-token:
get:
tags:
@ -456,16 +528,48 @@ paths:
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/register:
/api/set-password:
post:
tags:
- api
description: register a new user
operationId: ApiController.Register
description: set password
operationId: ApiController.SetPassword
parameters:
- in: formData
name: userOwner
description: The owner of the user
required: true
type: string
- in: formData
name: userName
description: The name of the user
required: true
type: string
- in: formData
name: oldPassword
description: The old password of the user
required: true
type: string
- in: formData
name: newPassword
description: The new password of the user
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/signup:
post:
tags:
- api
description: sign up a new user
operationId: ApiController.Signup
parameters:
- in: formData
name: username
description: The username to register
description: The username to sign up
required: true
type: string
- in: formData
@ -633,7 +737,7 @@ paths:
post:
tags:
- api
description: register a new user
description: upload avatar
operationId: ApiController.UploadAvatar
parameters:
- in: formData
@ -652,7 +756,10 @@ paths:
schema:
$ref: '#/definitions/controllers.Response'
definitions:
1471.0xc0003bd890.false:
1671.0xc00044ab10.false:
title: "false"
type: object
1705.0xc00044ab40.false:
title: "false"
type: object
RequestForm:
@ -663,7 +770,9 @@ definitions:
type: object
properties:
data:
$ref: '#/definitions/1471.0xc0003bd890.false'
$ref: '#/definitions/1671.0xc00044ab10.false'
data2:
$ref: '#/definitions/1705.0xc00044ab40.false'
msg:
type: string
status:
@ -673,7 +782,9 @@ definitions:
type: object
properties:
data:
$ref: '#/definitions/1471.0xc0003bd890.false'
$ref: '#/definitions/1671.0xc00044ab10.false'
data2:
$ref: '#/definitions/1705.0xc00044ab40.false'
msg:
type: string
status:
@ -682,6 +793,8 @@ definitions:
title: Application
type: object
properties:
affiliationUrl:
type: string
clientId:
type: string
clientSecret:
@ -699,6 +812,8 @@ definitions:
expireInHours:
type: integer
format: int64
forgetUrl:
type: string
homepageUrl:
type: string
logo:
@ -707,54 +822,130 @@ definitions:
type: string
organization:
type: string
organizationObj:
$ref: '#/definitions/object.Organization'
owner:
type: string
providerObjs:
type: array
items:
$ref: '#/definitions/object.Provider'
providers:
type: array
items:
type: string
$ref: '#/definitions/object.ProviderItem'
redirectUris:
type: array
items:
type: string
signinUrl:
type: string
signupItems:
type: array
items:
$ref: '#/definitions/object.SignupItem'
signupUrl:
type: string
object.Organization:
title: Organization
type: object
properties:
createdTime:
type: string
defaultAvatar:
type: string
displayName:
type: string
favicon:
type: string
name:
type: string
owner:
type: string
passwordSalt:
type: string
passwordType:
type: string
phonePrefix:
type: string
websiteUrl:
type: string
object.Provider:
title: Provider
type: object
properties:
appId:
type: string
category:
type: string
clientId:
type: string
clientSecret:
type: string
content:
type: string
createdTime:
type: string
displayName:
type: string
host:
type: string
name:
type: string
owner:
type: string
port:
type: integer
format: int64
providerUrl:
type: string
regionId:
type: string
signName:
type: string
templateCode:
type: string
title:
type: string
type:
type: string
object.ProviderItem:
title: ProviderItem
type: object
properties:
alertType:
type: string
canSignIn:
type: boolean
canSignUp:
type: boolean
canUnlink:
type: boolean
name:
type: string
prompted:
type: boolean
provider:
$ref: '#/definitions/object.Provider'
object.Records:
title: Records
type: object
properties:
Record:
$ref: '#/definitions/util.Record'
id:
type: integer
format: int64
object.SignupItem:
title: SignupItem
type: object
properties:
name:
type: string
prompted:
type: boolean
required:
type: boolean
rule:
type: string
visible:
type: boolean
object.Token:
title: Token
type: object
@ -772,12 +963,16 @@ definitions:
format: int64
name:
type: string
organization:
type: string
owner:
type: string
scope:
type: string
tokenType:
type: string
user:
type: string
object.TokenWrapper:
title: TokenWrapper
type: object
@ -795,37 +990,85 @@ definitions:
title: User
type: object
properties:
address:
type: array
items:
type: string
affiliation:
type: string
avatar:
type: string
createdTime:
type: string
dingtalk:
type: string
displayName:
type: string
email:
type: string
facebook:
type: string
gitee:
type: string
github:
type: string
google:
type: string
hash:
type: string
id:
type: string
isAdmin:
type: boolean
isForbidden:
type: boolean
isGlobalAdmin:
type: boolean
language:
type: string
name:
type: string
owner:
type: string
password:
type: string
passwordType:
type: string
phone:
type: string
preHash:
type: string
properties:
additionalProperties:
type: string
qq:
type: string
score:
type: integer
format: int64
signupApplication:
type: string
tag:
type: string
type:
type: string
updatedTime:
type: string
wechat:
type: string
weibo:
type: string
util.Record:
title: Record
type: object
properties:
ip:
type: string
organization:
type: string
requestTime:
type: string
requestUri:
type: string
urlpath:
type: string
username:
type: string

47
util/record.go Normal file
View File

@ -0,0 +1,47 @@
// Copyright 2021 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 util
import (
"strings"
"github.com/astaxie/beego/context"
)
type Record struct {
ClientIp string `xorm:"varchar(100)" json:"clientIp"`
Timestamp string `xorm:"varchar(100)" json:"timestamp"`
Organization string `xorm:"varchar(100)" json:"organization"`
Username string `xorm:"varchar(100)" json:"username"`
RequestUri string `xorm:"varchar(1000)" json:"requestUri"`
Action string `xorm:"varchar(1000)" json:"action"`
}
func Records(ctx *context.Context) *Record {
ip := strings.Replace(getIPFromRequest(ctx.Request), ": ", "", -1)
currenttime := GetCurrentTime()
requesturi := ctx.Request.RequestURI
action := strings.Replace(ctx.Request.URL.Path, "/api/", "", -1)
record := Record{
ClientIp: ip,
Timestamp: currenttime,
RequestUri: requesturi,
Username: "",
Organization: "",
Action: action,
}
return &record
}

View File

@ -29,6 +29,7 @@ import ApplicationListPage from "./ApplicationListPage";
import ApplicationEditPage from "./ApplicationEditPage";
import TokenListPage from "./TokenListPage";
import TokenEditPage from "./TokenEditPage";
import RecordListPage from "./RecordListPage";
import AccountPage from "./account/AccountPage";
import HomePage from "./basic/HomePage";
import CustomGithubCorner from "./CustomGithubCorner";
@ -317,6 +318,13 @@ class App extends Component {
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="7">
<Link to="/records">
{i18next.t("general:Records")}
</Link>
</Menu.Item>
);
}
res.push(
<Menu.Item key="6" onClick={() => window.location.href = "/swagger"}>
@ -392,6 +400,7 @@ class App extends Component {
<Route exact path="/applications/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)}/>
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)}/>
</Switch>
</div>
)

159
web/src/RecordListPage.js Normal file
View File

@ -0,0 +1,159 @@
// Copyright 2021 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.
import React from "react";
import {Link} from "react-router-dom";
import {Col, Row, Table} from 'antd';
import * as Setting from "./Setting";
import * as RecordBackend from "./backend/RecordBackend";
import i18next from "i18next";
class RecordListPage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
records: null,
};
}
UNSAFE_componentWillMount() {
this.getRecords();
}
getRecords() {
RecordBackend.getRecords()
.then((res) => {
this.setState({
records: res,
});
});
}
newRecord() {
return {
id : "",
Record:{
clientIp:"",
timestamp:"",
organization:"",
username:"",
requestUri:"",
action:"login",
},
}
}
renderTable(records) {
const columns = [
{
title: i18next.t("general:Client ip"),
dataIndex: 'Record',
key: 'Record',
width: '120px',
sorter: (a, b) => a.Record.clientIp.localeCompare(b.Record.clientIp),
render: (text, record, index) => {
return text.clientIp;
}
},
{
title: i18next.t("general:Timestamp"),
dataIndex: 'Record',
key: 'Record',
width: '160px',
sorter: (a, b) => a.Record.timestamp.localeCompare(b.Record.timestamp),
render: (text, record, index) => {
return Setting.getFormattedDate(text.timestamp);
}
},
{
title: i18next.t("general:Organization"),
dataIndex: 'Record',
key: 'Record',
width: '120px',
sorter: (a, b) => a.Record.organization.localeCompare(b.Record.organization),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text.organization}`}>
{text.organization}
</Link>
)
}
},
{
title: i18next.t("general:Username"),
dataIndex: 'Record',
key: 'Record',
width: '160px',
sorter: (a, b) => a.Record.username.localeCompare(b.Record.username),
render: (text, record, index) => {
return text.username;
}
},
{
title: i18next.t("general:Request uri"),
dataIndex: 'Record',
key: 'Record',
width: '160px',
sorter: (a, b) => a.Record.requestUri.localeCompare(b.Record.requestUri),
render: (text, record, index) => {
return text.requestUri;
}
},
{
title: i18next.t("general:Action"),
dataIndex: 'Record',
key: 'Record',
width: '160px',
sorter: (a, b) => a.Record.action.localeCompare(b.Record.action),
render: (text, record, index) => {
return text.action;
}
},
];
return (
<div>
<Table columns={columns} dataSource={records} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
title={() => (
<div>
{i18next.t("general:Records")}&nbsp;&nbsp;&nbsp;&nbsp;
</div>
)}
loading={records === null}
/>
</div>
);
}
render() {
return (
<div>
<Row style={{width: "100%"}}>
<Col span={1}>
</Col>
<Col span={22}>
{
this.renderTable(this.state.records)
}
</Col>
<Col span={1}>
</Col>
</Row>
</div>
);
}
}
export default RecordListPage;

View File

@ -0,0 +1,22 @@
// Copyright 2021 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.
import * as Setting from "../Setting";
export function getRecords() {
return fetch(`${Setting.ServerUrl}/api/get-records`, {
method: "GET",
credentials: "include"
}).then(res => res.json());
}

View File

@ -13,6 +13,11 @@
"User": "User",
"Applications": "Applications",
"Application": "Application",
"Records": "Records",
"Client ip": "Client ip",
"Timestamp": "Timestamp",
"Username": "Username",
"Request uri": "Request uri",
"Save": "Save",
"Add": "Add",
"Action": "Action",