Skip to content

Commit a449238

Browse files
committed
move code from src/ to v.mod subdirs
1 parent 57faba4 commit a449238

124 files changed

Lines changed: 1701 additions & 188 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
File renamed without changes.
File renamed without changes.
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub fn (mut app App) admin_settings(mut ctx Context) veb.Result {
1212
return ctx.redirect_to_index()
1313
}
1414

15-
return $veb.html()
15+
return $veb.html('../templates/admin/settings.html')
1616
}
1717

1818
@['/admin/settings'; post]
@@ -60,13 +60,13 @@ pub fn (mut app App) admin_users(mut ctx Context, page int) veb.Result {
6060
is_last_page := check_last_page(user_count, offset, admin_users_per_page)
6161
prev_page, next_page := generate_prev_next_pages(page)
6262

63-
return $veb.html()
63+
return $veb.html('../templates/admin/users.html')
6464
}
6565

6666
@['/admin/statistics']
6767
pub fn (mut app App) admin_statistics() veb.Result {
6868
if !ctx.is_admin() {
6969
return ctx.redirect_to_index()
7070
}
71-
return $veb.html()
71+
return $veb.html('../templates/admin/statistics.html')
7272
}
File renamed without changes.
File renamed without changes.

build.vsh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import net.http
22

3-
path := 'src/static/css/gitly.css'
3+
path := 'static/css/gitly.css'
44
if !exists(path) {
5-
ret := system('sassc src/static/css/gitly.scss > src/static/css/gitly.css')
5+
ret := system('sassc static/css/gitly.scss > static/css/gitly.css')
66
if ret != 0 {
77
http.download_file('https://gitly.org/css/gitly.css', path)!
88
println("No sassc detected on this system, gitly.css has been downloaded from gitly.org.")

ci/ci_routes.v

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
module main
2+
3+
import veb
4+
import json
5+
import net.http
6+
import os
7+
import time
8+
9+
struct CiStatusCallback {
10+
run_id string
11+
repo_id string
12+
commit_hash string
13+
branch string
14+
status string
15+
}
16+
17+
// POST /api/v1/ci/status - Callback endpoint for gitly_ci to report status updates
18+
@['/api/v1/ci/status'; post]
19+
pub fn (mut app App) handle_ci_status_callback() veb.Result {
20+
body := ctx.req.data
21+
callback := json.decode(CiStatusCallback, body) or {
22+
return ctx.json_error('Invalid request body')
23+
}
24+
25+
repo_id := callback.repo_id.int()
26+
ci_run_id := callback.run_id.int()
27+
status := ci_status_from_string(callback.status)
28+
29+
app.upsert_ci_status(repo_id, callback.commit_hash, callback.branch, status, ci_run_id) or {
30+
return ctx.json_error('Failed to update CI status: ${err}')
31+
}
32+
33+
return ctx.json_success('ok')
34+
}
35+
36+
// GET /:username/:repo_name/ci - CI runs list page
37+
@['/:username/:repo_name/ci']
38+
pub fn (mut app App) ci_runs(username string, repo_name string) veb.Result {
39+
repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() }
40+
41+
if !repo.is_public {
42+
if repo.user_id != ctx.user.id {
43+
return ctx.not_found()
44+
}
45+
}
46+
47+
// Check if .gitly-ci.yml exists in the repo
48+
has_ci_file := os.execute('git -C ${repo.git_dir} show ${repo.primary_branch}:.gitly-ci.yml').exit_code == 0
49+
50+
// Fetch runs from gitly_ci service for a complete list
51+
mut ci_runs := []CiRunListItem{}
52+
mut ci_service_error := false
53+
if app.config.ci_service_url != '' {
54+
runs_url := '${app.config.ci_service_url}/api/v1/runs/repo/${repo.id}'
55+
response := http.get(runs_url) or {
56+
ci_service_error = true
57+
http.Response{}
58+
}
59+
if !ci_service_error && response.status_code == 200 {
60+
runs_resp := json.decode(CiApiRunListResponse, response.body) or {
61+
CiApiRunListResponse{}
62+
}
63+
if runs_resp.success {
64+
for r in runs_resp.result {
65+
ci_runs << CiRunListItem{
66+
ci_run_id: r.id
67+
status: ci_status_from_string(r.status)
68+
commit_hash: r.commit_hash
69+
branch: r.branch
70+
created_at: r.created_at
71+
finished_at: r.finished_at
72+
}
73+
}
74+
}
75+
} else if !ci_service_error && response.status_code != 200 {
76+
ci_service_error = true
77+
}
78+
}
79+
80+
return $veb.html('../templates/ci_runs.html')
81+
}
82+
83+
// GET /:username/:repo_name/ci/:run_id_str - CI run detail page
84+
@['/:username/:repo_name/ci/:run_id_str']
85+
pub fn (mut app App) ci_run_detail(username string, repo_name string, run_id_str string) veb.Result {
86+
repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() }
87+
88+
if !repo.is_public {
89+
if repo.user_id != ctx.user.id {
90+
return ctx.not_found()
91+
}
92+
}
93+
94+
ci_run_id := run_id_str.int()
95+
96+
// Fetch run details from gitly_ci service
97+
if app.config.ci_service_url == '' {
98+
return ctx.not_found()
99+
}
100+
101+
ci_url := '${app.config.ci_service_url}/api/v1/runs/${ci_run_id}'
102+
response := http.get(ci_url) or {
103+
return ctx.not_found()
104+
}
105+
106+
if response.status_code != 200 {
107+
return ctx.not_found()
108+
}
109+
110+
ci_run_json := response.body
111+
112+
// Parse the response to display
113+
run_data := json.decode(CiApiRunResponse, ci_run_json) or {
114+
return ctx.not_found()
115+
}
116+
117+
ci_run := run_data.result
118+
119+
return $veb.html('../templates/ci_run_detail.html')
120+
}
121+
122+
// POST /:username/:repo_name/ci/:run_id_str/restart - Restart a CI run
123+
@['/:username/:repo_name/ci/:run_id_str/restart'; post]
124+
pub fn (mut app App) ci_restart_run(username string, repo_name string, run_id_str string) veb.Result {
125+
repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() }
126+
127+
// Only repo owner can restart
128+
if repo.user_id != ctx.user.id {
129+
return ctx.not_found()
130+
}
131+
132+
ci_run_id := run_id_str.int()
133+
134+
if app.config.ci_service_url == '' {
135+
return ctx.not_found()
136+
}
137+
138+
// Call gitly_ci restart API
139+
restart_url := '${app.config.ci_service_url}/api/v1/runs/${ci_run_id}/restart'
140+
response := http.post(restart_url, '') or {
141+
return ctx.not_found()
142+
}
143+
144+
if response.status_code != 200 {
145+
return ctx.not_found()
146+
}
147+
148+
result := json.decode(CiApiRunResponse, response.body) or {
149+
return ctx.not_found()
150+
}
151+
152+
if result.success {
153+
new_run := result.result
154+
// Update local CI status
155+
app.upsert_ci_status(repo.id, new_run.commit_hash, new_run.branch, .pending, new_run.id) or {}
156+
// Redirect to new run
157+
return ctx.redirect('/${username}/${repo_name}/ci/${new_run.id}')
158+
}
159+
160+
return ctx.redirect('/${username}/${repo_name}/ci/${ci_run_id}')
161+
}
162+
163+
// Structs for parsing gitly_ci API responses
164+
165+
struct CiApiRunListResponse {
166+
success bool
167+
result []CiRunListResponseItem
168+
}
169+
170+
struct CiRunListResponseItem {
171+
id int
172+
status string
173+
commit_hash string
174+
branch string
175+
created_at int
176+
finished_at int
177+
}
178+
179+
struct CiRunListItem {
180+
ci_run_id int
181+
status CiStatusEnum
182+
commit_hash string
183+
branch string
184+
created_at int
185+
finished_at int
186+
}
187+
188+
fn (ci &CiRunListItem) relative_time() string {
189+
if ci.finished_at > 0 {
190+
return time.unix(ci.finished_at).relative()
191+
}
192+
if ci.created_at > 0 {
193+
return time.unix(ci.created_at).relative()
194+
}
195+
return ''
196+
}
197+
198+
struct CiApiRunResponse {
199+
success bool
200+
result CiRunDetail
201+
}
202+
203+
struct CiRunDetail {
204+
id int
205+
status string
206+
commit_hash string
207+
branch string
208+
created_at int
209+
finished_at int
210+
jobs []CiJobDetail
211+
}
212+
213+
struct CiJobDetail {
214+
id int
215+
name string
216+
status string
217+
exit_code int
218+
started_at int
219+
finished_at int
220+
steps []CiStepDetail
221+
}
222+
223+
struct CiStepDetail {
224+
id int
225+
name string
226+
command string
227+
status string
228+
output string
229+
exit_code int
230+
}
231+
232+
fn (r &CiRunDetail) status_css_class() string {
233+
return match r.status {
234+
'success' { 'ci-success' }
235+
'failure' { 'ci-failure' }
236+
'running' { 'ci-running' }
237+
'cancelled' { 'ci-cancelled' }
238+
else { 'ci-pending' }
239+
}
240+
}
241+
242+
fn (r &CiRunDetail) created_relative() string {
243+
if r.created_at == 0 {
244+
return ''
245+
}
246+
return time.unix(r.created_at).relative()
247+
}
248+
249+
fn (r &CiRunDetail) duration() string {
250+
if r.finished_at == 0 || r.created_at == 0 {
251+
return 'running...'
252+
}
253+
d := r.finished_at - r.created_at
254+
if d < 60 {
255+
return '${d}s'
256+
}
257+
return '${d / 60}m ${d % 60}s'
258+
}
259+
260+
fn (j &CiJobDetail) status_css_class() string {
261+
return match j.status {
262+
'success' { 'ci-success' }
263+
'failure' { 'ci-failure' }
264+
'running' { 'ci-running' }
265+
'cancelled' { 'ci-cancelled' }
266+
else { 'ci-pending' }
267+
}
268+
}
269+
270+
fn (s &CiStepDetail) status_css_class() string {
271+
return match s.status {
272+
'success' { 'ci-success' }
273+
'failure' { 'ci-failure' }
274+
'running' { 'ci-running' }
275+
'cancelled' { 'ci-cancelled' }
276+
else { 'ci-pending' }
277+
}
278+
}
279+
280+
fn (s &CiStepDetail) status_icon() string {
281+
return match s.status {
282+
'success' { '✓' }
283+
'failure' { '✗' }
284+
'running' { '⟳' }
285+
'cancelled' { '⊘' }
286+
else { '○' }
287+
}
288+
}

0 commit comments

Comments
 (0)