Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
db-tools
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
yzy
db-tools
Commits
d57928c4
Commit
d57928c4
authored
Sep 10, 2026
by
wangteng
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
代码提交
parent
6d58b2e9
Changes
5
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
40 additions
and
64 deletions
+40
-64
CLAUDE.md
CLAUDE.md
+3
-0
web/backend/routers/operations.py
web/backend/routers/operations.py
+0
-41
web/src/views/ClearTablesView.vue
web/src/views/ClearTablesView.vue
+24
-14
web/src/views/DataSourceView.vue
web/src/views/DataSourceView.vue
+1
-1
web/src/views/TableDesignView.vue
web/src/views/TableDesignView.vue
+12
-8
No files found.
CLAUDE.md
View file @
d57928c4
...
...
@@ -5,3 +5,6 @@
# 总体要求
-
每做一个任务就在工作记录中记录一次
# 服务器发版本
-
deploy_server.py
web/backend/routers/operations.py
View file @
d57928c4
...
...
@@ -308,53 +308,12 @@ def list_operation_records(
operation
:
Optional
[
Literal
[
"backup"
,
"restore"
,
"clear"
,
"encrypt"
,
"decrypt"
]]
=
None
,
db
:
Session
=
Depends
(
get_session
),
):
if
operation
in
(
"backup"
,
"clear"
):
_import_file_backed_history
(
db
,
operation
)
query
=
db
.
query
(
OperationRecord
)
if
operation
:
query
=
query
.
filter
(
OperationRecord
.
operation
==
operation
)
return
[
row
.
to_dict
()
for
row
in
query
.
order_by
(
OperationRecord
.
created_at
.
desc
(),
OperationRecord
.
id
.
desc
()).
all
()]
def
_import_file_backed_history
(
db
:
Session
,
operation
:
str
)
->
None
:
"""将旧版已存在的备份文件补登记到历史表,仅首次扫描未登记文件。"""
known
=
{
row
[
0
]
for
row
in
db
.
query
(
OperationRecord
.
filename
)
.
filter
(
OperationRecord
.
operation
==
operation
,
OperationRecord
.
filename
!=
""
)
.
all
()
}
added
=
0
for
path
in
get_backup_dir
().
glob
(
"*.json"
):
if
path
.
name
in
known
:
continue
try
:
package
=
json
.
loads
(
path
.
read_text
(
encoding
=
"utf-8"
))
file_operation
=
package
.
get
(
"operation"
)
or
"backup"
if
file_operation
!=
operation
:
continue
source
=
package
.
get
(
"source"
)
or
{}
source_id
=
source
.
get
(
"id"
)
if
not
isinstance
(
source_id
,
int
)
or
not
db
.
get
(
DataSource
,
source_id
):
source_id
=
None
db
.
add
(
OperationRecord
(
operation
=
operation
,
source_id
=
source_id
,
source_name
=
str
(
source
.
get
(
"name"
)
or
""
),
database_name
=
str
(
source
.
get
(
"database"
)
or
source
.
get
(
"schema"
)
or
""
),
scope
=
str
(
source
.
get
(
"scope"
)
or
""
),
tables_json
=
json
.
dumps
([
item
.
get
(
"table_name"
)
for
item
in
package
.
get
(
"tables"
,
[])
if
item
.
get
(
"table_name"
)],
ensure_ascii
=
False
),
filename
=
path
.
name
,
execute_setting
=
"由已有备份文件补登记"
,
created_at
=
datetime
.
fromtimestamp
(
path
.
stat
().
st_mtime
),
))
added
+=
1
except
(
OSError
,
ValueError
,
TypeError
,
json
.
JSONDecodeError
):
continue
if
added
:
db
.
commit
()
@
router
.
post
(
"/records/import"
)
def
import_legacy_operation_records
(
operation
:
Literal
[
"backup"
,
"restore"
,
"clear"
,
"encrypt"
,
"decrypt"
],
...
...
web/src/views/ClearTablesView.vue
View file @
d57928c4
...
...
@@ -2,8 +2,8 @@
<div
class=
"clear-page"
>
<div
class=
"config-toolbar clear-toolbar"
>
<div
class=
"left"
>
<input
v-model=
"keyword"
class=
"text-input clear-search"
placeholder=
"备份文件名/数据库/数据表"
@
keyup.enter=
"
loadVersions
"
/>
<button
class=
"btn btn-primary"
@
click=
"
loadVersions"
>
查询
</button>
<input
v-model=
"keyword"
class=
"text-input clear-search"
placeholder=
"备份文件名/数据库/数据表"
@
keyup.enter=
"
onQuery
"
/>
<button
class=
"btn btn-primary"
@
click=
"
onQuery"
>
查询
</button><button
class=
"btn"
@
click=
"onReset"
>
重置
</button>
</div>
<div
class=
"right"
><button
class=
"btn btn-danger"
@
click=
"openCreate"
>
清空数据表
</button></div>
</div>
...
...
@@ -11,10 +11,10 @@
<div
class=
"q-card"
>
<div
class=
"table-scroll"
>
<table
class=
"config-list clear-list"
>
<thead><tr><th>
备份文件
</th><th>
数据源
</th><th>
数据库
/ Schema
</th><th>
已清空数据表
</th><th>
备份时间
</th><th>
操作
</th></tr></thead>
<thead><tr><th>
备份文件
</th><th>
数据源
</th><th>
数据库
</th><th>
已清空数据表
</th><th>
备份时间
</th><th>
操作
</th></tr></thead>
<tbody>
<tr
v-if=
"!records.length"
><td
colspan=
"6"
class=
"empty-cell"
>
暂无清表记录
</td></tr>
<tr
v-for=
"record in
r
ecords"
:key=
"record.id"
>
<tr
v-if=
"!records
Loading && !pagedRecords
.length"
><td
colspan=
"6"
class=
"empty-cell"
>
暂无清表记录
</td></tr>
<tr
v-for=
"record in
pagedR
ecords"
:key=
"record.id"
>
<td
class=
"filename"
>
{{
record
.
filename
}}
</td><td>
{{
record
.
source
?.
name
||
'
—
'
}}
</td>
<td>
{{
record
.
source
?.
database
||
record
.
source
?.
schema
||
'
—
'
}}
</td>
<td
class=
"tables"
>
{{
(
record
.
tables
||
[]).
join
(
'
、
'
)
||
'
—
'
}}
</td>
...
...
@@ -23,7 +23,7 @@
</tr>
</tbody>
</table>
</div>
</div>
<div
class=
"pagination-wrap"
v-if=
"filteredRecords.length"
><el-pagination
v-model:current-page=
"currentPage"
v-model:page-size=
"pageSize"
:page-sizes=
"[10, 15, 20, 30]"
:total=
"filteredRecords.length"
layout=
"total, sizes, prev, pager, next"
background
small
/></div>
</div>
</div>
...
...
@@ -39,15 +39,20 @@
</
template
>
<
script
setup
>
import
{
onMounted
,
reactive
,
ref
}
from
'
vue
'
import
{
computed
,
onMounted
,
reactive
,
ref
,
watch
}
from
'
vue
'
import
{
ElMessage
,
ElMessageBox
}
from
'
element-plus
'
import
{
listDataSources
}
from
'
@/api/dataSources
'
import
{
listSchemas
,
listTables
}
from
'
@/api/connections
'
import
{
clearTables
,
listOperationRecords
,
restoreClearedTables
}
from
'
@/api/backup
'
const
sources
=
ref
([]),
schemas
=
ref
([]),
tables
=
ref
([]),
records
=
ref
([])
const
keyword
=
ref
(
''
),
createOpen
=
ref
(
false
),
loading
=
ref
(
fals
e
),
clearing
=
ref
(
false
),
restoring
=
ref
(
''
)
const
keyword
=
ref
(
''
),
appliedKeyword
=
ref
(
''
),
currentPage
=
ref
(
1
),
pageSize
=
ref
(
10
),
createOpen
=
ref
(
false
),
loading
=
ref
(
false
),
recordsLoading
=
ref
(
tru
e
),
clearing
=
ref
(
false
),
restoring
=
ref
(
''
)
const
form
=
reactive
({
source
:
null
,
database
:
''
,
scope
:
'
database
'
,
tables
:
[]
})
const
filteredRecords
=
computed
(()
=>
{
const
q
=
appliedKeyword
.
value
.
trim
().
toLowerCase
()
return
q
?
records
.
value
.
filter
(
item
=>
[
item
.
filename
,
item
.
source_name
,
item
.
database
,
...(
item
.
tables
||
[])].
join
(
'
'
).
toLowerCase
().
includes
(
q
))
:
records
.
value
})
const
pagedRecords
=
computed
(()
=>
filteredRecords
.
value
.
slice
((
currentPage
.
value
-
1
)
*
pageSize
.
value
,
currentPage
.
value
*
pageSize
.
value
))
function
sourceForm
(
source
,
database
=
''
)
{
return
{
dbType
:
source
.
db_type
,
host
:
source
.
host
,
port
:
source
.
port
,
user
:
source
.
user
,
password
:
source
.
password
||
''
,
db
:
database
||
source
.
database
||
''
,
schema
:
database
||
source
.
schema
||
source
.
database
||
''
}
...
...
@@ -56,13 +61,12 @@ function formatDate(value) { return value ? new Date(value).toLocaleString() : '
function
resetForm
()
{
Object
.
assign
(
form
,
{
source
:
null
,
database
:
''
,
scope
:
'
database
'
,
tables
:
[]
});
schemas
.
value
=
[];
tables
.
value
=
[]
}
function
openCreate
()
{
resetForm
();
createOpen
.
value
=
true
}
async
function
loadVersions
()
{
recordsLoading
.
value
=
true
try
{
const
versions
=
await
listOperationRecords
(
'
clear
'
)
const
q
=
keyword
.
value
.
trim
().
toLowerCase
()
records
.
value
=
versions
.
filter
(
item
=>
!
q
||
[
item
.
filename
,
item
.
source_name
,
item
.
database
,
...(
item
.
tables
||
[])].
join
(
'
'
).
toLowerCase
().
includes
(
q
))
.
map
(
item
=>
({
...
item
,
source
:
{
name
:
item
.
source_name
,
database
:
item
.
database
}
}))
}
catch
(
error
)
{
ElMessage
.
error
(
`加载清表记录失败:
${
error
.
message
}
`
)
}
records
.
value
=
versions
.
map
(
item
=>
({
...
item
,
source
:
{
name
:
item
.
source_name
,
database
:
item
.
database
}
}))
currentPage
.
value
=
1
}
catch
(
error
)
{
ElMessage
.
error
(
`加载清表记录失败:
${
error
.
message
}
`
)
}
finally
{
recordsLoading
.
value
=
false
}
}
async
function
onSourceChange
()
{
form
.
database
=
''
;
form
.
tables
=
[];
schemas
.
value
=
[];
tables
.
value
=
[]
...
...
@@ -97,7 +101,13 @@ async function restore(record) {
restoring
.
value
=
record
.
filename
try
{
const
result
=
await
restoreClearedTables
({
filename
:
record
.
filename
,
source_id
:
source
.
id
,
database
,
schema
:
database
});
ElMessage
.
success
(
result
.
message
)
}
catch
(
error
)
{
ElMessage
.
error
(
error
.
message
)
}
finally
{
restoring
.
value
=
''
}
}
onMounted
(
async
()
=>
{
try
{
sources
.
value
=
await
listDataSources
()
}
catch
(
_
)
{
sources
.
value
=
[]
}
await
loadVersions
()
})
function
onQuery
()
{
appliedKeyword
.
value
=
keyword
.
value
;
currentPage
.
value
=
1
}
function
onReset
()
{
keyword
.
value
=
''
;
appliedKeyword
.
value
=
''
;
currentPage
.
value
=
1
}
watch
([
filteredRecords
,
()
=>
Math
.
max
(
1
,
Math
.
ceil
(
filteredRecords
.
value
.
length
/
pageSize
.
value
))],
()
=>
{
const
pageCount
=
Math
.
max
(
1
,
Math
.
ceil
(
filteredRecords
.
value
.
length
/
pageSize
.
value
))
if
(
currentPage
.
value
>
pageCount
)
currentPage
.
value
=
pageCount
})
onMounted
(
async
()
=>
{
await
loadVersions
();
try
{
sources
.
value
=
await
listDataSources
()
}
catch
(
_
)
{
sources
.
value
=
[]
}
})
</
script
>
<
style
scoped
>
...
...
web/src/views/DataSourceView.vue
View file @
d57928c4
...
...
@@ -14,7 +14,7 @@
<div
class=
"right"
><button
class=
"btn btn-success"
@
click=
"openAdd"
>
+ 新增数据源
</button></div>
</div>
<div
class=
"q-card"
>
<div
class=
"table-scroll"
><table
class=
"config-list"
><thead><tr><th>
名称
</th><th>
类型
</th><th>
地址
</th><th>
数据库
/ Schema
</th><th>
状态
</th><th>
最近测试
</th><th>
操作
</th></tr></thead>
<div
class=
"table-scroll"
><table
class=
"config-list"
><thead><tr><th>
名称
</th><th>
类型
</th><th>
地址
</th><th>
数据库
</th><th>
状态
</th><th>
最近测试
</th><th>
操作
</th></tr></thead>
<tbody><tr
v-if=
"!loading && !pagedRows.length"
><td
colspan=
"7"
class=
"empty-cell"
>
{{
filteredRows
.
length
?
'
当前页暂无数据
'
:
'
暂无数据源,请先新增
'
}}
</td></tr>
<tr
v-for=
"row in pagedRows"
:key=
"row.id"
><td><b>
{{
row
.
name
}}
</b></td><td>
{{
row
.
db_type
}}
</td><td>
{{
row
.
host
}}
:
{{
row
.
port
}}
</td><td>
{{
row
.
database
||
'
—
'
}}
<span
v-if=
"row.schema"
>
/
{{
row
.
schema
}}
</span></td><td><span
class=
"tag"
:class=
"row.status === '启用' ? 'tag-green' : 'tag-gray'"
>
{{
row
.
status
}}
</span></td><td><span
v-if=
"row.last_tested_at"
:class=
"row.last_test_ok ? 'ok-text' : 'error-text'"
>
{{
row
.
last_test_ok
?
'
成功
'
:
'
失败
'
}}
</span><span
v-else
>
—
</span></td><td><button
class=
"btn-text"
@
click=
"onTest(row)"
>
测试
</button><button
class=
"btn-text"
@
click=
"openEdit(row)"
>
编辑
</button><button
class=
"btn-text danger"
@
click=
"onDelete(row)"
>
删除
</button></td></tr></tbody>
</table></div>
...
...
web/src/views/TableDesignView.vue
View file @
d57928c4
This diff is collapsed.
Click to expand it.
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