mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-29 13:21:10 +00:00
Step logs removing API and Button (#3451)
Closes #3444 closes #1272 --- Admin: ![Screenshot 2024-02-27 1](https://github.com/woodpecker-ci/woodpecker/assets/127358482/285b2daa-9a37-4bd8-a11e-c4b7ced20e5a) ![Screenshot 2024-02-27 2](https://github.com/woodpecker-ci/woodpecker/assets/127358482/891ff2f8-71b5-4687-80a7-e3e4b1cb4e41) ![Screenshot 2024-02-27 3](https://github.com/woodpecker-ci/woodpecker/assets/127358482/362dbfe9-ac63-4be4-a4bb-f4e5140d54a5) User: ![Screenshot 2024-02-27 4](https://github.com/woodpecker-ci/woodpecker/assets/127358482/b2f3db6b-5ec3-4e06-a508-61dd07a69d60)
This commit is contained in:
parent
b4cd1da29c
commit
b8763a8f34
6 changed files with 144 additions and 1 deletions
|
@ -2012,6 +2012,53 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/repos/{repo_id}/logs/{number}/{stepId}": {
|
||||
"delete": {
|
||||
"produces": [
|
||||
"text/plain"
|
||||
],
|
||||
"tags": [
|
||||
"Pipeline logs"
|
||||
],
|
||||
"summary": "Deletes step log",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "the repository id",
|
||||
"name": "repo_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "the number of the pipeline",
|
||||
"name": "number",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "the step id",
|
||||
"name": "stepId",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{repo_id}/move": {
|
||||
"post": {
|
||||
"produces": [
|
||||
|
|
|
@ -224,6 +224,66 @@ func GetStepLogs(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, logs)
|
||||
}
|
||||
|
||||
// DeleteStepLogs
|
||||
//
|
||||
// @Summary Deletes step log
|
||||
// @Router /repos/{repo_id}/logs/{number}/{stepId} [delete]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
// @Tags Pipeline logs
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param repo_id path int true "the repository id"
|
||||
// @Param number path int true "the number of the pipeline"
|
||||
// @Param stepId path int true "the step id"
|
||||
func DeleteStepLogs(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
repo := session.Repo(c)
|
||||
|
||||
pipelineNumber, err := strconv.ParseInt(c.Params.ByName("number"), 10, 64)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
_pipeline, err := _store.GetPipelineNumber(repo, pipelineNumber)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
stepID, err := strconv.ParseInt(c.Params.ByName("stepId"), 10, 64)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
_step, err := _store.StepLoad(stepID)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if _step.PipelineID != _pipeline.ID {
|
||||
// make sure we cannot read arbitrary logs by id
|
||||
_ = c.AbortWithError(http.StatusBadRequest, fmt.Errorf("step with id %d is not part of repo %s", stepID, repo.FullName))
|
||||
return
|
||||
}
|
||||
|
||||
switch _step.State {
|
||||
case model.StatusRunning, model.StatusPending:
|
||||
c.String(http.StatusUnprocessableEntity, "Cannot delete logs for a pending or running step")
|
||||
return
|
||||
}
|
||||
|
||||
err = _store.LogDelete(_step)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// GetPipelineConfig
|
||||
//
|
||||
// @Summary Pipeline configuration
|
||||
|
|
|
@ -104,6 +104,7 @@ func apiRoutes(e *gin.RouterGroup) {
|
|||
repo.POST("/pipelines/:number/decline", session.MustPush, api.PostDecline)
|
||||
|
||||
repo.GET("/logs/:number/:stepId", api.GetStepLogs)
|
||||
repo.DELETE("/logs/:number/:stepId", session.MustPush, api.DeleteStepLogs)
|
||||
|
||||
// requires push permissions
|
||||
repo.DELETE("/logs/:number", session.MustPush, api.DeletePipelineLogs)
|
||||
|
|
|
@ -245,6 +245,8 @@
|
|||
"pipeline": "Pipeline #{pipelineId}",
|
||||
"log_title": "Step Logs",
|
||||
"log_download_error": "There was an error while downloading the log file",
|
||||
"log_delete_confirm": "Do you really want to delete logs of this step?",
|
||||
"log_delete_error": "There was an error while deleting the step logs",
|
||||
"actions": {
|
||||
"cancel": "Cancel",
|
||||
"restart": "Restart",
|
||||
|
@ -253,6 +255,7 @@
|
|||
"deploy": "Deploy",
|
||||
"restart_success": "Pipeline restarted",
|
||||
"log_download": "Download",
|
||||
"log_delete": "Delete",
|
||||
"log_auto_scroll": "Automatically scroll down",
|
||||
"log_auto_scroll_off": "Turn off automatic scrolling"
|
||||
},
|
||||
|
|
|
@ -20,6 +20,13 @@
|
|||
icon="download"
|
||||
@click="download"
|
||||
/>
|
||||
<IconButton
|
||||
v-if="step?.end_time !== undefined && hasLogs && hasPushPermission"
|
||||
:title="$t('repo.pipeline.actions.log_delete')"
|
||||
class="!hover:bg-white !hover:bg-opacity-10"
|
||||
icon="trash"
|
||||
@click="deleteLogs"
|
||||
/>
|
||||
<IconButton
|
||||
v-if="step?.end_time === undefined"
|
||||
:title="
|
||||
|
@ -111,7 +118,7 @@ import IconButton from '~/components/atomic/IconButton.vue';
|
|||
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
|
||||
import useApiClient from '~/compositions/useApiClient';
|
||||
import useNotifications from '~/compositions/useNotifications';
|
||||
import { Pipeline, Repo } from '~/lib/api/types';
|
||||
import { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
|
||||
import { findStep, isStepFinished, isStepRunning } from '~/utils/helpers';
|
||||
|
||||
type LogLine = {
|
||||
|
@ -136,6 +143,7 @@ const i18n = useI18n();
|
|||
const pipeline = toRef(props, 'pipeline');
|
||||
const stepId = toRef(props, 'stepId');
|
||||
const repo = inject<Ref<Repo>>('repo');
|
||||
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');
|
||||
const apiClient = useApiClient();
|
||||
const route = useRoute();
|
||||
|
||||
|
@ -160,6 +168,7 @@ ansiUp.value.use_classes = true;
|
|||
const logBuffer = ref<LogLine[]>([]);
|
||||
|
||||
const maxLineCount = 5000; // TODO(2653): set back to 500 and implement lazy-loading support
|
||||
const hasPushPermission = computed(() => repoPermissions?.value?.push);
|
||||
|
||||
function isSelected(line: LogLine): boolean {
|
||||
return route.hash === `#L${line.number}`;
|
||||
|
@ -297,6 +306,25 @@ async function loadLogs() {
|
|||
}
|
||||
}
|
||||
|
||||
async function deleteLogs() {
|
||||
if (!repo?.value || !pipeline.value || !step.value) {
|
||||
throw new Error('The repository, pipeline or step was undefined');
|
||||
}
|
||||
|
||||
// TODO use proper dialog (copy-pasted from web/src/components/secrets/SecretList.vue:deleteSecret)
|
||||
// eslint-disable-next-line no-alert, no-restricted-globals
|
||||
if (!confirm(i18n.t('repo.pipeline.log_delete_confirm'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await apiClient.deleteLogs(repo.value.id, pipeline.value.number, step.value.id);
|
||||
log.value = [];
|
||||
} catch (e) {
|
||||
notifications.notifyError(e, i18n.t('repo.pipeline.log_delete_error'));
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadLogs();
|
||||
});
|
||||
|
|
|
@ -136,6 +136,10 @@ export default class WoodpeckerClient extends ApiClient {
|
|||
return this._get(`/api/repos/${repoId}/logs/${pipeline}/${step}`) as Promise<PipelineLog[]>;
|
||||
}
|
||||
|
||||
deleteLogs(repoId: number, pipeline: number, step: number): Promise<unknown> {
|
||||
return this._delete(`/api/repos/${repoId}/logs/${pipeline}/${step}`);
|
||||
}
|
||||
|
||||
getSecretList(repoId: number, page: number): Promise<Secret[] | null> {
|
||||
return this._get(`/api/repos/${repoId}/secrets?page=${page}`) as Promise<Secret[] | null>;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue