Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
program-updater
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
周田
program-updater
Commits
8de93954
You need to sign in or sign up before continuing.
Commit
8de93954
authored
Oct 30, 2026
by
周田
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' of
http://39.170.91.81:10020/zhoutian/program-updater
parents
9301122e
15fd3ece
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
38 additions
and
16 deletions
+38
-16
README.md
distribution-server/README.md
+1
-1
store.py
distribution-server/core/store.py
+2
-2
downloads.css
distribution-server/frontend/assets/downloads.css
+4
-1
downloads.js
distribution-server/frontend/assets/downloads.js
+17
-6
downloads.html
distribution-server/frontend/downloads.html
+6
-3
test_distribution.py
distribution-server/tests/test_distribution.py
+8
-3
No files found.
distribution-server/README.md
View file @
8de93954
...
...
@@ -15,7 +15,7 @@ distribution-server/
└─ settings.py
```
管理 API 以
`/api`
开头;创建应用时可填写应用说明。上传版本必须填写 Markdown 更新内容;服务器流式计算文件大小和 SHA-256,并记录文件名、格式、平台、架构、更新内容、上传时间和发布状态。支持 EXE、MSI、ZIP、7Z、DMG、PKG、DEB、RPM、AppImage 和 TAR.GZ。Markdown 说明支持标题、列表、粗体、链接和代码。
管理 API 以
`/api`
开头;创建应用时可填写应用说明
,公开下载页会将其标记为“程序介绍”,与各版本的更新内容分开展示
。上传版本必须填写 Markdown 更新内容;服务器流式计算文件大小和 SHA-256,并记录文件名、格式、平台、架构、更新内容、上传时间和发布状态。支持 EXE、MSI、ZIP、7Z、DMG、PKG、DEB、RPM、AppImage 和 TAR.GZ。Markdown 说明支持标题、列表、粗体、链接和代码。
版本说明也可以在管理页的程序包记录中单独编辑,无需重新上传程序包;保存后会同时显示在管理记录和公开下载页。
...
...
distribution-server/core/store.py
View file @
8de93954
...
...
@@ -304,8 +304,8 @@ class DistributionStore:
rows
=
connection
.
execute
(
"""
SELECT a.app_id, a.display_name, a.description, a.current_version,
r.filename, r.description
, r.platform, r.architecture
,
r.size, r.sha256,
r.filename, r.description
AS release_description
,
r.
platform, r.architecture, r.
size, r.sha256,
r.published_at
FROM applications AS a
JOIN releases AS r
...
...
distribution-server/frontend/assets/downloads.css
View file @
8de93954
...
...
@@ -33,6 +33,8 @@ h1 { margin: 8px 0 7px; font-size: clamp(24px, 4vw, 32px); letter-spacing: -.03e
.package-version
{
flex
:
0
0
auto
;
padding
:
4px
8px
;
border-radius
:
999px
;
color
:
#536077
;
background
:
#f0f2f6
;
font-size
:
11px
;
font-weight
:
700
;
}
.package-name
{
min-width
:
0
;
color
:
var
(
--ink
);
font-size
:
18px
;
font-weight
:
700
;
letter-spacing
:
-.02em
;
overflow-wrap
:
anywhere
;
}
.package-app-description
{
margin
:
10px
0
0
;
color
:
var
(
--muted
);
font-size
:
13px
;
line-height
:
1.6
;
overflow-wrap
:
anywhere
;
}
.package-app-description-title
,
.application-description-title
{
display
:
block
;
color
:
#536077
;
font-size
:
13px
;
}
.package-app-description-content
{
margin
:
4px
0
0
;
}
.package-badge
{
flex
:
0
0
auto
;
padding
:
5px
9px
;
border-radius
:
999px
;
color
:
#56647a
;
background
:
#f0f2f6
;
font-size
:
10px
;
font-weight
:
700
;
}
.package-badge-helper
{
color
:
#6652b8
;
background
:
#f0edff
;
}
.package-metadata
{
display
:
grid
;
grid-template-columns
:
repeat
(
3
,
minmax
(
0
,
1
fr
));
gap
:
12px
;
margin
:
18px
0
;
padding
:
13px
0
;
border-top
:
1px
solid
#eff1f5
;
border-bottom
:
1px
solid
#eff1f5
;
}
...
...
@@ -86,7 +88,8 @@ footer { font-size: 12px; }
.package-description
{
margin
:
14px
0
0
;
color
:
#68758a
;
font-size
:
13px
;
}
.package-description-title
{
display
:
block
;
margin-bottom
:
6px
;
color
:
#536077
;
font-size
:
13px
;
}
.package-description-content
{
padding
:
13px
15px
;
border-radius
:
8px
;
background
:
#f7f8fc
;
}
.application-description
{
margin-top
:
8px
!important
;
color
:
var
(
--muted
);
font-size
:
15px
;
line-height
:
1.7
;
overflow-wrap
:
anywhere
;
}
.application-description
{
margin-top
:
8px
;
color
:
var
(
--muted
);
font-size
:
15px
;
line-height
:
1.7
;
overflow-wrap
:
anywhere
;
}
.application-description
p
{
margin
:
4px
0
0
;
}
.markdown-body
{
color
:
#465268
;
font-size
:
14px
;
line-height
:
1.7
;
overflow-wrap
:
anywhere
;
}
.markdown-body
>
:first-child
{
margin-top
:
0
;
}
.markdown-body
>
:last-child
{
margin-bottom
:
0
;
}
...
...
distribution-server/frontend/assets/downloads.js
View file @
8de93954
...
...
@@ -39,7 +39,7 @@ function formatSize(bytes) {
function
packageCard
(
packageInfo
,
appName
,
{
compact
=
false
,
applicationDescription
=
""
}
=
{},
{
compact
=
false
,
applicationDescription
=
""
,
releaseNotes
=
""
}
=
{},
)
{
const
card
=
node
(
"article"
,
`package-card
${
compact
?
" package-card-compact"
:
""
}
`
);
const
heading
=
node
(
"div"
,
"package-heading"
);
...
...
@@ -56,12 +56,17 @@ function packageCard(
heading
.
append
(
identity
,
badge
);
card
.
append
(
heading
);
if
(
applicationDescription
)
{
card
.
append
(
node
(
"p"
,
"package-app-description"
,
applicationDescription
));
const
description
=
node
(
"section"
,
"package-app-description"
);
description
.
append
(
node
(
"strong"
,
"package-app-description-title"
,
"程序介绍"
),
node
(
"p"
,
"package-app-description-content"
,
applicationDescription
),
);
card
.
append
(
description
);
}
if
(
packageInfo
.
description
)
{
if
(
releaseNotes
)
{
const
notes
=
node
(
"section"
,
"package-description"
);
const
content
=
node
(
"div"
,
"package-description-content"
);
content
.
append
(
renderMarkdown
(
packageInfo
.
description
));
content
.
append
(
renderMarkdown
(
releaseNotes
));
notes
.
append
(
node
(
"strong"
,
"package-description-title"
,
"版本更新内容"
),
content
,
...
...
@@ -116,6 +121,7 @@ function renderLibrary(applications) {
},
application
.
display_name
,
{
compact
:
true
,
applicationDescription
:
application
.
description
,
releaseNotes
:
application
.
release_description
,
});
const
detail
=
node
(
"a"
,
"history-link"
,
"查看已发布版本"
);
detail
.
href
=
`/download/
${
encodeURIComponent
(
application
.
app_id
)}
`
;
...
...
@@ -129,11 +135,16 @@ function renderApplication(application) {
document
.
querySelector
(
"#page-description"
).
textContent
=
`
${
application
.
packages
.
length
}
个已发布程序包 · App ID:
${
application
.
app_id
}
`
;
const
applicationDescription
=
document
.
querySelector
(
"#application-description"
);
applicationDescription
.
textContent
=
application
.
description
||
""
;
document
.
querySelector
(
"#application-description-content"
).
textContent
=
application
.
description
||
""
;
applicationDescription
.
hidden
=
!
application
.
description
;
packageList
.
replaceChildren
();
for
(
const
packageInfo
of
application
.
packages
)
{
packageList
.
append
(
packageCard
(
packageInfo
,
application
.
display_name
));
packageList
.
append
(
packageCard
(
packageInfo
,
application
.
display_name
,
{
releaseNotes
:
packageInfo
.
description
,
}),
);
}
}
...
...
distribution-server/frontend/downloads.html
View file @
8de93954
...
...
@@ -5,8 +5,8 @@
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1"
>
<meta
name=
"color-scheme"
content=
"light"
>
<title>
程序下载中心
</title>
<link
rel=
"stylesheet"
href=
"/assets/downloads.css?v=2026102
8-1
"
>
<script
type=
"module"
src=
"/assets/downloads.js?v=2026102
8-1
"
></script>
<link
rel=
"stylesheet"
href=
"/assets/downloads.css?v=2026102
9-2
"
>
<script
type=
"module"
src=
"/assets/downloads.js?v=2026102
9-2
"
></script>
</head>
<body>
<main
class=
"page"
>
...
...
@@ -21,7 +21,10 @@
<div
class=
"eyebrow"
>
DOWNLOAD LIBRARY
</div>
<h1
id=
"page-title"
>
获取程序与更新包
</h1>
<p
id=
"page-description"
>
浏览已发布的程序版本,下载前请核对平台与架构。
</p>
<p
id=
"application-description"
class=
"application-description"
hidden
></p>
<section
id=
"application-description"
class=
"application-description"
hidden
>
<strong
class=
"application-description-title"
>
程序介绍
</strong>
<p
id=
"application-description-content"
></p>
</section>
</section>
<div
id=
"error"
class=
"error-card"
role=
"status"
hidden
></div>
<section
id=
"package-list"
class=
"package-list"
aria-live=
"polite"
></section>
...
...
distribution-server/tests/test_distribution.py
View file @
8de93954
...
...
@@ -142,6 +142,8 @@ class DistributionTests(unittest.TestCase):
public_library
=
self
.
client
.
get
(
"/api/public/apps"
)
self
.
assertEqual
(
public_application
.
json
()[
"description"
],
description
)
self
.
assertEqual
(
public_library
.
json
()[
0
][
"description"
],
description
)
self
.
assertEqual
(
public_library
.
json
()[
0
][
"release_description"
],
"Release notes."
)
self
.
assertEqual
(
public_application
.
json
()[
"packages"
][
0
][
"description"
],
"Release notes."
)
def
test_generic_package_is_recorded_and_published_for_public_download
(
self
)
->
None
:
self
.
create_application
()
...
...
@@ -388,6 +390,7 @@ class DistributionTests(unittest.TestCase):
self
.
assertIn
(
".shell"
,
stylesheet
.
text
)
self
.
assertEqual
(
download_script
.
status_code
,
200
)
self
.
assertIn
(
"renderLibrary"
,
download_script
.
text
)
self
.
assertIn
(
'"程序介绍"'
,
download_script
.
text
)
self
.
assertEqual
(
download_stylesheet
.
status_code
,
200
)
self
.
assertIn
(
".package-card"
,
download_stylesheet
.
text
)
self
.
assertEqual
(
markdown_script
.
status_code
,
200
)
...
...
@@ -402,8 +405,8 @@ class DistributionTests(unittest.TestCase):
"/download/my-program"
,
"/assets/app.js?v=20261028-1"
,
"/assets/app.css?v=20261028-1"
,
"/assets/downloads.js?v=2026102
8-1
"
,
"/assets/downloads.css?v=2026102
8-1
"
,
"/assets/downloads.js?v=2026102
9-2
"
,
"/assets/downloads.css?v=2026102
9-2
"
,
):
with
self
.
subTest
(
path
=
path
):
response
=
self
.
client
.
get
(
path
)
...
...
@@ -418,10 +421,12 @@ class DistributionTests(unittest.TestCase):
dashboard
=
self
.
client
.
get
(
"/"
)
downloads
=
self
.
client
.
get
(
"/downloads"
)
package_page
=
self
.
client
.
get
(
"/download/my-program"
)
for
asset
in
(
"app.js"
,
"app.css"
):
self
.
assertIn
(
f
"/assets/{asset}?v=20261028-1"
,
dashboard
.
text
)
for
asset
in
(
"downloads.js"
,
"downloads.css"
):
self
.
assertIn
(
f
"/assets/{asset}?v=20261028-1"
,
downloads
.
text
)
self
.
assertIn
(
f
"/assets/{asset}?v=20261029-2"
,
downloads
.
text
)
self
.
assertIn
(
"程序介绍"
,
package_page
.
text
)
def
test_public_download_surfaces_are_remote_but_management_is_not
(
self
)
->
None
:
self
.
create_application
()
...
...
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