Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
532b60f7
authored
Jun 10, 2023
by
JustSong
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: now user can check its topup & consume history (close #78, close #95)
parent
d6931d05
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
240 additions
and
3 deletions
+240
-3
controller/relay.go
+2
-0
model/log.go
+18
-3
model/main.go
+4
-0
model/redemption.go
+2
-0
web/src/App.js
+9
-0
web/src/components/Header.js
+5
-0
web/src/components/LogsTable.js
+186
-0
web/src/pages/Log/index.js
+14
-0
No files found.
controller/relay.go
View file @
532b60f7
...
@@ -244,6 +244,8 @@ func relayHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
...
@@ -244,6 +244,8 @@ func relayHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
if
err
!=
nil
{
if
err
!=
nil
{
common
.
SysError
(
"Error consuming token remain quota: "
+
err
.
Error
())
common
.
SysError
(
"Error consuming token remain quota: "
+
err
.
Error
())
}
}
userId
:=
c
.
GetInt
(
"id"
)
model
.
RecordLog
(
userId
,
model
.
LogTypeConsume
,
fmt
.
Sprintf
(
"使用模型 %s 消耗 %d 点额度"
,
textRequest
.
Model
,
quota
))
}
}
}()
}()
...
...
model/log.go
View file @
532b60f7
package
model
package
model
import
"one-api/common"
import
(
"gorm.io/gorm"
"one-api/common"
)
type
Log
struct
{
type
Log
struct
{
Id
int
`json:"id"`
Id
int
`json:"id"`
...
@@ -10,6 +13,12 @@ type Log struct {
...
@@ -10,6 +13,12 @@ type Log struct {
Content
string
`json:"content"`
Content
string
`json:"content"`
}
}
const
(
LogTypeUnknown
=
iota
LogTypeTopup
LogTypeConsume
)
func
RecordLog
(
userId
int
,
logType
int
,
content
string
)
{
func
RecordLog
(
userId
int
,
logType
int
,
content
string
)
{
log
:=
&
Log
{
log
:=
&
Log
{
UserId
:
userId
,
UserId
:
userId
,
...
@@ -29,7 +38,13 @@ func GetAllLogs(logType int, startIdx int, num int) (logs []*Log, err error) {
...
@@ -29,7 +38,13 @@ func GetAllLogs(logType int, startIdx int, num int) (logs []*Log, err error) {
}
}
func
GetUserLogs
(
userId
int
,
logType
int
,
startIdx
int
,
num
int
)
(
logs
[]
*
Log
,
err
error
)
{
func
GetUserLogs
(
userId
int
,
logType
int
,
startIdx
int
,
num
int
)
(
logs
[]
*
Log
,
err
error
)
{
err
=
DB
.
Where
(
"user_id = ? and type = ?"
,
userId
,
logType
)
.
Order
(
"id desc"
)
.
Limit
(
num
)
.
Offset
(
startIdx
)
.
Find
(
&
logs
)
.
Error
var
tx
*
gorm
.
DB
if
logType
==
LogTypeUnknown
{
tx
=
DB
.
Where
(
"user_id = ?"
,
userId
)
}
else
{
tx
=
DB
.
Where
(
"user_id = ? and type = ?"
,
userId
,
logType
)
}
err
=
tx
.
Order
(
"id desc"
)
.
Limit
(
num
)
.
Offset
(
startIdx
)
.
Omit
(
"id"
)
.
Find
(
&
logs
)
.
Error
return
logs
,
err
return
logs
,
err
}
}
...
@@ -39,6 +54,6 @@ func SearchAllLogs(keyword string) (logs []*Log, err error) {
...
@@ -39,6 +54,6 @@ func SearchAllLogs(keyword string) (logs []*Log, err error) {
}
}
func
SearchUserLogs
(
userId
int
,
keyword
string
)
(
logs
[]
*
Log
,
err
error
)
{
func
SearchUserLogs
(
userId
int
,
keyword
string
)
(
logs
[]
*
Log
,
err
error
)
{
err
=
DB
.
Where
(
"user_id = ? and type = ?"
,
userId
,
keyword
)
.
Order
(
"id desc"
)
.
Limit
(
common
.
MaxRecentItems
)
.
Find
(
&
logs
)
.
Error
err
=
DB
.
Where
(
"user_id = ? and type = ?"
,
userId
,
keyword
)
.
Order
(
"id desc"
)
.
Limit
(
common
.
MaxRecentItems
)
.
Omit
(
"id"
)
.
Find
(
&
logs
)
.
Error
return
logs
,
err
return
logs
,
err
}
}
model/main.go
View file @
532b60f7
...
@@ -79,6 +79,10 @@ func InitDB() (err error) {
...
@@ -79,6 +79,10 @@ func InitDB() (err error) {
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
err
=
db
.
AutoMigrate
(
&
Log
{})
if
err
!=
nil
{
return
err
}
err
=
createRootAccountIfNeed
()
err
=
createRootAccountIfNeed
()
return
err
return
err
}
else
{
}
else
{
...
...
model/redemption.go
View file @
532b60f7
...
@@ -2,6 +2,7 @@ package model
...
@@ -2,6 +2,7 @@ package model
import
(
import
(
"errors"
"errors"
"fmt"
"one-api/common"
"one-api/common"
)
)
...
@@ -65,6 +66,7 @@ func Redeem(key string, userId int) (quota int, err error) {
...
@@ -65,6 +66,7 @@ func Redeem(key string, userId int) (quota int, err error) {
if
err
!=
nil
{
if
err
!=
nil
{
common
.
SysError
(
"更新兑换码状态失败:"
+
err
.
Error
())
common
.
SysError
(
"更新兑换码状态失败:"
+
err
.
Error
())
}
}
RecordLog
(
userId
,
LogTypeTopup
,
fmt
.
Sprintf
(
"通过兑换码充值 %d 点额度"
,
redemption
.
Quota
))
}()
}()
return
redemption
.
Quota
,
nil
return
redemption
.
Quota
,
nil
}
}
...
...
web/src/App.js
View file @
532b60f7
...
@@ -22,6 +22,7 @@ import EditChannel from './pages/Channel/EditChannel';
...
@@ -22,6 +22,7 @@ import EditChannel from './pages/Channel/EditChannel';
import
Redemption
from
'./pages/Redemption'
;
import
Redemption
from
'./pages/Redemption'
;
import
EditRedemption
from
'./pages/Redemption/EditRedemption'
;
import
EditRedemption
from
'./pages/Redemption/EditRedemption'
;
import
TopUp
from
'./pages/TopUp'
;
import
TopUp
from
'./pages/TopUp'
;
import
Log
from
'./pages/Log'
;
const
Home
=
lazy
(()
=>
import
(
'./pages/Home'
));
const
Home
=
lazy
(()
=>
import
(
'./pages/Home'
));
const
About
=
lazy
(()
=>
import
(
'./pages/About'
));
const
About
=
lazy
(()
=>
import
(
'./pages/About'
));
...
@@ -251,6 +252,14 @@ function App() {
...
@@ -251,6 +252,14 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
'/log'
element
=
{
<
PrivateRoute
>
<
Log
/>
<
/PrivateRoute
>
}
/
>
<
Route
path
=
'/about'
path
=
'/about'
element
=
{
element
=
{
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
...
...
web/src/components/Header.js
View file @
532b60f7
...
@@ -42,6 +42,11 @@ const headerButtons = [
...
@@ -42,6 +42,11 @@ const headerButtons = [
admin
:
true
,
admin
:
true
,
},
},
{
{
name
:
'日志'
,
to
:
'/log'
,
icon
:
'book'
,
},
{
name
:
'设置'
,
name
:
'设置'
,
to
:
'/setting'
,
to
:
'/setting'
,
icon
:
'setting'
,
icon
:
'setting'
,
...
...
web/src/components/LogsTable.js
0 → 100644
View file @
532b60f7
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
Button
,
Label
,
Pagination
,
Table
}
from
'semantic-ui-react'
;
import
{
API
,
showError
,
timestamp2string
}
from
'../helpers'
;
import
{
ITEMS_PER_PAGE
}
from
'../constants'
;
function
renderTimestamp
(
timestamp
)
{
return
(
<>
{
timestamp2string
(
timestamp
)}
<
/
>
);
}
function
renderType
(
type
)
{
switch
(
type
)
{
case
1
:
return
<
Label
basic
color
=
'green'
>
充值
<
/Label>
;
case
2
:
return
<
Label
basic
color
=
'olive'
>
消费
<
/Label>
;
default
:
return
<
Label
basic
color
=
'black'
>
未知
<
/Label>
;
}
}
const
LogsTable
=
()
=>
{
const
[
logs
,
setLogs
]
=
useState
([]);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
activePage
,
setActivePage
]
=
useState
(
1
);
const
[
searchKeyword
,
setSearchKeyword
]
=
useState
(
''
);
const
[
searching
,
setSearching
]
=
useState
(
false
);
const
loadLogs
=
async
(
startIdx
)
=>
{
const
res
=
await
API
.
get
(
`/api/log/self/?p=
${
startIdx
}
`
);
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
if
(
startIdx
===
0
)
{
setLogs
(
data
);
}
else
{
let
newLogs
=
logs
;
newLogs
.
push
(...
data
);
setLogs
(
newLogs
);
}
}
else
{
showError
(
message
);
}
setLoading
(
false
);
};
const
onPaginationChange
=
(
e
,
{
activePage
})
=>
{
(
async
()
=>
{
if
(
activePage
===
Math
.
ceil
(
logs
.
length
/
ITEMS_PER_PAGE
)
+
1
)
{
// In this case we have to load more data and then append them.
await
loadLogs
(
activePage
-
1
);
}
setActivePage
(
activePage
);
})();
};
const
refresh
=
async
()
=>
{
setLoading
(
true
);
await
loadLogs
(
0
);
};
useEffect
(()
=>
{
loadLogs
(
0
)
.
then
()
.
catch
((
reason
)
=>
{
showError
(
reason
);
});
},
[]);
const
searchLogs
=
async
()
=>
{
if
(
searchKeyword
===
''
)
{
// if keyword is blank, load files instead.
await
loadLogs
(
0
);
setActivePage
(
1
);
return
;
}
setSearching
(
true
);
const
res
=
await
API
.
get
(
`/api/log/self/search?keyword=
${
searchKeyword
}
`
);
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
setLogs
(
data
);
setActivePage
(
1
);
}
else
{
showError
(
message
);
}
setSearching
(
false
);
};
const
handleKeywordChange
=
async
(
e
,
{
value
})
=>
{
setSearchKeyword
(
value
.
trim
());
};
const
sortLog
=
(
key
)
=>
{
if
(
logs
.
length
===
0
)
return
;
setLoading
(
true
);
let
sortedLogs
=
[...
logs
];
sortedLogs
.
sort
((
a
,
b
)
=>
{
return
(
''
+
a
[
key
]).
localeCompare
(
b
[
key
]);
});
if
(
sortedLogs
[
0
].
id
===
logs
[
0
].
id
)
{
sortedLogs
.
reverse
();
}
setLogs
(
sortedLogs
);
setLoading
(
false
);
};
return
(
<>
<
Table
basic
>
<
Table
.
Header
>
<
Table
.
Row
>
<
Table
.
HeaderCell
style
=
{{
cursor
:
'pointer'
}}
onClick
=
{()
=>
{
sortLog
(
'created_time'
);
}}
width
=
{
3
}
>
时间
<
/Table.HeaderCell
>
<
Table
.
HeaderCell
style
=
{{
cursor
:
'pointer'
}}
onClick
=
{()
=>
{
sortLog
(
'type'
);
}}
width
=
{
2
}
>
类型
<
/Table.HeaderCell
>
<
Table
.
HeaderCell
style
=
{{
cursor
:
'pointer'
}}
onClick
=
{()
=>
{
sortLog
(
'content'
);
}}
width
=
{
11
}
>
详情
<
/Table.HeaderCell
>
<
/Table.Row
>
<
/Table.Header
>
<
Table
.
Body
>
{
logs
.
slice
(
(
activePage
-
1
)
*
ITEMS_PER_PAGE
,
activePage
*
ITEMS_PER_PAGE
)
.
map
((
log
,
idx
)
=>
{
if
(
log
.
deleted
)
return
<><
/>
;
return
(
<
Table
.
Row
key
=
{
log
.
created_at
}
>
<
Table
.
Cell
>
{
renderTimestamp
(
log
.
created_at
)}
<
/Table.Cell
>
<
Table
.
Cell
>
{
renderType
(
log
.
type
)}
<
/Table.Cell
>
<
Table
.
Cell
>
{
log
.
content
}
<
/Table.Cell
>
<
/Table.Row
>
);
})}
<
/Table.Body
>
<
Table
.
Footer
>
<
Table
.
Row
>
<
Table
.
HeaderCell
colSpan
=
'4'
>
<
Button
size
=
'small'
onClick
=
{
refresh
}
loading
=
{
loading
}
>
刷新
<
/Button
>
<
Pagination
floated
=
'right'
activePage
=
{
activePage
}
onPageChange
=
{
onPaginationChange
}
size
=
'small'
siblingRange
=
{
1
}
totalPages
=
{
Math
.
ceil
(
logs
.
length
/
ITEMS_PER_PAGE
)
+
(
logs
.
length
%
ITEMS_PER_PAGE
===
0
?
1
:
0
)
}
/
>
<
/Table.HeaderCell
>
<
/Table.Row
>
<
/Table.Footer
>
<
/Table
>
<
/
>
);
};
export
default
LogsTable
;
web/src/pages/Log/index.js
0 → 100644
View file @
532b60f7
import
React
from
'react'
;
import
{
Header
,
Segment
}
from
'semantic-ui-react'
;
import
LogsTable
from
'../../components/LogsTable'
;
const
Token
=
()
=>
(
<>
<
Segment
>
<
Header
as
=
'h3'
>
额度明细
<
/Header
>
<
LogsTable
/>
<
/Segment
>
<
/
>
);
export
default
Token
;
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment