mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-26 06:33:42 +09:00
1. Give menu items an inset pill highlight, text position and menu width are unchanged in most menus 1. Add arrow key, Enter, Space and Escape handling to all tippy menus 1. Mark the keyboard cursor of fomantic and tippy menus with the focus ring instead of the hover background 1. Mark the current dropdown item with the active color so it stands out from the hovered one 1. Stop navbar dropdown links from taking the navbar item hover color 1. Replace the actions job log options dropdown with the shared tippy menu, its hover highlight was invisible 1. Stop changing font weight on active and selected menu items, it resized rows while arrowing 1. Stretch the "All extensions" button in the diff file extension filter to the full menu width 1. Fix the actions run summary block covering the panel's rounded corners and indenting wrapped stats
621 lines
21 KiB
Vue
621 lines
21 KiB
Vue
<script setup lang="ts">
|
|
import SvgIcon from './SvgIcon.vue';
|
|
import ActionStatusIcon from './ActionStatusIcon.vue';
|
|
import {computed, onBeforeUnmount, ref, toRefs, watch} from 'vue';
|
|
import {resetActionFavicon, syncActionRunFavicon} from '../modules/favicon-status.ts';
|
|
import {POST, DELETE} from '../modules/fetch.ts';
|
|
import ActionRunSummaryView, {type ActionRunSummaryViewLocale} from './ActionRunSummaryView.vue';
|
|
import ActionRunJobView, {type ActionRunJobViewLocale} from './ActionRunJobView.vue';
|
|
import type {ActionsJob, ActionsRunAttempt} from '../modules/gitea-actions.ts';
|
|
import {buildJobsByParentJobID, createActionRunViewStore} from './ActionRunView.ts';
|
|
import {buildArtifactTooltipHtml} from './ActionRunArtifacts.ts';
|
|
import {trString} from '../modules/i18n.ts';
|
|
|
|
defineOptions({
|
|
name: 'RepoActionView',
|
|
});
|
|
|
|
type RepoActionViewLocale = ActionRunSummaryViewLocale & ActionRunJobViewLocale & {
|
|
approve: string,
|
|
cancel: string,
|
|
rerun: string,
|
|
rerun_all: string,
|
|
rerun_failed: string,
|
|
latest: string,
|
|
latestAttempt: string,
|
|
attempt: string,
|
|
summary: string,
|
|
allJobs: string,
|
|
jobSummaries: string,
|
|
expandCallerJobs: string,
|
|
collapseCallerJobs: string,
|
|
backToPullRequest: string,
|
|
backToWorkflow: string,
|
|
artifactExpired: string,
|
|
artifactExpiresAt: string,
|
|
artifactExpiredAt: string,
|
|
confirmDeleteArtifact: string,
|
|
workflowFile: string,
|
|
workflowFileNoPermission: string,
|
|
runDetails: string,
|
|
};
|
|
|
|
const props = defineProps<{
|
|
jobId: number;
|
|
actionsViewUrl: string;
|
|
locale: RepoActionViewLocale;
|
|
}>();
|
|
|
|
const locale = props.locale;
|
|
const store = createActionRunViewStore(props.actionsViewUrl);
|
|
const {currentRun: run, runArtifacts: artifacts} = toRefs(store.viewData);
|
|
const visibleJobSummaries = computed(() => {
|
|
const summaries = run.value.jobSummaries || [];
|
|
if (!props.jobId) return summaries;
|
|
return summaries.filter((summary) => summary.jobId === props.jobId);
|
|
});
|
|
|
|
type JobListItem = {
|
|
job: ActionsJob;
|
|
depth: number;
|
|
};
|
|
|
|
// Caller jobs default to collapsed. Membership in this set means "user has manually expanded this caller"
|
|
const expandedJobIDs = ref(new Set<number>());
|
|
|
|
function toggleExpandedJob(jobID: number) {
|
|
const next = new Set(expandedJobIDs.value);
|
|
if (next.has(jobID)) {
|
|
next.delete(jobID);
|
|
} else {
|
|
next.add(jobID);
|
|
}
|
|
expandedJobIDs.value = next;
|
|
}
|
|
|
|
// When a child job is currently selected, force-expand the chain of caller ancestors
|
|
const forcedExpandedJobIDs = computed(() => {
|
|
const expanded = new Set<number>();
|
|
if (!props.jobId) return expanded;
|
|
const jobsByID = new Map((run.value.jobs || []).map((job) => [job.id, job]));
|
|
let cur = jobsByID.get(props.jobId);
|
|
while (cur?.parentJobID) {
|
|
expanded.add(cur.parentJobID);
|
|
cur = jobsByID.get(cur.parentJobID);
|
|
}
|
|
return expanded;
|
|
});
|
|
|
|
function isJobCollapsed(jobID: number) {
|
|
return !expandedJobIDs.value.has(jobID) && !forcedExpandedJobIDs.value.has(jobID);
|
|
}
|
|
|
|
const visibleJobListItems = computed<JobListItem[]>(() => {
|
|
const jobs = [...(run.value.jobs || [])].sort((a, b) => a.id - b.id);
|
|
const childrenByParent = buildJobsByParentJobID(jobs);
|
|
|
|
const result: JobListItem[] = [];
|
|
const stack: Array<{job: ActionsJob; depth: number}> = [];
|
|
const top = childrenByParent.get(0) || [];
|
|
for (let i = top.length - 1; i >= 0; i--) stack.push({job: top[i], depth: 0});
|
|
|
|
while (stack.length > 0) {
|
|
const {job, depth} = stack.pop()!;
|
|
const children = childrenByParent.get(job.id) || [];
|
|
result.push({job, depth});
|
|
if (children.length > 0 && isJobCollapsed(job.id)) continue;
|
|
for (let i = children.length - 1; i >= 0; i--) stack.push({job: children[i], depth: depth + 1});
|
|
}
|
|
return result;
|
|
});
|
|
|
|
function formatAttemptTitle(attempt: ActionsRunAttempt) {
|
|
return attempt.latest ? `${locale.latestAttempt} #${attempt.attempt}` : `${locale.attempt} #${attempt.attempt}`;
|
|
}
|
|
|
|
function formatCurrentAttemptTitle(attempt: ActionsRunAttempt) {
|
|
return attempt.latest ? `${locale.latest} #${attempt.attempt}` : formatAttemptTitle(attempt);
|
|
}
|
|
|
|
const backLink = computed(() => {
|
|
if (run.value.pullRequest) {
|
|
return {href: run.value.pullRequest.link, prefix: locale.backToPullRequest, name: run.value.pullRequest.index};
|
|
}
|
|
if (run.value.workflowLink) {
|
|
return {href: run.value.workflowLink, prefix: locale.backToWorkflow, name: run.value.workflowID.replace(/\.(yml|yaml)$/i, '')};
|
|
}
|
|
return null;
|
|
});
|
|
|
|
function buildArtifactLink(name: string) {
|
|
const searchString = run.value.runAttempt > 0 ? `?attempt=${run.value.runAttempt}` : '';
|
|
return `${run.value.link}/artifacts/${encodeURIComponent(name)}${searchString}`;
|
|
}
|
|
|
|
function cancelRun() {
|
|
POST(`${run.value.link}/cancel`);
|
|
}
|
|
|
|
function approveRun() {
|
|
POST(`${run.value.link}/approve`);
|
|
}
|
|
|
|
async function deleteArtifact(name: string) {
|
|
if (!window.confirm(trString(locale.confirmDeleteArtifact, name))) return;
|
|
await DELETE(buildArtifactLink(name));
|
|
await store.forceReloadCurrentRun();
|
|
}
|
|
|
|
watch(() => run.value.status, (status) => {
|
|
syncActionRunFavicon(status);
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
resetActionFavicon();
|
|
});
|
|
</script>
|
|
<template>
|
|
<!-- make the view container full width to make users easier to read logs -->
|
|
<div class="ui fluid container">
|
|
<div class="action-view-header">
|
|
<a v-if="backLink" class="action-view-back" :href="backLink.href">
|
|
<SvgIcon name="octicon-arrow-left" :size="14"/>
|
|
<span>{{ backLink.prefix }} <span class="action-view-back-name">{{ backLink.name }}</span></span>
|
|
</a>
|
|
<div class="action-info-summary">
|
|
<div class="action-info-summary-title">
|
|
<ActionStatusIcon :locale-status="locale.status[run.status]" :status="run.status" :size="22" icon-variant="circle-fill"/>
|
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
<h2 class="action-info-summary-title-text" v-html="run.titleHTML"/>
|
|
<span class="action-info-summary-title-index">#{{ run.index }}</span>
|
|
</div>
|
|
<div class="flex-text-block tw-shrink-0 tw-flex-wrap">
|
|
<button class="ui basic small compact button primary" @click="approveRun()" v-if="run.canApprove">
|
|
{{ locale.approve }}
|
|
</button>
|
|
<button class="ui small compact button tw-text-red" @click="cancelRun()" v-else-if="run.canCancel">
|
|
{{ locale.cancel }}
|
|
</button>
|
|
<template v-if="run.canRerun">
|
|
<div v-if="run.canRerunFailed" class="ui small compact buttons">
|
|
<button class="ui basic small compact button link-action" :data-url="`${run.link}/rerun-failed`">
|
|
{{ locale.rerun_failed }}
|
|
</button>
|
|
<div class="ui basic small compact dropdown icon button">
|
|
<SvgIcon name="octicon-triangle-down" :size="14"/>
|
|
<div class="menu">
|
|
<div class="item link-action" :data-url="`${run.link}/rerun`">
|
|
{{ locale.rerun_all }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button v-else class="ui basic small compact button link-action" :data-url="`${run.link}/rerun`">
|
|
{{ locale.rerun_all }}
|
|
</button>
|
|
</template>
|
|
<div v-if="run.attempts.length > 1" class="ui dropdown basic small compact button">
|
|
<div class="flex-text-inline">
|
|
<SvgIcon name="octicon-history" :size="14"/>
|
|
<span>{{ formatCurrentAttemptTitle(run.attempts.find((attempt) => attempt.current)!) }}</span>
|
|
</div>
|
|
<SvgIcon name="octicon-triangle-down" :size="14" class="dropdown icon"/>
|
|
<div class="menu">
|
|
<a
|
|
v-for="attempt in run.attempts"
|
|
:key="attempt.attempt"
|
|
class="item tw-flex tw-flex-col tw-gap-2"
|
|
:class="attempt.current ? 'selected' : ''"
|
|
:href="attempt.link"
|
|
>
|
|
<div class="flex-text-block">
|
|
<SvgIcon name="octicon-check" :size="14" :class="{'tw-invisible': !Boolean(attempt.current)}"/>
|
|
<strong class="tw-text-sm gt-ellipsis">{{ formatAttemptTitle(attempt) }}</strong>
|
|
</div>
|
|
<div class="flex-text-block tw-pl-[20px]">
|
|
<span class="flex-text-inline tw-flex-shrink-0">
|
|
<ActionStatusIcon :locale-status="locale.status[attempt.status]" :status="attempt.status" :size="14" icon-variant="circle-fill"/>
|
|
<span>{{ locale.status[attempt.status] }}</span>
|
|
</span>
|
|
<span>•</span>
|
|
<relative-time :datetime="attempt.triggeredAt" prefix=""/>
|
|
<span>•</span>
|
|
<span class="gt-ellipsis">{{ attempt.triggerUserName }}</span>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="action-view-body">
|
|
<div class="action-view-left">
|
|
<!-- summary -->
|
|
<div class="flex-items-block action-view-sidebar-list">
|
|
<a class="item silenced" :href="run.viewLink" :class="!props.jobId ? 'selected' : ''">
|
|
<SvgIcon name="octicon-home"/>
|
|
<span class="gt-ellipsis">{{ locale.summary }}</span>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- jobs list -->
|
|
<div class="ui divider"/>
|
|
<div class="left-list-header">{{ locale.allJobs }}</div>
|
|
<div class="flex-items-block action-view-sidebar-list">
|
|
<template
|
|
v-for="item in visibleJobListItems"
|
|
:key="item.job.id"
|
|
>
|
|
<!-- Callers have no log page of their own; the whole row toggles expansion
|
|
(matches GitHub Actions, where caller rows are not navigation targets). -->
|
|
<button
|
|
v-if="item.job.isReusableCaller"
|
|
type="button"
|
|
class="item caller-row-toggle"
|
|
:class="{'selected': props.jobId === item.job.id}"
|
|
:style="{paddingLeft: `${10 + item.depth * 16}px`}"
|
|
@click="toggleExpandedJob(item.job.id)"
|
|
:title="isJobCollapsed(item.job.id) ? locale.expandCallerJobs : locale.collapseCallerJobs"
|
|
:aria-label="isJobCollapsed(item.job.id) ? locale.expandCallerJobs : locale.collapseCallerJobs"
|
|
:aria-expanded="!isJobCollapsed(item.job.id)"
|
|
>
|
|
<ActionStatusIcon :locale-status="locale.status[item.job.status]" :status="item.job.status" icon-variant="circle-fill"/>
|
|
<span class="tw-min-w-0 gt-ellipsis">{{ item.job.name }}</span>
|
|
<span class="job-duration">{{ item.job.duration }}</span>
|
|
<SvgIcon name="octicon-chevron-down" :size="14" class="job-brief-toggle-icon" :class="{'collapsed': isJobCollapsed(item.job.id)}"/>
|
|
</button>
|
|
<a
|
|
v-else
|
|
class="item silenced"
|
|
:class="{'selected': props.jobId === item.job.id}"
|
|
:style="{paddingLeft: `${10 + item.depth * 16}px`}"
|
|
:href="item.job.link"
|
|
>
|
|
<ActionStatusIcon :locale-status="locale.status[item.job.status]" :status="item.job.status" icon-variant="circle-fill"/>
|
|
<span class="tw-min-w-0 gt-ellipsis">{{ item.job.name }}</span>
|
|
<SvgIcon name="octicon-sync" role="button" :data-tooltip-content="locale.rerun" class="job-rerun-button tw-cursor-pointer link-action interact-fg" :data-url="`${run.link}/jobs/${item.job.id}/rerun`" v-if="item.job.canRerun"/>
|
|
<span class="job-duration">{{ item.job.duration }}</span>
|
|
</a>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- artifacts list -->
|
|
<template v-if="artifacts.length > 0">
|
|
<div class="ui divider"/>
|
|
<div class="left-list-header">{{ locale.artifactsTitle }} ({{ artifacts.length }})</div>
|
|
<div class="flex-items-block action-view-sidebar-list">
|
|
<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)"
|
|
:data-tooltip-content="buildArtifactTooltipHtml(artifact, locale.artifactExpiresAt)"
|
|
data-tooltip-render="html"
|
|
data-tooltip-placement="top-end"
|
|
>
|
|
<SvgIcon name="octicon-file" class="tw-text-text-light"/>
|
|
<span class="tw-flex-1 gt-ellipsis">{{ artifact.name }}</span>
|
|
</a>
|
|
<a v-if="run.canDeleteArtifact" class="silenced" @click="deleteArtifact(artifact.name)">
|
|
<SvgIcon name="octicon-trash"/>
|
|
</a>
|
|
</template>
|
|
<span
|
|
v-else class="flex-text-block tw-flex-1 tw-min-w-0 tw-text-text-light-2"
|
|
:data-tooltip-content="buildArtifactTooltipHtml(artifact, locale.artifactExpiredAt)"
|
|
data-tooltip-render="html"
|
|
data-tooltip-placement="top-end"
|
|
>
|
|
<SvgIcon name="octicon-file-removed"/>
|
|
<span class="tw-flex-1 gt-ellipsis">{{ artifact.name }}</span>
|
|
<span class="ui label tw-flex-shrink-0">{{ locale.artifactExpired }}</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- run details -->
|
|
<div class="ui divider"/>
|
|
<div class="left-list-header">{{ locale.runDetails }}</div>
|
|
<div class="flex-items-block action-view-sidebar-list">
|
|
<div class="item">
|
|
<a v-if="run.canViewWorkflowFile" class="flex-text-block silenced" :href="`${run.link}/workflow`">
|
|
<SvgIcon name="octicon-file-code" class="tw-text-text"/>
|
|
<span class="gt-ellipsis">{{ locale.workflowFile }}</span>
|
|
</a>
|
|
<span v-else class="flex-text-block silenced" :data-tooltip-content="locale.workflowFileNoPermission">
|
|
<SvgIcon name="octicon-lock" class="tw-text-text"/>
|
|
<span class="gt-ellipsis">{{ locale.workflowFileNoPermission }}</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="action-view-right">
|
|
<div class="action-view-right-panel">
|
|
<ActionRunSummaryView
|
|
v-if="!props.jobId"
|
|
:store="store"
|
|
:locale="locale"
|
|
:artifact-count="artifacts.length"
|
|
/>
|
|
<ActionRunJobView
|
|
v-else
|
|
:store="store"
|
|
:locale="locale"
|
|
:actions-view-url="props.actionsViewUrl"
|
|
:job-id="props.jobId"
|
|
/>
|
|
</div>
|
|
<div v-if="visibleJobSummaries.length" class="action-view-right-panel job-summary-section">
|
|
<div class="job-summary-section-header">
|
|
{{ locale.jobSummaries }}
|
|
</div>
|
|
<div class="job-summary-list">
|
|
<div v-for="s in visibleJobSummaries" :key="s.jobId" class="job-summary-item">
|
|
<div class="job-summary-header">
|
|
<strong class="gt-ellipsis">{{ s.jobName || `Job ${s.jobId}` }}</strong>
|
|
</div>
|
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
<div class="markup job-summary-body" v-html="s.summaryHTML"/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<style scoped>
|
|
.action-view-body {
|
|
padding-top: 12px;
|
|
padding-bottom: 12px;
|
|
display: flex;
|
|
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 */
|
|
|
|
.action-view-left {
|
|
width: 30%;
|
|
max-width: 400px;
|
|
position: sticky;
|
|
top: 12px;
|
|
|
|
/* about 12px top padding + 12px bottom padding + 37px footer height,
|
|
TODO: need to use JS to calculate the height for better scrolling experience*/
|
|
max-height: calc(100vh - 62px);
|
|
|
|
overflow-y: auto;
|
|
background: var(--color-body);
|
|
z-index: 2; /* above .job-info-header */
|
|
}
|
|
|
|
@media (max-width: 767.98px) {
|
|
.action-view-left {
|
|
position: static; /* can not sticky because multiple jobs would overlap into right view */
|
|
max-height: unset;
|
|
}
|
|
}
|
|
|
|
.left-list-header {
|
|
font-size: 13px;
|
|
font-weight: var(--font-weight-semibold);
|
|
color: var(--color-text-light-2);
|
|
}
|
|
|
|
.action-view-sidebar-list {
|
|
margin: var(--gap-block) 0;
|
|
}
|
|
|
|
.action-view-sidebar-list:first-child {
|
|
margin-top: 0;
|
|
}
|
|
|
|
.action-view-sidebar-list > .item {
|
|
padding: 6px 10px;
|
|
border-radius: var(--border-radius);
|
|
}
|
|
|
|
.action-view-sidebar-list > .item:hover {
|
|
background-color: var(--color-hover);
|
|
}
|
|
|
|
.action-view-sidebar-list > .item.selected {
|
|
font-weight: var(--font-weight-bold);
|
|
background-color: var(--color-active);
|
|
}
|
|
|
|
.caller-row-toggle {
|
|
width: 100%;
|
|
border: none;
|
|
background: transparent;
|
|
color: inherit;
|
|
line-height: inherit; /* buttons don't inherit line-height; match the <a> rows' row height */
|
|
cursor: pointer;
|
|
text-align: inherit;
|
|
}
|
|
|
|
.job-brief-toggle-icon {
|
|
flex-shrink: 0;
|
|
transition: transform 0.15s ease;
|
|
/* sit between name and duration; duration uses order:2 with margin-left:auto to float right */
|
|
order: 1;
|
|
}
|
|
|
|
.job-brief-toggle-icon:not(.collapsed) {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
/* push rerun/duration to the right edge; only one is visible at a time (hover swap),
|
|
the visible one absorbs the free space via auto-margin */
|
|
.action-view-sidebar-list > .item .job-rerun-button,
|
|
.action-view-sidebar-list > .item .job-duration {
|
|
order: 2;
|
|
margin-left: auto;
|
|
}
|
|
|
|
/* the re-run button replaces the duration on hover or job-link focus */
|
|
.action-view-sidebar-list > .item .job-rerun-button {
|
|
display: none;
|
|
}
|
|
|
|
.action-view-sidebar-list > .item:hover .job-rerun-button,
|
|
.action-view-sidebar-list > .item:focus .job-rerun-button {
|
|
display: inline-flex;
|
|
}
|
|
|
|
/* only swap out the duration when a re-run button exists to take its place */
|
|
.action-view-sidebar-list > .item:hover .job-rerun-button ~ .job-duration,
|
|
.action-view-sidebar-list > .item:focus .job-rerun-button ~ .job-duration {
|
|
display: none;
|
|
}
|
|
|
|
/* ================ */
|
|
/* action view right */
|
|
|
|
.action-view-right {
|
|
flex: 1;
|
|
color: var(--color-console-fg-subtle);
|
|
max-height: 100%;
|
|
width: 70%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.action-view-right-panel {
|
|
flex: 1; /* fill the right column so the summary graph stretches even without a job-summary section */
|
|
border: 1px solid var(--color-secondary);
|
|
border-radius: var(--border-radius);
|
|
background: var(--color-console-bg);
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 0;
|
|
}
|
|
|
|
/* begin fomantic button overrides */
|
|
|
|
.action-view-right-panel .ui.button,
|
|
.action-view-right-panel .ui.button:focus {
|
|
background: transparent;
|
|
color: var(--color-console-fg-subtle);
|
|
}
|
|
|
|
.action-view-right-panel .ui.button:hover {
|
|
background: var(--color-console-hover-bg);
|
|
color: var(--color-console-fg);
|
|
}
|
|
|
|
.action-view-right-panel .ui.button:active {
|
|
background: var(--color-console-active-bg);
|
|
color: var(--color-console-fg);
|
|
}
|
|
|
|
/* end fomantic button overrides */
|
|
|
|
@media (max-width: 767.98px) {
|
|
.action-view-body {
|
|
flex-direction: column;
|
|
}
|
|
.action-view-left, .action-view-right {
|
|
width: 100%;
|
|
}
|
|
.action-view-left {
|
|
max-width: none;
|
|
}
|
|
}
|
|
|
|
.job-summary-section {
|
|
flex: 0 0 auto; /* size to its content; let the summary panel keep the remaining height */
|
|
overflow: hidden;
|
|
}
|
|
|
|
.job-summary-section-header {
|
|
padding: 12px;
|
|
border-bottom: 1px solid var(--color-secondary);
|
|
background: var(--color-console-bg);
|
|
color: var(--color-console-fg);
|
|
font-weight: var(--font-weight-semibold);
|
|
}
|
|
|
|
.job-summary-list {
|
|
padding: 12px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.job-summary-item {
|
|
padding: 12px;
|
|
border-radius: var(--border-radius);
|
|
background: var(--color-console-hover-bg);
|
|
border: 1px solid var(--color-secondary);
|
|
}
|
|
|
|
.job-summary-header {
|
|
color: var(--color-console-fg);
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.job-summary-body {
|
|
color: var(--color-console-fg);
|
|
}
|
|
</style>
|