mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-02 10:54:25 +02:00
feat(actions): Add artifact preview in Actions run view (#36754)
Closes https://github.com/go-gitea/gitea/issues/33579. Adds browser previews for Actions artifacts. Selecting an artifact opens its file browser; selecting a file renders it in the same tab. The ZIP download remains available separately. Previews require sign-in and read access to the run. Text, image and PDF files are supported; rendered HTML and JavaScript run in a sandboxed frame and are labeled as automatically generated. The frame loads files from a signed link that expires after an hour, because its requests carry no session cookie. `[actions] ARTIFACT_PREVIEW_MAX_SIZE` limits total previewable artifact size (`0` disables previews; `-1` removes the limit); individual files also follow `[ui] MAX_DISPLAY_FILE_SIZE`. <img width="1803" height="913" alt="image" src="https://github.com/user-attachments/assets/a38fd704-2244-44fa-9181-c695ecbe0276" /> Docs: https://gitea.com/gitea/docs/pulls/533 --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Zettat123 <zettat123@gmail.com>
This commit is contained in:
22 files changed
+971
-68
No files matched your search
@@ -94,3 +94,61 @@
|
||||
order: 2;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.action-view-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
margin-top: 8px;
|
||||
min-height: 50px; /* reserve the back link and title height so the body does not shift when the run data arrives */
|
||||
}
|
||||
|
||||
.action-view-back {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
align-self: flex-start;
|
||||
gap: 4px;
|
||||
font-size: 13px;
|
||||
color: var(--color-text-light-1);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.action-view-back:hover {
|
||||
color: var(--color-text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.action-info-summary {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.action-info-summary-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.action-info-summary-title-text {
|
||||
font-size: 20px;
|
||||
margin: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.action-info-summary-title-index {
|
||||
font-size: 20px;
|
||||
color: var(--color-text-light-2);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.action-info-summary .ui.button {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ui.vertical.menu .artifact-preview-item {
|
||||
padding-left: calc(1em + var(--depth, 0) * 1.25em);
|
||||
}
|
||||
@@ -35,6 +35,7 @@ type RepoActionViewLocale = ActionRunSummaryViewLocale & ActionRunJobViewLocale
|
||||
artifactExpiresAt: string,
|
||||
artifactExpiredAt: string,
|
||||
confirmDeleteArtifact: string,
|
||||
downloadFile: string,
|
||||
workflowFile: string,
|
||||
workflowFileNoPermission: string,
|
||||
runDetails: string,
|
||||
@@ -287,8 +288,8 @@ onBeforeUnmount(() => {
|
||||
<div class="item" v-for="artifact in artifacts" :key="artifact.name">
|
||||
<template v-if="artifact.status !== 'expired'">
|
||||
<a
|
||||
class="tw-flex-1 tw-min-w-0 flex-text-block silenced" target="_blank"
|
||||
:href="buildArtifactLink(artifact.name)"
|
||||
class="tw-flex-1 tw-min-w-0 flex-text-block silenced"
|
||||
:href="artifact.previewLink"
|
||||
:data-tooltip-content="buildArtifactTooltipHtml(artifact, locale.artifactExpiresAt)"
|
||||
data-tooltip-render="html"
|
||||
data-tooltip-placement="top-end"
|
||||
@@ -296,6 +297,9 @@ onBeforeUnmount(() => {
|
||||
<SvgIcon name="octicon-file" class="tw-text-text-light"/>
|
||||
<span class="tw-flex-1 gt-ellipsis">{{ artifact.name }}</span>
|
||||
</a>
|
||||
<a download class="silenced" :href="buildArtifactLink(artifact.name)" :data-tooltip-content="locale.downloadFile">
|
||||
<SvgIcon name="octicon-download"/>
|
||||
</a>
|
||||
<a v-if="run.canDeleteArtifact" class="silenced" @click="deleteArtifact(artifact.name)">
|
||||
<SvgIcon name="octicon-trash"/>
|
||||
</a>
|
||||
@@ -373,62 +377,6 @@ onBeforeUnmount(() => {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* ================ */
|
||||
/* action view header */
|
||||
|
||||
.action-view-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
margin-top: 8px;
|
||||
min-height: 50px; /* reserve the back link and title height so the body does not shift when the run data arrives */
|
||||
}
|
||||
|
||||
.action-view-back {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
align-self: flex-start;
|
||||
gap: 4px;
|
||||
font-size: 13px;
|
||||
color: var(--color-text-light-1);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.action-view-back:hover {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.action-info-summary {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.action-info-summary-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.action-info-summary-title-text {
|
||||
font-size: 20px;
|
||||
margin: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.action-info-summary-title-index {
|
||||
font-size: 20px;
|
||||
color: var(--color-text-light-2);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.action-info-summary .ui.button {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ================ */
|
||||
/* action view left */
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ function initRepositoryActionsView() {
|
||||
showLogSeconds: el.getAttribute('data-locale-show-log-seconds'),
|
||||
showFullScreen: el.getAttribute('data-locale-show-full-screen'),
|
||||
downloadLogs: el.getAttribute('data-locale-download-logs'),
|
||||
downloadFile: el.getAttribute('data-locale-download-file'),
|
||||
copyOutput: el.getAttribute('data-locale-copy-output'),
|
||||
status: {
|
||||
unknown: el.getAttribute('data-locale-status-unknown'),
|
||||
|
||||
@@ -53,6 +53,10 @@ export function initExternalRenderIframe(iframe: HTMLIFrameElement) {
|
||||
if (cmd === 'resize') {
|
||||
iframe.style.height = `${e.data.iframeHeight}px`;
|
||||
} else if (cmd === 'open-link') {
|
||||
if (!navigator.userActivation.isActive) {
|
||||
console.error(`iframe attempted to open link without user activation: ${e.data.openLink}`);
|
||||
return;
|
||||
}
|
||||
navigateToIframeLink(e.data.openLink, e.data.anchorTarget);
|
||||
} else {
|
||||
throw new Error(`Unknown gitea iframe cmd: ${cmd}`);
|
||||
|
||||
@@ -88,4 +88,5 @@ export type ActionsArtifact = {
|
||||
size: number;
|
||||
status: ActionsArtifactStatus;
|
||||
expiresUnix: number;
|
||||
previewLink?: string;
|
||||
};
|
||||
Reference in new issue
Block a user