Add a new page to list repos and submissions

This commit is contained in:
nemunaire 2022-09-09 20:14:38 +02:00
parent 643e4d50bd
commit 6323d96b60
12 changed files with 167 additions and 33 deletions

View 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}

View File

@ -1,5 +1,4 @@
<script>
import { user } from '../stores/user';
import DateFormat from '../components/DateFormat.svelte';
import { getUserRendu } from '../lib/works';
@ -7,12 +6,13 @@
export { className as class };
export let work = null;
export let user = null;
</script>
{#if work.submission_url == '-'}
<!-- Display nothing -->
{: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>
{:then rendu}
{#if rendu === null}
@ -24,7 +24,7 @@
<i class="bi text-warning bi-exclamation-triangle-fill" title={error}></i>
{/await}
{:else}
{#await work.getSubmission()}
{#await work.getSubmission(user.id)}
<div class="spinner-border spinner-border-sm" role="status"></div>
{:then submission}
<i class="bi text-success bi-check-circle-fill" title={"Rendu effectué : " + JSON.stringify(submission)}></i>

View File

@ -68,7 +68,7 @@
{survey.title}
{#if survey.group}<span class="badge bg-secondary">{survey.group}</span>{/if}
{#if $user && survey.kind === "w" && survey.startAvailability() < Date.now()}
<SubmissionStatus work={survey} />
<SubmissionStatus work={survey} user={$user} />
{/if}
<SurveyBadge {survey} class="float-end" />
</td>

View File

@ -1,6 +1,7 @@
<script>
import { createEventDispatcher } from 'svelte';
import BuildState from '../components/BuildState.svelte';
import DateFormat from '../components/DateFormat.svelte';
import { WorkRepository, getRemoteRepositories, getRepositories } from '../lib/repositories';
import { ToastsStore } from '../stores/toasts';
@ -106,25 +107,7 @@
</div>
Dernière récupération&nbsp;: <strong>{#if repo.last_check}<DateFormat date={new Date(repo.last_check)} dateStyle="medium" timeStyle="medium" />{:else}-{/if}</strong>
{#if repo_pull_state}
{#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"}
<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}
<BuildState {repo_pull_state} on:show_logs={() => showlogs(repo)} />
{/if}
</div>
</div>

View File

@ -7,8 +7,11 @@ export async function getPromos() {
}
}
export async function getUsers() {
const res = await fetch('api/users', {headers: {'Accept': 'application/json'}})
export async function getUsers(promo, group) {
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) {
return await res.json();
} else {

View File

@ -91,8 +91,8 @@ export class Work {
}
}
async getSubmission() {
const res = await fetch(`api/works/${this.id}/submission`, {
async getSubmission(uid) {
const res = await fetch(uid?`api/users/${uid}/works/${this.id}/submission`:`api/works/${this.id}/submission`, {
headers: {'Accept': 'application/json'}
});
if (res.status == 200) {

View File

@ -17,7 +17,6 @@
import SurveyAdmin from '../../../components/SurveyAdmin.svelte';
import SurveyBadge from '../../../components/SurveyBadge.svelte';
import SurveyQuestions from '../../../components/SurveyQuestions.svelte';
import { getSurvey } from '../../../lib/surveys';
import { getQuestions } from '../../../lib/questions';
export let surveyP;

View File

@ -13,7 +13,6 @@
<script lang="ts">
import SurveyBadge from '../../../../components/SurveyBadge.svelte';
import SurveyQuestions from '../../../../components/SurveyQuestions.svelte';
import { getSurvey } from '../../../../lib/surveys';
import { getQuestions } from '../../../../lib/questions';
export let surveyP;

View File

@ -37,6 +37,7 @@
{#await work then w}
{#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>
<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}
<div class="d-flex align-items-center">
<h2>
@ -108,7 +109,7 @@
<dt>Rendu&nbsp;?</dt>
<dd>
{#if w.submission_url}
<SubmissionStatus work={w} />
<SubmissionStatus work={w} user={$user} />
{:else}
{#await my_submission}
<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>
{#if w.submission_url}
<strong>État du rendu&nbsp;:</strong> <SubmissionStatus work={w} />
<strong>État du rendu&nbsp;:</strong> <SubmissionStatus work={w} user={$user} />
{:else}
Rendu&nbsp;:
{#await my_submission}

View 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">&lt;</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}

View File

@ -63,7 +63,7 @@
{work.title}
{#if work.group}<span class="badge bg-secondary">{work.group}</span>{/if}
{#if work.startAvailability() < Date.now()}
<SubmissionStatus {work} />
<SubmissionStatus {work} user={$user} />
{/if}
<SurveyBadge survey={work} class="float-end" />
</td>

View File

@ -1,9 +1,11 @@
package main
import (
"fmt"
"log"
"net/http"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
@ -24,6 +26,7 @@ func declareAPIAuthUsersRoutes(router *gin.RouterGroup) {
declareAPIAuthSurveysRoutes(usersRoutes)
declareAPIAuthKeysRoutes(usersRoutes)
declareAPIAuthWorksRoutes(usersRoutes)
}
func declareAPIAdminUsersRoutes(router *gin.RouterGroup) {
@ -50,7 +53,31 @@ func declareAPIAdminUsersRoutes(router *gin.RouterGroup) {
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")