Add a new page to list repos and submissions
This commit is contained in:
parent
643e4d50bd
commit
6323d96b60
27
ui/src/components/BuildState.svelte
Normal file
27
ui/src/components/BuildState.svelte
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<script>
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
export let repo_pull_state = null
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#await repo_pull_state}
|
||||||
|
<div class="spinner-grow spinner-grow-sm mx-1" role="status"></div>
|
||||||
|
{:then state}
|
||||||
|
{#if state.status == "pending" || state.status == "running"}
|
||||||
|
<div
|
||||||
|
class="spinner-grow spinner-grow-sm mx-1"
|
||||||
|
class:text-primary={state.status == "pending"}
|
||||||
|
class:text-warning={state.status == "running"}
|
||||||
|
title="La récupération est en cours"
|
||||||
|
role="status"
|
||||||
|
></div>
|
||||||
|
{:else if state.status == "success"}
|
||||||
|
<i class="bi bi-check-circle-fill text-success mx-1" title="La récupération s'est bien passée"></i>
|
||||||
|
{:else if state.status == "failure" || state.status == "killed"}
|
||||||
|
<i class="bi bi-exclamation-circle-fill text-danger mx-1" title="La récupération ne s'est pas bien passée" style="cursor: pointer" on:click={() => dispatch('show_logs')}></i>
|
||||||
|
{:else}
|
||||||
|
{state.status}
|
||||||
|
{/if}
|
||||||
|
{/await}
|
@ -1,5 +1,4 @@
|
|||||||
<script>
|
<script>
|
||||||
import { user } from '../stores/user';
|
|
||||||
import DateFormat from '../components/DateFormat.svelte';
|
import DateFormat from '../components/DateFormat.svelte';
|
||||||
import { getUserRendu } from '../lib/works';
|
import { getUserRendu } from '../lib/works';
|
||||||
|
|
||||||
@ -7,12 +6,13 @@
|
|||||||
export { className as class };
|
export { className as class };
|
||||||
|
|
||||||
export let work = null;
|
export let work = null;
|
||||||
|
export let user = null;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if work.submission_url == '-'}
|
{#if work.submission_url == '-'}
|
||||||
<!-- Display nothing -->
|
<!-- Display nothing -->
|
||||||
{:else if work.submission_url}
|
{:else if work.submission_url}
|
||||||
{#await getUserRendu(work.submission_url, $user)}
|
{#await getUserRendu(work.submission_url, user)}
|
||||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||||
{:then rendu}
|
{:then rendu}
|
||||||
{#if rendu === null}
|
{#if rendu === null}
|
||||||
@ -24,7 +24,7 @@
|
|||||||
<i class="bi text-warning bi-exclamation-triangle-fill" title={error}></i>
|
<i class="bi text-warning bi-exclamation-triangle-fill" title={error}></i>
|
||||||
{/await}
|
{/await}
|
||||||
{:else}
|
{:else}
|
||||||
{#await work.getSubmission()}
|
{#await work.getSubmission(user.id)}
|
||||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||||
{:then submission}
|
{:then submission}
|
||||||
<i class="bi text-success bi-check-circle-fill" title={"Rendu effectué : " + JSON.stringify(submission)}></i>
|
<i class="bi text-success bi-check-circle-fill" title={"Rendu effectué : " + JSON.stringify(submission)}></i>
|
||||||
|
@ -68,7 +68,7 @@
|
|||||||
{survey.title}
|
{survey.title}
|
||||||
{#if survey.group}<span class="badge bg-secondary">{survey.group}</span>{/if}
|
{#if survey.group}<span class="badge bg-secondary">{survey.group}</span>{/if}
|
||||||
{#if $user && survey.kind === "w" && survey.startAvailability() < Date.now()}
|
{#if $user && survey.kind === "w" && survey.startAvailability() < Date.now()}
|
||||||
<SubmissionStatus work={survey} />
|
<SubmissionStatus work={survey} user={$user} />
|
||||||
{/if}
|
{/if}
|
||||||
<SurveyBadge {survey} class="float-end" />
|
<SurveyBadge {survey} class="float-end" />
|
||||||
</td>
|
</td>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
import BuildState from '../components/BuildState.svelte';
|
||||||
import DateFormat from '../components/DateFormat.svelte';
|
import DateFormat from '../components/DateFormat.svelte';
|
||||||
import { WorkRepository, getRemoteRepositories, getRepositories } from '../lib/repositories';
|
import { WorkRepository, getRemoteRepositories, getRepositories } from '../lib/repositories';
|
||||||
import { ToastsStore } from '../stores/toasts';
|
import { ToastsStore } from '../stores/toasts';
|
||||||
@ -106,25 +107,7 @@
|
|||||||
</div>
|
</div>
|
||||||
Dernière récupération : <strong>{#if repo.last_check}<DateFormat date={new Date(repo.last_check)} dateStyle="medium" timeStyle="medium" />{:else}-{/if}</strong>
|
Dernière récupération : <strong>{#if repo.last_check}<DateFormat date={new Date(repo.last_check)} dateStyle="medium" timeStyle="medium" />{:else}-{/if}</strong>
|
||||||
{#if repo_pull_state}
|
{#if repo_pull_state}
|
||||||
{#await repo_pull_state}
|
<BuildState {repo_pull_state} on:show_logs={() => showlogs(repo)} />
|
||||||
<div class="spinner-grow spinner-grow-sm mx-1" role="status"></div>
|
|
||||||
{:then state}
|
|
||||||
{#if state.status == "pending" || state.status == "running"}
|
|
||||||
<div
|
|
||||||
class="spinner-grow spinner-grow-sm mx-1"
|
|
||||||
class:text-primary={state.status == "pending"}
|
|
||||||
class:text-warning={state.status == "running"}
|
|
||||||
title="La récupération est en cours"
|
|
||||||
role="status"
|
|
||||||
></div>
|
|
||||||
{:else if state.status == "success"}
|
|
||||||
<i class="bi bi-check-circle-fill text-success mx-1" title="La récupération s'est bien passée"></i>
|
|
||||||
{:else if state.status == "failure"}
|
|
||||||
<i class="bi bi-exclamation-circle-fill text-danger mx-1" title="La récupération ne s'est pas bien passée" style="cursor: pointer" on:click={() => showLogs(repo)}></i>
|
|
||||||
{:else}
|
|
||||||
{state.status}
|
|
||||||
{/if}
|
|
||||||
{/await}
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,8 +7,11 @@ export async function getPromos() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUsers() {
|
export async function getUsers(promo, group) {
|
||||||
const res = await fetch('api/users', {headers: {'Accept': 'application/json'}})
|
let url = '/api/users?';
|
||||||
|
if (promo) url += "promo=" + encodeURIComponent(promo) + "&";
|
||||||
|
if (group) url += "group=" + encodeURIComponent(group) + "&";
|
||||||
|
const res = await fetch(url, {headers: {'Accept': 'application/json'}})
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return await res.json();
|
return await res.json();
|
||||||
} else {
|
} else {
|
||||||
|
@ -91,8 +91,8 @@ export class Work {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSubmission() {
|
async getSubmission(uid) {
|
||||||
const res = await fetch(`api/works/${this.id}/submission`, {
|
const res = await fetch(uid?`api/users/${uid}/works/${this.id}/submission`:`api/works/${this.id}/submission`, {
|
||||||
headers: {'Accept': 'application/json'}
|
headers: {'Accept': 'application/json'}
|
||||||
});
|
});
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
import SurveyAdmin from '../../../components/SurveyAdmin.svelte';
|
import SurveyAdmin from '../../../components/SurveyAdmin.svelte';
|
||||||
import SurveyBadge from '../../../components/SurveyBadge.svelte';
|
import SurveyBadge from '../../../components/SurveyBadge.svelte';
|
||||||
import SurveyQuestions from '../../../components/SurveyQuestions.svelte';
|
import SurveyQuestions from '../../../components/SurveyQuestions.svelte';
|
||||||
import { getSurvey } from '../../../lib/surveys';
|
|
||||||
import { getQuestions } from '../../../lib/questions';
|
import { getQuestions } from '../../../lib/questions';
|
||||||
|
|
||||||
export let surveyP;
|
export let surveyP;
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SurveyBadge from '../../../../components/SurveyBadge.svelte';
|
import SurveyBadge from '../../../../components/SurveyBadge.svelte';
|
||||||
import SurveyQuestions from '../../../../components/SurveyQuestions.svelte';
|
import SurveyQuestions from '../../../../components/SurveyQuestions.svelte';
|
||||||
import { getSurvey } from '../../../../lib/surveys';
|
|
||||||
import { getQuestions } from '../../../../lib/questions';
|
import { getQuestions } from '../../../../lib/questions';
|
||||||
|
|
||||||
export let surveyP;
|
export let surveyP;
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
{#await work then w}
|
{#await work then w}
|
||||||
{#if $user && $user.is_admin}
|
{#if $user && $user.is_admin}
|
||||||
<button class="btn btn-primary ms-1 float-end" on:click={() => { edit = !edit; } } title="Éditer"><i class="bi bi-pencil"></i></button>
|
<button class="btn btn-primary ms-1 float-end" on:click={() => { edit = !edit; } } title="Éditer"><i class="bi bi-pencil"></i></button>
|
||||||
|
<a class="btn btn-success ms-1 float-end" href="works/{w.id}/rendus" title="Voir les rendus"><i class="bi bi-files"></i></a>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<h2>
|
<h2>
|
||||||
@ -108,7 +109,7 @@
|
|||||||
<dt>Rendu ?</dt>
|
<dt>Rendu ?</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{#if w.submission_url}
|
{#if w.submission_url}
|
||||||
<SubmissionStatus work={w} />
|
<SubmissionStatus work={w} user={$user} />
|
||||||
{:else}
|
{:else}
|
||||||
{#await my_submission}
|
{#await my_submission}
|
||||||
<div class="spinner-grow spinner-grow-sm mx-1" role="status"></div>
|
<div class="spinner-grow spinner-grow-sm mx-1" role="status"></div>
|
||||||
@ -162,7 +163,7 @@
|
|||||||
<div class="card-body d-flex justify-content-between">
|
<div class="card-body d-flex justify-content-between">
|
||||||
<div>
|
<div>
|
||||||
{#if w.submission_url}
|
{#if w.submission_url}
|
||||||
<strong>État du rendu :</strong> <SubmissionStatus work={w} />
|
<strong>État du rendu :</strong> <SubmissionStatus work={w} user={$user} />
|
||||||
{:else}
|
{:else}
|
||||||
Rendu :
|
Rendu :
|
||||||
{#await my_submission}
|
{#await my_submission}
|
||||||
|
95
ui/src/routes/works/[wid]/rendus.svelte
Normal file
95
ui/src/routes/works/[wid]/rendus.svelte
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<script context="module">
|
||||||
|
import { getWork } from '../../../lib/works';
|
||||||
|
|
||||||
|
export async function load({ params, stuff }) {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
work: stuff.work,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
|
import { user } from '../../../stores/user';
|
||||||
|
import BuildState from '../../../components/BuildState.svelte';
|
||||||
|
import DateFormat from '../../../components/DateFormat.svelte';
|
||||||
|
import SubmissionStatus from '../../../components/SubmissionStatus.svelte';
|
||||||
|
import SurveyBadge from '../../../components/SurveyBadge.svelte';
|
||||||
|
import { getRepositories } from '../../../lib/repositories';
|
||||||
|
import { getUsers } from '../../../lib/users';
|
||||||
|
|
||||||
|
export let work = null;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#await work then w}
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<h2>
|
||||||
|
<a href="works/{w.id}" class="text-muted" style="text-decoration: none"><</a>
|
||||||
|
{w.title}
|
||||||
|
</h2>
|
||||||
|
<SurveyBadge class="ms-2" survey={w} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#await getUsers(w.promo, w.group)}
|
||||||
|
{:then users}
|
||||||
|
<table class="w-100 mb-5">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Login</th>
|
||||||
|
<th>Rendu</th>
|
||||||
|
<th>Dépôts</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each users as user (user.id)}
|
||||||
|
<tr>
|
||||||
|
<td><a href="users/{user.login}">{user.login}</a></td>
|
||||||
|
<td>
|
||||||
|
<SubmissionStatus work={w} user={user} />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{#await getRepositories(w.id, user.id) then repos}
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
{#each repos as repo (repo.id)}
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<code class="text-truncate mx-1">
|
||||||
|
{repo.uri}
|
||||||
|
</code>
|
||||||
|
<div class="mx-1">
|
||||||
|
{#if repo.last_check}
|
||||||
|
<DateFormat date={new Date(repo.last_check)} dateStyle="medium" timeStyle="medium" />
|
||||||
|
<BuildState repo_pull_state={repo.getBuildState()} />
|
||||||
|
{:else}
|
||||||
|
-
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-primary mx-1"
|
||||||
|
title="Rafraîchir"
|
||||||
|
on:click={() => repo.retrieveWork()}
|
||||||
|
>
|
||||||
|
<i class="bi bi-arrow-clockwise"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
href="/api/users/{user.id}/works/{w.id}/download"
|
||||||
|
class="btn btn-sm btn-dark"
|
||||||
|
title="Télécharger la tarball du rendu"
|
||||||
|
>
|
||||||
|
<i class="bi bi-download"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{/await}
|
||||||
|
{/await}
|
@ -63,7 +63,7 @@
|
|||||||
{work.title}
|
{work.title}
|
||||||
{#if work.group}<span class="badge bg-secondary">{work.group}</span>{/if}
|
{#if work.group}<span class="badge bg-secondary">{work.group}</span>{/if}
|
||||||
{#if work.startAvailability() < Date.now()}
|
{#if work.startAvailability() < Date.now()}
|
||||||
<SubmissionStatus {work} />
|
<SubmissionStatus {work} user={$user} />
|
||||||
{/if}
|
{/if}
|
||||||
<SurveyBadge survey={work} class="float-end" />
|
<SurveyBadge survey={work} class="float-end" />
|
||||||
</td>
|
</td>
|
||||||
|
29
users.go
29
users.go
@ -1,9 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@ -24,6 +26,7 @@ func declareAPIAuthUsersRoutes(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
declareAPIAuthSurveysRoutes(usersRoutes)
|
declareAPIAuthSurveysRoutes(usersRoutes)
|
||||||
declareAPIAuthKeysRoutes(usersRoutes)
|
declareAPIAuthKeysRoutes(usersRoutes)
|
||||||
|
declareAPIAuthWorksRoutes(usersRoutes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func declareAPIAdminUsersRoutes(router *gin.RouterGroup) {
|
func declareAPIAdminUsersRoutes(router *gin.RouterGroup) {
|
||||||
@ -50,7 +53,31 @@ func declareAPIAdminUsersRoutes(router *gin.RouterGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, users)
|
var filterPromo *uint64
|
||||||
|
if c.Query("promo") != "" {
|
||||||
|
fPromo, err := strconv.ParseUint(c.Query("promo"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to parse promo: %s", err.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filterPromo = &fPromo
|
||||||
|
}
|
||||||
|
|
||||||
|
filterGroup := c.Query("group")
|
||||||
|
|
||||||
|
if filterPromo == nil && filterGroup == "" {
|
||||||
|
c.JSON(http.StatusOK, users)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret []User
|
||||||
|
for _, u := range users {
|
||||||
|
if (filterPromo == nil || uint(*filterPromo) == u.Promo) && (filterGroup == "" || strings.Contains(u.Groups, filterGroup)) {
|
||||||
|
ret = append(ret, u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, ret)
|
||||||
})
|
})
|
||||||
|
|
||||||
usersRoutes := router.Group("/users/:uid")
|
usersRoutes := router.Group("/users/:uid")
|
||||||
|
Reference in New Issue
Block a user