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 { } else {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application)) application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
resp = c.HandleLoggedIn(application, user, &form) 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 != "" { } else if form.Provider != "" {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application)) application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
@ -252,6 +258,12 @@ func (c *ApiController) Login() {
//} //}
resp = c.HandleLoggedIn(application, user, &form) resp = c.HandleLoggedIn(application, user, &form)
record := util.Records(c.Ctx)
record.Organization = application.Organization
record.Username = user.Name
object.AddRecord(record)
} else { } else {
// Sign up via OAuth // Sign up via OAuth
if !application.EnableSignUp { if !application.EnableSignUp {
@ -294,6 +306,12 @@ func (c *ApiController) Login() {
object.LinkUserAccount(user, provider.Type, userInfo.Id) object.LinkUserAccount(user, provider.Type, userInfo.Id)
resp = c.HandleLoggedIn(application, user, &form) 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} //resp = &Response{Status: "ok", Msg: "", Data: res}
} else { // form.Method != "signup" } 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.StaticFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoLoginFilter) beego.InsertFilter("*", beego.BeforeRouter, routers.AutoLoginFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter) beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id" beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
beego.BConfig.WebConfig.Session.SessionProvider = "file" beego.BConfig.WebConfig.Session.SessionProvider = "file"

View File

@ -133,4 +133,8 @@ func (a *Adapter) createTable() {
if err != nil { if err != nil {
panic(err) 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/add-token", &controllers.ApiController{}, "POST:AddToken")
beego.Router("/api/delete-token", &controllers.ApiController{}, "POST:DeleteToken") beego.Router("/api/delete-token", &controllers.ApiController{}, "POST:DeleteToken")
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken") 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": { "/api/get-global-users": {
"get": { "get": {
"tags": [ "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": { "/api/get-token": {
"get": { "get": {
"tags": [ "tags": [
@ -700,18 +810,65 @@
} }
} }
}, },
"/api/register": { "/api/set-password": {
"post": { "post": {
"tags": [ "tags": [
"api" "api"
], ],
"description": "register a new user", "description": "set password",
"operationId": "ApiController.Register", "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": [ "parameters": [
{ {
"in": "formData", "in": "formData",
"name": "username", "name": "username",
"description": "The username to register", "description": "The username to sign up",
"required": true, "required": true,
"type": "string" "type": "string"
}, },
@ -965,7 +1122,7 @@
"tags": [ "tags": [
"api" "api"
], ],
"description": "register a new user", "description": "upload avatar",
"operationId": "ApiController.UploadAvatar", "operationId": "ApiController.UploadAvatar",
"parameters": [ "parameters": [
{ {
@ -995,7 +1152,11 @@
} }
}, },
"definitions": { "definitions": {
"1471.0xc0003bd890.false": { "1671.0xc00044ab10.false": {
"title": "false",
"type": "object"
},
"1705.0xc00044ab40.false": {
"title": "false", "title": "false",
"type": "object" "type": "object"
}, },
@ -1008,7 +1169,10 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"$ref": "#/definitions/1471.0xc0003bd890.false" "$ref": "#/definitions/1671.0xc00044ab10.false"
},
"data2": {
"$ref": "#/definitions/1705.0xc00044ab40.false"
}, },
"msg": { "msg": {
"type": "string" "type": "string"
@ -1023,7 +1187,10 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"$ref": "#/definitions/1471.0xc0003bd890.false" "$ref": "#/definitions/1671.0xc00044ab10.false"
},
"data2": {
"$ref": "#/definitions/1705.0xc00044ab40.false"
}, },
"msg": { "msg": {
"type": "string" "type": "string"
@ -1037,6 +1204,9 @@
"title": "Application", "title": "Application",
"type": "object", "type": "object",
"properties": { "properties": {
"affiliationUrl": {
"type": "string"
},
"clientId": { "clientId": {
"type": "string" "type": "string"
}, },
@ -1062,6 +1232,9 @@
"type": "integer", "type": "integer",
"format": "int64" "format": "int64"
}, },
"forgetUrl": {
"type": "string"
},
"homepageUrl": { "homepageUrl": {
"type": "string" "type": "string"
}, },
@ -1074,19 +1247,16 @@
"organization": { "organization": {
"type": "string" "type": "string"
}, },
"organizationObj": {
"$ref": "#/definitions/object.Organization"
},
"owner": { "owner": {
"type": "string" "type": "string"
}, },
"providerObjs": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Provider"
}
},
"providers": { "providers": {
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "$ref": "#/definitions/object.ProviderItem"
} }
}, },
"redirectUris": { "redirectUris": {
@ -1094,6 +1264,18 @@
"items": { "items": {
"type": "string" "type": "string"
} }
},
"signinUrl": {
"type": "string"
},
"signupItems": {
"type": "array",
"items": {
"$ref": "#/definitions/object.SignupItem"
}
},
"signupUrl": {
"type": "string"
} }
} }
}, },
@ -1104,15 +1286,30 @@
"createdTime": { "createdTime": {
"type": "string" "type": "string"
}, },
"defaultAvatar": {
"type": "string"
},
"displayName": { "displayName": {
"type": "string" "type": "string"
}, },
"favicon": {
"type": "string"
},
"name": { "name": {
"type": "string" "type": "string"
}, },
"owner": { "owner": {
"type": "string" "type": "string"
}, },
"passwordSalt": {
"type": "string"
},
"passwordType": {
"type": "string"
},
"phonePrefix": {
"type": "string"
},
"websiteUrl": { "websiteUrl": {
"type": "string" "type": "string"
} }
@ -1122,32 +1319,121 @@
"title": "Provider", "title": "Provider",
"type": "object", "type": "object",
"properties": { "properties": {
"appId": {
"type": "string"
},
"category": {
"type": "string"
},
"clientId": { "clientId": {
"type": "string" "type": "string"
}, },
"clientSecret": { "clientSecret": {
"type": "string" "type": "string"
}, },
"content": {
"type": "string"
},
"createdTime": { "createdTime": {
"type": "string" "type": "string"
}, },
"displayName": { "displayName": {
"type": "string" "type": "string"
}, },
"host": {
"type": "string"
},
"name": { "name": {
"type": "string" "type": "string"
}, },
"owner": { "owner": {
"type": "string" "type": "string"
}, },
"port": {
"type": "integer",
"format": "int64"
},
"providerUrl": { "providerUrl": {
"type": "string" "type": "string"
}, },
"regionId": {
"type": "string"
},
"signName": {
"type": "string"
},
"templateCode": {
"type": "string"
},
"title": {
"type": "string"
},
"type": { "type": {
"type": "string" "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": { "object.Token": {
"title": "Token", "title": "Token",
"type": "object", "type": "object",
@ -1171,6 +1457,9 @@
"name": { "name": {
"type": "string" "type": "string"
}, },
"organization": {
"type": "string"
},
"owner": { "owner": {
"type": "string" "type": "string"
}, },
@ -1179,6 +1468,9 @@
}, },
"tokenType": { "tokenType": {
"type": "string" "type": "string"
},
"user": {
"type": "string"
} }
} }
}, },
@ -1205,6 +1497,12 @@
"title": "User", "title": "User",
"type": "object", "type": "object",
"properties": { "properties": {
"address": {
"type": "array",
"items": {
"type": "string"
}
},
"affiliation": { "affiliation": {
"type": "string" "type": "string"
}, },
@ -1214,27 +1512,45 @@
"createdTime": { "createdTime": {
"type": "string" "type": "string"
}, },
"dingtalk": {
"type": "string"
},
"displayName": { "displayName": {
"type": "string" "type": "string"
}, },
"email": { "email": {
"type": "string" "type": "string"
}, },
"facebook": {
"type": "string"
},
"gitee": {
"type": "string"
},
"github": { "github": {
"type": "string" "type": "string"
}, },
"google": { "google": {
"type": "string" "type": "string"
}, },
"hash": {
"type": "string"
},
"id": { "id": {
"type": "string" "type": "string"
}, },
"isAdmin": { "isAdmin": {
"type": "boolean" "type": "boolean"
}, },
"isForbidden": {
"type": "boolean"
},
"isGlobalAdmin": { "isGlobalAdmin": {
"type": "boolean" "type": "boolean"
}, },
"language": {
"type": "string"
},
"name": { "name": {
"type": "string" "type": "string"
}, },
@ -1244,17 +1560,65 @@
"password": { "password": {
"type": "string" "type": "string"
}, },
"passwordType": {
"type": "string"
},
"phone": { "phone": {
"type": "string" "type": "string"
}, },
"preHash": {
"type": "string"
},
"properties": {
"additionalProperties": {
"type": "string"
}
},
"qq": { "qq": {
"type": "string" "type": "string"
}, },
"score": {
"type": "integer",
"format": "int64"
},
"signupApplication": {
"type": "string"
},
"tag": { "tag": {
"type": "string" "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 type: array
items: items:
$ref: '#/definitions/object.Application' $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: /api/get-global-users:
get: get:
tags: tags:
@ -319,6 +358,39 @@ paths:
type: array type: array
items: items:
$ref: '#/definitions/object.Provider' $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: /api/get-token:
get: get:
tags: tags:
@ -456,16 +528,48 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/register: /api/set-password:
post: post:
tags: tags:
- api - api
description: register a new user description: set password
operationId: ApiController.Register 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: parameters:
- in: formData - in: formData
name: username name: username
description: The username to register description: The username to sign up
required: true required: true
type: string type: string
- in: formData - in: formData
@ -633,7 +737,7 @@ paths:
post: post:
tags: tags:
- api - api
description: register a new user description: upload avatar
operationId: ApiController.UploadAvatar operationId: ApiController.UploadAvatar
parameters: parameters:
- in: formData - in: formData
@ -652,7 +756,10 @@ paths:
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
definitions: definitions:
1471.0xc0003bd890.false: 1671.0xc00044ab10.false:
title: "false"
type: object
1705.0xc00044ab40.false:
title: "false" title: "false"
type: object type: object
RequestForm: RequestForm:
@ -663,7 +770,9 @@ definitions:
type: object type: object
properties: properties:
data: data:
$ref: '#/definitions/1471.0xc0003bd890.false' $ref: '#/definitions/1671.0xc00044ab10.false'
data2:
$ref: '#/definitions/1705.0xc00044ab40.false'
msg: msg:
type: string type: string
status: status:
@ -673,7 +782,9 @@ definitions:
type: object type: object
properties: properties:
data: data:
$ref: '#/definitions/1471.0xc0003bd890.false' $ref: '#/definitions/1671.0xc00044ab10.false'
data2:
$ref: '#/definitions/1705.0xc00044ab40.false'
msg: msg:
type: string type: string
status: status:
@ -682,6 +793,8 @@ definitions:
title: Application title: Application
type: object type: object
properties: properties:
affiliationUrl:
type: string
clientId: clientId:
type: string type: string
clientSecret: clientSecret:
@ -699,6 +812,8 @@ definitions:
expireInHours: expireInHours:
type: integer type: integer
format: int64 format: int64
forgetUrl:
type: string
homepageUrl: homepageUrl:
type: string type: string
logo: logo:
@ -707,54 +822,130 @@ definitions:
type: string type: string
organization: organization:
type: string type: string
organizationObj:
$ref: '#/definitions/object.Organization'
owner: owner:
type: string type: string
providerObjs:
type: array
items:
$ref: '#/definitions/object.Provider'
providers: providers:
type: array type: array
items: items:
type: string $ref: '#/definitions/object.ProviderItem'
redirectUris: redirectUris:
type: array type: array
items: items:
type: string type: string
signinUrl:
type: string
signupItems:
type: array
items:
$ref: '#/definitions/object.SignupItem'
signupUrl:
type: string
object.Organization: object.Organization:
title: Organization title: Organization
type: object type: object
properties: properties:
createdTime: createdTime:
type: string type: string
defaultAvatar:
type: string
displayName: displayName:
type: string type: string
favicon:
type: string
name: name:
type: string type: string
owner: owner:
type: string type: string
passwordSalt:
type: string
passwordType:
type: string
phonePrefix:
type: string
websiteUrl: websiteUrl:
type: string type: string
object.Provider: object.Provider:
title: Provider title: Provider
type: object type: object
properties: properties:
appId:
type: string
category:
type: string
clientId: clientId:
type: string type: string
clientSecret: clientSecret:
type: string type: string
content:
type: string
createdTime: createdTime:
type: string type: string
displayName: displayName:
type: string type: string
host:
type: string
name: name:
type: string type: string
owner: owner:
type: string type: string
port:
type: integer
format: int64
providerUrl: providerUrl:
type: string type: string
regionId:
type: string
signName:
type: string
templateCode:
type: string
title:
type: string
type: type:
type: string 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: object.Token:
title: Token title: Token
type: object type: object
@ -772,12 +963,16 @@ definitions:
format: int64 format: int64
name: name:
type: string type: string
organization:
type: string
owner: owner:
type: string type: string
scope: scope:
type: string type: string
tokenType: tokenType:
type: string type: string
user:
type: string
object.TokenWrapper: object.TokenWrapper:
title: TokenWrapper title: TokenWrapper
type: object type: object
@ -795,37 +990,85 @@ definitions:
title: User title: User
type: object type: object
properties: properties:
address:
type: array
items:
type: string
affiliation: affiliation:
type: string type: string
avatar: avatar:
type: string type: string
createdTime: createdTime:
type: string type: string
dingtalk:
type: string
displayName: displayName:
type: string type: string
email: email:
type: string type: string
facebook:
type: string
gitee:
type: string
github: github:
type: string type: string
google: google:
type: string type: string
hash:
type: string
id: id:
type: string type: string
isAdmin: isAdmin:
type: boolean type: boolean
isForbidden:
type: boolean
isGlobalAdmin: isGlobalAdmin:
type: boolean type: boolean
language:
type: string
name: name:
type: string type: string
owner: owner:
type: string type: string
password: password:
type: string type: string
passwordType:
type: string
phone: phone:
type: string type: string
preHash:
type: string
properties:
additionalProperties:
type: string
qq: qq:
type: string type: string
score:
type: integer
format: int64
signupApplication:
type: string
tag: tag:
type: string 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 ApplicationEditPage from "./ApplicationEditPage";
import TokenListPage from "./TokenListPage"; import TokenListPage from "./TokenListPage";
import TokenEditPage from "./TokenEditPage"; import TokenEditPage from "./TokenEditPage";
import RecordListPage from "./RecordListPage";
import AccountPage from "./account/AccountPage"; import AccountPage from "./account/AccountPage";
import HomePage from "./basic/HomePage"; import HomePage from "./basic/HomePage";
import CustomGithubCorner from "./CustomGithubCorner"; import CustomGithubCorner from "./CustomGithubCorner";
@ -317,6 +318,13 @@ class App extends Component {
</Link> </Link>
</Menu.Item> </Menu.Item>
); );
res.push(
<Menu.Item key="7">
<Link to="/records">
{i18next.t("general:Records")}
</Link>
</Menu.Item>
);
} }
res.push( res.push(
<Menu.Item key="6" onClick={() => window.location.href = "/swagger"}> <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="/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" 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="/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> </Switch>
</div> </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", "User": "User",
"Applications": "Applications", "Applications": "Applications",
"Application": "Application", "Application": "Application",
"Records": "Records",
"Client ip": "Client ip",
"Timestamp": "Timestamp",
"Username": "Username",
"Request uri": "Request uri",
"Save": "Save", "Save": "Save",
"Add": "Add", "Add": "Add",
"Action": "Action", "Action": "Action",