mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Add show timestamp/seconds and fullscreen options to action page (#24876)
Part of #24728 - The timestamp shows local time and is parsed by `date.toLocaleString`; - "show seconds" and "show timestamps" are mutually exclusive, and they can be both hidden. https://github.com/go-gitea/gitea/assets/17645053/89531e54-37b7-4400-a6a0-bb3cc69eb6f5 Update for timestamp format: <img width="306" alt="Screen Shot 2023-05-25 at 09 07 47" src="https://github.com/go-gitea/gitea/assets/17645053/2d99768d-d39c-4c9e-81a2-7bc7470399dd"> --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		@@ -125,6 +125,10 @@ concept_user_individual = Individual
 | 
			
		||||
concept_code_repository = Repository
 | 
			
		||||
concept_user_organization = Organization
 | 
			
		||||
 | 
			
		||||
show_timestamps = Show timestamps
 | 
			
		||||
show_log_seconds = Show seconds
 | 
			
		||||
show_full_screen = Show full screen
 | 
			
		||||
 | 
			
		||||
[aria]
 | 
			
		||||
navbar = Navigation Bar
 | 
			
		||||
footer = Footer
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,9 @@
 | 
			
		||||
		data-locale-status-skipped="{{.locale.Tr "actions.status.skipped"}}"
 | 
			
		||||
		data-locale-status-blocked="{{.locale.Tr "actions.status.blocked"}}"
 | 
			
		||||
		data-locale-artifacts-title="{{$.locale.Tr "artifacts"}}"
 | 
			
		||||
		data-locale-show-timestamps="{{.locale.Tr "show_timestamps"}}"
 | 
			
		||||
		data-locale-show-log-seconds="{{.locale.Tr "show_log_seconds"}}"
 | 
			
		||||
		data-locale-show-full-screen="{{.locale.Tr "show_full_screen"}}"
 | 
			
		||||
	>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -74,12 +74,15 @@
 | 
			
		||||
  --color-secondary-alpha-90: #dededee1;
 | 
			
		||||
  --color-secondary-hover: var(--color-secondary-dark-1);
 | 
			
		||||
  --color-secondary-active: var(--color-secondary-dark-2);
 | 
			
		||||
  /* console colors */
 | 
			
		||||
  --color-console-fg: #ffffff;
 | 
			
		||||
  --color-console-bg: #252a2f;
 | 
			
		||||
  --color-console-border: #ffffff16;
 | 
			
		||||
  /* console colors - used for actions console and console files */
 | 
			
		||||
  --color-console-fg: #eeeff2;
 | 
			
		||||
  --color-console-fg-subtle: #959cab;
 | 
			
		||||
  --color-console-bg: #262936;
 | 
			
		||||
  --color-console-border: #383c47;
 | 
			
		||||
  --color-console-hover-bg: #ffffff16;
 | 
			
		||||
  --color-console-active-bg: #353a3f;
 | 
			
		||||
  --color-console-active-bg: #454a57;
 | 
			
		||||
  --color-console-menu-bg: #383c47;
 | 
			
		||||
  --color-console-menu-border: #5c6374;
 | 
			
		||||
  /* named colors */
 | 
			
		||||
  --color-red: #db2828;
 | 
			
		||||
  --color-orange: #f2711c;
 | 
			
		||||
 
 | 
			
		||||
@@ -60,12 +60,15 @@
 | 
			
		||||
  --color-secondary-alpha-90: #525767e1;
 | 
			
		||||
  --color-secondary-hover: var(--color-secondary-light-1);
 | 
			
		||||
  --color-secondary-active: var(--color-secondary-light-2);
 | 
			
		||||
  /* console colors */
 | 
			
		||||
  --color-console-fg: #ffffff;
 | 
			
		||||
  /* console colors - used for actions console and console files */
 | 
			
		||||
  --color-console-fg: #eeeff2;
 | 
			
		||||
  --color-console-fg-subtle: #959cab;
 | 
			
		||||
  --color-console-bg: #262936;
 | 
			
		||||
  --color-console-border: #ffffff16;
 | 
			
		||||
  --color-console-border: #383c47;
 | 
			
		||||
  --color-console-hover-bg: #ffffff16;
 | 
			
		||||
  --color-console-active-bg: #383c47;
 | 
			
		||||
  --color-console-active-bg: #454a57;
 | 
			
		||||
  --color-console-menu-bg: #383c47;
 | 
			
		||||
  --color-console-menu-border: #5c6374;
 | 
			
		||||
  /* named colors */
 | 
			
		||||
  --color-red: #cc4848;
 | 
			
		||||
  --color-orange: #cc580c;
 | 
			
		||||
 
 | 
			
		||||
@@ -60,14 +60,38 @@
 | 
			
		||||
 | 
			
		||||
      <div class="action-view-right">
 | 
			
		||||
        <div class="job-info-header">
 | 
			
		||||
          <h3 class="job-info-header-title">
 | 
			
		||||
            {{ currentJob.title }}
 | 
			
		||||
          </h3>
 | 
			
		||||
          <p class="job-info-header-detail">
 | 
			
		||||
            {{ currentJob.detail }}
 | 
			
		||||
          </p>
 | 
			
		||||
          <div class="job-info-header-left">
 | 
			
		||||
            <h3 class="job-info-header-title">
 | 
			
		||||
              {{ currentJob.title }}
 | 
			
		||||
            </h3>
 | 
			
		||||
            <p class="job-info-header-detail">
 | 
			
		||||
              {{ currentJob.detail }}
 | 
			
		||||
            </p>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="job-info-header-right">
 | 
			
		||||
            <div class="ui top right pointing dropdown custom jump item" @click.stop="menuVisible = !menuVisible" @keyup.enter="menuVisible = !menuVisible">
 | 
			
		||||
              <button class="ui button button-ghost gt-p-3">
 | 
			
		||||
                <SvgIcon name="octicon-gear" :size="18"/>
 | 
			
		||||
              </button>
 | 
			
		||||
              <div class="menu transition action-job-menu" :class="{visible: menuVisible}" v-if="menuVisible" v-cloak>
 | 
			
		||||
                <a class="item" @click="toggleTimeDisplay('seconds')">
 | 
			
		||||
                  <i class="icon"><SvgIcon v-show="timeVisible['log-time-seconds']" name="octicon-check"/></i>
 | 
			
		||||
                  {{ locale.showLogSeconds }}
 | 
			
		||||
                </a>
 | 
			
		||||
                <a class="item" @click="toggleTimeDisplay('stamp')">
 | 
			
		||||
                  <i class="icon"><SvgIcon v-show="timeVisible['log-time-stamp']" name="octicon-check"/></i>
 | 
			
		||||
                  {{ locale.showTimeStamps }}
 | 
			
		||||
                </a>
 | 
			
		||||
                <div class="divider"/>
 | 
			
		||||
                <a class="item" @click="toggleFullScreen()">
 | 
			
		||||
                  <i class="icon"><SvgIcon v-show="isFullScreen" name="octicon-check"/></i>
 | 
			
		||||
                  {{ locale.showFullScreen }}
 | 
			
		||||
                </a>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="job-step-container">
 | 
			
		||||
        <div class="job-step-container" ref="steps">
 | 
			
		||||
          <div class="job-step-section" v-for="(jobStep, i) in currentJob.steps" :key="i">
 | 
			
		||||
            <div class="job-step-summary" @click.stop="toggleStepLogs(i)" :class="currentJobStepsStates[i].expanded ? 'selected' : ''">
 | 
			
		||||
              <!-- If the job is done and the job step log is loaded for the first time, show the loading icon
 | 
			
		||||
@@ -81,7 +105,8 @@
 | 
			
		||||
              <span class="step-summary-duration">{{ jobStep.duration }}</span>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- the log elements could be a lot, do not use v-if to destroy/reconstruct the DOM -->
 | 
			
		||||
            <!-- the log elements could be a lot, do not use v-if to destroy/reconstruct the DOM,
 | 
			
		||||
            use native DOM elements for "log line" to improve performance, Vue is not suitable for managing so many reactive elements. -->
 | 
			
		||||
            <div class="job-step-logs" ref="logs" v-show="currentJobStepsStates[i].expanded"/>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
@@ -95,6 +120,8 @@ import {SvgIcon} from '../svg.js';
 | 
			
		||||
import ActionRunStatus from './ActionRunStatus.vue';
 | 
			
		||||
import {createApp} from 'vue';
 | 
			
		||||
import AnsiToHTML from 'ansi-to-html';
 | 
			
		||||
import {toggleElem} from '../utils/dom.js';
 | 
			
		||||
import {getCurrentLocale} from '../utils.js';
 | 
			
		||||
 | 
			
		||||
const {csrfToken} = window.config;
 | 
			
		||||
 | 
			
		||||
@@ -121,6 +148,12 @@ const sfc = {
 | 
			
		||||
      currentJobStepsStates: [],
 | 
			
		||||
      artifacts: [],
 | 
			
		||||
      onHoverRerunIndex: -1,
 | 
			
		||||
      menuVisible: false,
 | 
			
		||||
      isFullScreen: false,
 | 
			
		||||
      timeVisible: {
 | 
			
		||||
        'log-time-stamp': false,
 | 
			
		||||
        'log-time-seconds': false,
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      // provided by backend
 | 
			
		||||
      run: {
 | 
			
		||||
@@ -173,6 +206,11 @@ const sfc = {
 | 
			
		||||
    // load job data and then auto-reload periodically
 | 
			
		||||
    this.loadJob();
 | 
			
		||||
    this.intervalID = setInterval(this.loadJob, 1000);
 | 
			
		||||
    document.body.addEventListener('click', this.closeDropdown);
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  beforeUnmount() {
 | 
			
		||||
    document.body.removeEventListener('click', this.closeDropdown);
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  unmounted() {
 | 
			
		||||
@@ -240,7 +278,7 @@ const sfc = {
 | 
			
		||||
      this.fetchPost(`${this.run.link}/approve`);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    createLogLine(line) {
 | 
			
		||||
    createLogLine(line, startTime) {
 | 
			
		||||
      const div = document.createElement('div');
 | 
			
		||||
      div.classList.add('job-log-line');
 | 
			
		||||
      div._jobLogTime = line.timestamp;
 | 
			
		||||
@@ -250,21 +288,35 @@ const sfc = {
 | 
			
		||||
      lineNumber.textContent = line.index;
 | 
			
		||||
      div.append(lineNumber);
 | 
			
		||||
 | 
			
		||||
      // TODO: Support displaying time optionally
 | 
			
		||||
      // for "Show timestamps"
 | 
			
		||||
      const logTimeStamp = document.createElement('span');
 | 
			
		||||
      logTimeStamp.className = 'log-time-stamp';
 | 
			
		||||
      const date = new Date(parseFloat(line.timestamp * 1000));
 | 
			
		||||
      const timeStamp = date.toLocaleString(getCurrentLocale(), {timeZoneName: 'short'});
 | 
			
		||||
      logTimeStamp.textContent = timeStamp;
 | 
			
		||||
      toggleElem(logTimeStamp, this.timeVisible['log-time-stamp']);
 | 
			
		||||
      // for "Show seconds"
 | 
			
		||||
      const logTimeSeconds = document.createElement('span');
 | 
			
		||||
      logTimeSeconds.className = 'log-time-seconds';
 | 
			
		||||
      const seconds = Math.floor(parseFloat(line.timestamp) - parseFloat(startTime));
 | 
			
		||||
      logTimeSeconds.textContent = `${seconds}s`;
 | 
			
		||||
      toggleElem(logTimeSeconds, this.timeVisible['log-time-seconds']);
 | 
			
		||||
 | 
			
		||||
      const logMessage = document.createElement('div');
 | 
			
		||||
      const logMessage = document.createElement('span');
 | 
			
		||||
      logMessage.className = 'log-msg';
 | 
			
		||||
      logMessage.innerHTML = ansiLogToHTML(line.message);
 | 
			
		||||
      div.append(logTimeStamp);
 | 
			
		||||
      div.append(logMessage);
 | 
			
		||||
      div.append(logTimeSeconds);
 | 
			
		||||
 | 
			
		||||
      return div;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    appendLogs(stepIndex, logLines) {
 | 
			
		||||
    appendLogs(stepIndex, logLines, startTime) {
 | 
			
		||||
      for (const line of logLines) {
 | 
			
		||||
        // TODO: group support: ##[group]GroupTitle , ##[endgroup]
 | 
			
		||||
        const el = this.getLogsContainer(stepIndex);
 | 
			
		||||
        el.append(this.createLogLine(line));
 | 
			
		||||
        el.append(this.createLogLine(line, startTime));
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
@@ -309,7 +361,7 @@ const sfc = {
 | 
			
		||||
        for (const logs of response.logs.stepsLog) {
 | 
			
		||||
          // save the cursor, it will be passed to backend next time
 | 
			
		||||
          this.currentJobStepsStates[logs.step].cursor = logs.cursor;
 | 
			
		||||
          this.appendLogs(logs.step, logs.lines);
 | 
			
		||||
          this.appendLogs(logs.step, logs.lines, logs.started);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.run.done && this.intervalID) {
 | 
			
		||||
@@ -335,6 +387,46 @@ const sfc = {
 | 
			
		||||
 | 
			
		||||
    isDone(status) {
 | 
			
		||||
      return ['success', 'skipped', 'failure', 'cancelled'].includes(status);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    closeDropdown() {
 | 
			
		||||
      if (this.menuVisible) this.menuVisible = false;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // show at most one of log seconds and timestamp (can be both invisible)
 | 
			
		||||
    toggleTimeDisplay(type) {
 | 
			
		||||
      const toToggleTypes = [];
 | 
			
		||||
      const other = type === 'seconds' ? 'stamp' : 'seconds';
 | 
			
		||||
      this.timeVisible[`log-time-${type}`] = !this.timeVisible[`log-time-${type}`];
 | 
			
		||||
      toToggleTypes.push(type);
 | 
			
		||||
      if (this.timeVisible[`log-time-${type}`] && this.timeVisible[`log-time-${other}`]) {
 | 
			
		||||
        this.timeVisible[`log-time-${other}`] = false;
 | 
			
		||||
        toToggleTypes.push(other);
 | 
			
		||||
      }
 | 
			
		||||
      for (const toToggle of toToggleTypes) {
 | 
			
		||||
        for (const el of this.$refs.steps.querySelectorAll(`.log-time-${toToggle}`)) {
 | 
			
		||||
          toggleElem(el, this.timeVisible[`log-time-${toToggle}`]);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    toggleFullScreen() {
 | 
			
		||||
      this.isFullScreen = !this.isFullScreen;
 | 
			
		||||
      const fullScreenEl = document.querySelector('.action-view-right');
 | 
			
		||||
      const outerEl = document.querySelector('.full.height');
 | 
			
		||||
      const actionBodyEl = document.querySelector('.action-view-body');
 | 
			
		||||
      const headerEl = document.querySelector('.ui.main.menu');
 | 
			
		||||
      const contentEl = document.querySelector('.page-content.repository');
 | 
			
		||||
      const footerEl = document.querySelector('.page-footer');
 | 
			
		||||
      toggleElem(headerEl, !this.isFullScreen);
 | 
			
		||||
      toggleElem(contentEl, !this.isFullScreen);
 | 
			
		||||
      toggleElem(footerEl, !this.isFullScreen);
 | 
			
		||||
      // move .action-view-right to new parent
 | 
			
		||||
      if (this.isFullScreen) {
 | 
			
		||||
        outerEl.append(fullScreenEl);
 | 
			
		||||
      } else {
 | 
			
		||||
        actionBodyEl.append(fullScreenEl);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
@@ -360,6 +452,9 @@ export function initRepositoryActionView() {
 | 
			
		||||
      rerun: el.getAttribute('data-locale-rerun'),
 | 
			
		||||
      artifactsTitle: el.getAttribute('data-locale-artifacts-title'),
 | 
			
		||||
      rerun_all: el.getAttribute('data-locale-rerun-all'),
 | 
			
		||||
      showTimeStamps: el.getAttribute('data-locale-show-timestamps'),
 | 
			
		||||
      showLogSeconds: el.getAttribute('data-locale-show-log-seconds'),
 | 
			
		||||
      showFullScreen: el.getAttribute('data-locale-show-full-screen'),
 | 
			
		||||
      status: {
 | 
			
		||||
        unknown: el.getAttribute('data-locale-status-unknown'),
 | 
			
		||||
        waiting: el.getAttribute('data-locale-status-waiting'),
 | 
			
		||||
@@ -369,7 +464,7 @@ export function initRepositoryActionView() {
 | 
			
		||||
        cancelled: el.getAttribute('data-locale-status-cancelled'),
 | 
			
		||||
        skipped: el.getAttribute('data-locale-status-skipped'),
 | 
			
		||||
        blocked: el.getAttribute('data-locale-status-blocked'),
 | 
			
		||||
      }
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  view.mount(el);
 | 
			
		||||
@@ -567,21 +662,95 @@ export function ansiLogToHTML(line) {
 | 
			
		||||
 | 
			
		||||
.action-view-right {
 | 
			
		||||
  flex: 1;
 | 
			
		||||
  color: var(--color-secondary-dark-3);
 | 
			
		||||
  color: var(--color-console-fg-subtle);
 | 
			
		||||
  max-height: 100%;
 | 
			
		||||
  width: 70%;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* begin fomantic button overrides */
 | 
			
		||||
 | 
			
		||||
.action-view-right .ui.button,
 | 
			
		||||
.action-view-right .ui.button:focus {
 | 
			
		||||
  background: transparent;
 | 
			
		||||
  color: var(--color-console-fg-subtle);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.action-view-right .ui.button:hover {
 | 
			
		||||
  background: var(--color-console-hover-bg);
 | 
			
		||||
  color: var(--color-console-fg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.action-view-right .ui.button:active {
 | 
			
		||||
  background: var(--color-console-active-bg);
 | 
			
		||||
  color: var(--color-console-fg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* end fomantic button overrides */
 | 
			
		||||
 | 
			
		||||
/* begin fomantic dropdown menu overrides */
 | 
			
		||||
 | 
			
		||||
.action-view-right .ui.dropdown .menu {
 | 
			
		||||
  background: var(--color-console-menu-bg);
 | 
			
		||||
  border-color: var(--color-console-menu-border);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.action-view-right .ui.dropdown .menu > .item {
 | 
			
		||||
  color: var(--color-console-fg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.action-view-right .ui.dropdown .menu > .item:hover {
 | 
			
		||||
  color: var(--color-console-fg);
 | 
			
		||||
  background: var(--color-console-hover-bg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.action-view-right .ui.dropdown .menu > .item:active {
 | 
			
		||||
  color: var(--color-console-fg);
 | 
			
		||||
  background: var(--color-console-active-bg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.action-view-right .ui.dropdown .menu > .divider {
 | 
			
		||||
  border-top-color: var(--color-console-menu-border);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.action-view-right .ui.pointing.dropdown > .menu:not(.hidden)::after {
 | 
			
		||||
  background: var(--color-console-menu-bg);
 | 
			
		||||
  box-shadow: -1px -1px 0 0 var(--color-console-menu-border);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* end fomantic dropdown menu overrides */
 | 
			
		||||
 | 
			
		||||
/* selectors here are intentionally exact to only match fullscreen */
 | 
			
		||||
 | 
			
		||||
.full.height > .action-view-right {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  border-radius: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.full.height > .action-view-right > .job-info-header {
 | 
			
		||||
  border-radius: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.full.height > .action-view-right > .job-step-container {
 | 
			
		||||
  height: calc(100% - 60px);
 | 
			
		||||
  border-radius: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.job-info-header {
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  padding: 0 12px;
 | 
			
		||||
  border-bottom: 1px solid var(--color-console-border);
 | 
			
		||||
  background-color: var(--color-console-bg);
 | 
			
		||||
  position: sticky;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  border-radius: var(--border-radius) var(--border-radius) 0 0;
 | 
			
		||||
  height: 60px;
 | 
			
		||||
  z-index: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.job-info-header .job-info-header-title {
 | 
			
		||||
@@ -591,7 +760,7 @@ export function ansiLogToHTML(line) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.job-info-header .job-info-header-detail {
 | 
			
		||||
  color: var(--color-secondary-dark-3);
 | 
			
		||||
  color: var(--color-console-fg-subtle);
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -676,14 +845,20 @@ export function ansiLogToHTML(line) {
 | 
			
		||||
  background-color: var(--color-console-hover-bg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.job-step-section .job-step-logs .job-log-line .line-num {
 | 
			
		||||
/* class names 'log-time-seconds' and 'log-time-stamp' are used in the method toggleTimeDisplay */
 | 
			
		||||
.job-log-line .line-num, .log-time-seconds {
 | 
			
		||||
  width: 48px;
 | 
			
		||||
  color: var(--color-grey-light);
 | 
			
		||||
  text-align: right;
 | 
			
		||||
  user-select: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.job-step-section .job-step-logs .job-log-line .log-time {
 | 
			
		||||
.log-time-seconds {
 | 
			
		||||
  padding-right: 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.job-log-line .log-time,
 | 
			
		||||
.log-time-stamp {
 | 
			
		||||
  color: var(--color-grey-light);
 | 
			
		||||
  margin-left: 10px;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@ import octiconEye from '../../public/img/svg/octicon-eye.svg';
 | 
			
		||||
import octiconFile from '../../public/img/svg/octicon-file.svg';
 | 
			
		||||
import octiconFileDirectoryFill from '../../public/img/svg/octicon-file-directory-fill.svg';
 | 
			
		||||
import octiconFilter from '../../public/img/svg/octicon-filter.svg';
 | 
			
		||||
import octiconGear from '../../public/img/svg/octicon-gear.svg';
 | 
			
		||||
import octiconGitBranch from '../../public/img/svg/octicon-git-branch.svg';
 | 
			
		||||
import octiconGitMerge from '../../public/img/svg/octicon-git-merge.svg';
 | 
			
		||||
import octiconGitPullRequest from '../../public/img/svg/octicon-git-pull-request.svg';
 | 
			
		||||
@@ -94,6 +95,7 @@ const svgs = {
 | 
			
		||||
  'octicon-file': octiconFile,
 | 
			
		||||
  'octicon-file-directory-fill': octiconFileDirectoryFill,
 | 
			
		||||
  'octicon-filter': octiconFilter,
 | 
			
		||||
  'octicon-gear': octiconGear,
 | 
			
		||||
  'octicon-git-branch': octiconGitBranch,
 | 
			
		||||
  'octicon-git-merge': octiconGitMerge,
 | 
			
		||||
  'octicon-git-pull-request': octiconGitPullRequest,
 | 
			
		||||
@@ -132,7 +134,7 @@ const svgs = {
 | 
			
		||||
  'octicon-tag': octiconTag,
 | 
			
		||||
  'octicon-triangle-down': octiconTriangleDown,
 | 
			
		||||
  'octicon-x': octiconX,
 | 
			
		||||
  'octicon-x-circle-fill': octiconXCircleFill
 | 
			
		||||
  'octicon-x-circle-fill': octiconXCircleFill,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// TODO: use a more general approach to access SVG icons.
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,7 @@ export function parseUrl(str) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// return current locale chosen by user
 | 
			
		||||
function getCurrentLocale() {
 | 
			
		||||
export function getCurrentLocale() {
 | 
			
		||||
  return document.documentElement.lang;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user