mirror of https://github.com/rancher/dashboard.git
Persistent deployment data (#6852)
* Persists Github deployment data * Moves GitHub Deployment card to own component * Fixes/Tweaks - Add indicator to app detail commits list to show deployed commit - Add l10n - hide github description field if there's no description (phantom icon) - add typing for app env var - Fix application of app env var (add/remove as appropriate) --------- Co-authored-by: Richard Cox <richard.cox@suse.com>
This commit is contained in:
parent
93381d9cf3
commit
00e389d6da
|
|
@ -0,0 +1,150 @@
|
|||
<script>
|
||||
import day from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
gitSource: { default: null, type: Object },
|
||||
commitPosition: {
|
||||
default: null,
|
||||
type: Object
|
||||
},
|
||||
gitDeployment: {
|
||||
default: null,
|
||||
type: Object
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
formatDate(date, from) {
|
||||
day.extend(relativeTime);
|
||||
|
||||
return from ? day(date).fromNow() : day(date).format('DD MMM YYYY');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="repo-info">
|
||||
<div class="repo-info-owner">
|
||||
<img
|
||||
:src="gitSource.owner.avatar_url"
|
||||
alt=""
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
ref="nofollow"
|
||||
target="_blank"
|
||||
:href="gitSource.owner.html_url"
|
||||
>{{ gitSource.owner.login }}</a>
|
||||
<span>/</span>
|
||||
<a
|
||||
ref="nofollow"
|
||||
target="_blank"
|
||||
:href="gitSource.html_url"
|
||||
>{{ gitSource.name }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="gitDeployment.deployedCommit"
|
||||
class="repo-info-revision"
|
||||
>
|
||||
<span>
|
||||
<i class="icon icon-fw icon-commit" />
|
||||
{{ gitDeployment.deployedCommit.short }}
|
||||
|
||||
</span>
|
||||
<span
|
||||
v-if="commitPosition"
|
||||
class="masthead-state badge-state"
|
||||
>
|
||||
<i class="icon icon-fw icon-commit" />
|
||||
{{ commitPosition.text }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="gitSource.description"
|
||||
class="repo-info-description"
|
||||
>
|
||||
<i class="icon icon-fw icon-comment" />
|
||||
<p>
|
||||
{{ gitSource.description }}
|
||||
</p>
|
||||
</div>
|
||||
<ul>
|
||||
<li>
|
||||
<span>{{ t('epinio.applications.detail.deployment.details.gitHub.created') }}</span>: {{ formatDate(gitSource.created_at) }}
|
||||
</li>
|
||||
<li>
|
||||
<span>{{ t('epinio.applications.detail.deployment.details.gitHub.updated') }}</span>: {{ formatDate(gitSource.updated_at, true) }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.application-card {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.repo-info {
|
||||
display: grid;
|
||||
grid-auto-columns: minmax(0, 1fr);
|
||||
grid-gap: 20px;
|
||||
font-size: 14px;
|
||||
|
||||
&-owner {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
a {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-right: 8px;
|
||||
align-self: center;
|
||||
width: 20px;
|
||||
border-radius: 5%;
|
||||
}
|
||||
|
||||
span {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&-description, &-revision{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: center;
|
||||
i {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
&-revision {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
li {
|
||||
font-size: 14px;
|
||||
opacity: 0.5;
|
||||
span {
|
||||
color: var(--default-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -1,15 +1,20 @@
|
|||
<script lang="ts">
|
||||
import day from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import Vue, { PropType } from 'vue';
|
||||
import Application from '../models/applications';
|
||||
import SimpleBox from '@shell/components/SimpleBox.vue';
|
||||
import ConsumptionGauge from '@shell/components/ConsumptionGauge.vue';
|
||||
import { EPINIO_PRODUCT_NAME, EPINIO_TYPES } from '../types';
|
||||
import { APPLICATION_ENV_VAR, EPINIO_APP_ENV_VAR_GITHUB, EPINIO_PRODUCT_NAME, EPINIO_TYPES } from '../types';
|
||||
import ResourceTable from '@shell/components/ResourceTable.vue';
|
||||
import PlusMinus from '@shell/components/form/PlusMinus.vue';
|
||||
import { epinioExceptionToErrorsArray } from '../utils/errors';
|
||||
import ApplicationCard from '@shell/components/cards/ApplicationCard.vue';
|
||||
import Tabbed from '@shell/components/Tabbed/index.vue';
|
||||
import Tab from '@shell/components/Tabbed/Tab.vue';
|
||||
import SortableTable from '@shell/components/SortableTable/index.vue';
|
||||
import AppGitHubDeployment from '../components/application/AppGitHubDeployment.vue';
|
||||
import Link from '@shell/components/formatter/Link.vue';
|
||||
|
||||
interface Data {
|
||||
}
|
||||
|
|
@ -19,11 +24,14 @@ export default Vue.extend<Data, any, any, any>({
|
|||
components: {
|
||||
SimpleBox,
|
||||
ConsumptionGauge,
|
||||
SortableTable,
|
||||
ResourceTable,
|
||||
PlusMinus,
|
||||
ApplicationCard,
|
||||
AppGitHubDeployment,
|
||||
Tabbed,
|
||||
Tab,
|
||||
Link
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
|
|
@ -42,8 +50,8 @@ export default Vue.extend<Data, any, any, any>({
|
|||
fetch() {
|
||||
this.$store.dispatch(`epinio/findAll`, { type: EPINIO_TYPES.SERVICE_INSTANCE });
|
||||
this.$store.dispatch(`epinio/findAll`, { type: EPINIO_TYPES.CONFIGURATION });
|
||||
this.fetchRepoDetails();
|
||||
},
|
||||
|
||||
data() {
|
||||
const appInstanceSchema = this.$store.getters[`${ EPINIO_PRODUCT_NAME }/schemaFor`](EPINIO_TYPES.APP_INSTANCE);
|
||||
const servicesSchema = this.$store.getters[`${ EPINIO_PRODUCT_NAME }/schemaFor`](EPINIO_TYPES.SERVICE_INSTANCE);
|
||||
|
|
@ -52,7 +60,12 @@ export default Vue.extend<Data, any, any, any>({
|
|||
const configsHeaders: [] = this.$store.getters['type-map/headersFor'](configsSchema);
|
||||
|
||||
return {
|
||||
saving: false,
|
||||
saving: false,
|
||||
gitSource: null,
|
||||
gitDeployment: {
|
||||
deployedCommit: null,
|
||||
commitsArray: null,
|
||||
},
|
||||
appInstance: {
|
||||
schema: appInstanceSchema,
|
||||
headers: this.$store.getters['type-map/headersFor'](appInstanceSchema),
|
||||
|
|
@ -64,10 +77,37 @@ export default Vue.extend<Data, any, any, any>({
|
|||
configs: {
|
||||
schema: configsSchema,
|
||||
headers: configsHeaders.filter((h: any) => !['namespace', 'boundApps', 'service'].includes(h.name)),
|
||||
}
|
||||
},
|
||||
commitsTableHeaders: [{
|
||||
name: 'sha',
|
||||
label: this.t('githubPicker.tableHeaders.sha.label'),
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
name: 'author',
|
||||
label: this.t('githubPicker.tableHeaders.author.label'),
|
||||
width: 190,
|
||||
value: 'author.login',
|
||||
sort: 'author.login',
|
||||
},
|
||||
{
|
||||
name: 'message',
|
||||
label: this.t('githubPicker.tableHeaders.message.label'),
|
||||
value: 'message',
|
||||
sort: 'message',
|
||||
},
|
||||
{
|
||||
name: 'date',
|
||||
width: 220,
|
||||
label: this.t('githubPicker.tableHeaders.date.label'),
|
||||
value: 'date',
|
||||
sort: ['date:desc'],
|
||||
formatter: 'Date',
|
||||
defaultSort: true,
|
||||
},
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
async updateInstances(newInstances: number) {
|
||||
this.$set(this, 'saving', true);
|
||||
|
|
@ -84,19 +124,112 @@ export default Vue.extend<Data, any, any, any>({
|
|||
const matchGithub = str.match('^(https|git)(:\/\/|@)([^\/:]+)[\/:]([^\/:]+)\/(.+)(.git)*$');
|
||||
|
||||
return `${ matchGithub?.[4] }/${ matchGithub?.[5] }`;
|
||||
},
|
||||
async fetchRepoDetails() {
|
||||
const envs = this.value?.envDetails;
|
||||
|
||||
if (envs[APPLICATION_ENV_VAR] ) {
|
||||
const { usernameOrOrg, repo } = JSON.parse(envs[APPLICATION_ENV_VAR]) as EPINIO_APP_ENV_VAR_GITHUB;
|
||||
const res = await this.$store.dispatch('github/fetchRepoDetails', { username: usernameOrOrg, repo });
|
||||
|
||||
const {
|
||||
// eslint-disable-next-line camelcase
|
||||
owner, description, created_at, updated_at, html_url, name
|
||||
} = res;
|
||||
|
||||
this.gitSource = {
|
||||
owner,
|
||||
description,
|
||||
created_at,
|
||||
updated_at,
|
||||
html_url,
|
||||
name
|
||||
};
|
||||
|
||||
const commit = this.value.sourceInfo?.details.filter((ele: { label: string; }) => ele.label === 'Revision')[0]?.value;
|
||||
|
||||
if (commit) {
|
||||
this.gitDeployment.deployedCommit = {
|
||||
short: commit?.slice(0, 7),
|
||||
long: commit
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
await this.fetchCommits();
|
||||
},
|
||||
async fetchCommits() {
|
||||
const envs = this.value?.envDetails;
|
||||
|
||||
if (!envs[APPLICATION_ENV_VAR]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { usernameOrOrg, repo, branch } = JSON.parse(envs[APPLICATION_ENV_VAR]);
|
||||
|
||||
this.gitDeployment.commitsArray = await this.$store.dispatch('github/fetchCommits', {
|
||||
username: usernameOrOrg, repo, branch
|
||||
});
|
||||
},
|
||||
formatDate(date: string, from: boolean) {
|
||||
day.extend(relativeTime);
|
||||
|
||||
return from ? day(date).fromNow() : day(date).format('DD MMM YYYY');
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
prepareCommitArray() {
|
||||
if (this.gitDeployment.commitsArray.length) {
|
||||
return this.gitDeployment.commitsArray.reduce((acc: any, cur: any) => {
|
||||
acc.push({
|
||||
message: cur.commit.message,
|
||||
html_url: cur.html_url,
|
||||
sha: cur.sha.slice(0, 7),
|
||||
commitId: cur?.sha,
|
||||
author: cur.author,
|
||||
isChecked: false,
|
||||
date: cur?.commit.committer.date
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
sourceIcon(): string {
|
||||
return this.value.sourceInfo?.icon || 'icon-epinio';
|
||||
},
|
||||
commitPosition() {
|
||||
if (!this.gitDeployment?.commitsArray && !this.gitDeployment.deployedCommit) {
|
||||
return;
|
||||
}
|
||||
|
||||
let idx = null;
|
||||
|
||||
if (this.gitDeployment.commitsArray) {
|
||||
this.gitDeployment.commitsArray.map((ele: { sha: any; }, i: number) => {
|
||||
if (ele.sha === this.gitDeployment.deployedCommit.long) {
|
||||
idx = i - 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!idx) {
|
||||
return idx;
|
||||
}
|
||||
|
||||
return {
|
||||
text: ( idx - 1) >= 0 ? `${ idx } ${ this.t('epinio.applications.gitSource.behindCommits') }` : this.t('epinio.applications.gitSource.latestCommit'),
|
||||
position: idx
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="content">
|
||||
<div class="application-details">
|
||||
<ApplicationCard>
|
||||
<!-- Icon slot -->
|
||||
|
|
@ -159,90 +292,196 @@ export default Vue.extend<Data, any, any, any>({
|
|||
v-if="value.deployment"
|
||||
class="deployment"
|
||||
>
|
||||
<div class="simple-box-row app-instances">
|
||||
<SimpleBox>
|
||||
<ConsumptionGauge
|
||||
:resource-name="t('epinio.applications.detail.deployment.instances')"
|
||||
:capacity="value.desiredInstances"
|
||||
:used="value.readyInstances"
|
||||
:used-as-resource-name="true"
|
||||
:color-stops="{ 70: '--success', 30: '--warning', 0: '--error' }"
|
||||
/>
|
||||
<div class="scale-instances">
|
||||
<PlusMinus
|
||||
class="mt-15 mb-10"
|
||||
:value="value.desiredInstances"
|
||||
:disabled="saving"
|
||||
@minus="updateInstances(value.desiredInstances - 1)"
|
||||
@plus="updateInstances(value.desiredInstances + 1)"
|
||||
/>
|
||||
</div>
|
||||
</SimpleBox>
|
||||
<!-- Source information -->
|
||||
<SimpleBox v-if="value.sourceInfo">
|
||||
<div class="deployment__origin__row">
|
||||
<h4>Deployment Details</h4>
|
||||
</div>
|
||||
<div class="deployment__origin__list">
|
||||
<ul>
|
||||
<li>
|
||||
<h4>Origin</h4>
|
||||
<span v-if="value.sourceInfo.label === 'Git'">
|
||||
<i class="icon icon-fw icon-github" />
|
||||
{{ value.sourceInfo.label }}
|
||||
</span>
|
||||
<span v-else>{{ value.sourceInfo.label }}</span>
|
||||
</li>
|
||||
<!-- Source information -->
|
||||
<Tabbed>
|
||||
<Tab
|
||||
label-key="epinio.applications.detail.tables.overview"
|
||||
name="overview"
|
||||
:weight="3"
|
||||
>
|
||||
<div class="simple-box-row app-instances">
|
||||
<SimpleBox>
|
||||
<ConsumptionGauge
|
||||
:resource-name="t('epinio.applications.detail.deployment.instances')"
|
||||
:capacity="value.desiredInstances"
|
||||
:used="value.readyInstances"
|
||||
:used-as-resource-name="true"
|
||||
:color-stops="{ 70: '--success', 30: '--warning', 0: '--error' }"
|
||||
/>
|
||||
<div class="scale-instances">
|
||||
<PlusMinus
|
||||
class="mt-15 mb-10"
|
||||
:value="value.desiredInstances"
|
||||
:disabled="saving"
|
||||
@minus="updateInstances(value.desiredInstances - 1)"
|
||||
@plus="updateInstances(value.desiredInstances + 1)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<li
|
||||
v-for="d of value.sourceInfo.details"
|
||||
:key="d.label"
|
||||
<div class="deployment__origin__row">
|
||||
<hr class="mt-10 mb-10">
|
||||
<h4 class="mt-10 mb-10">
|
||||
{{ t('epinio.applications.detail.deployment.metrics') }}
|
||||
</h4>
|
||||
<div
|
||||
v-if="gitSource"
|
||||
class="stats"
|
||||
>
|
||||
<div>
|
||||
<h3>{{ t('tableHeaders.memory') }}</h3>
|
||||
<ul>
|
||||
<li> <span>Min: </span> {{ value.instanceMemory.min }}</li>
|
||||
<li> <span>Max: </span>{{ value.instanceMemory.max }}</li>
|
||||
<li><span>Avg: </span>{{ value.instanceMemory.avg }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3>{{ t('tableHeaders.cpu') }}</h3>
|
||||
<ul>
|
||||
<li> <span>Min: </span> {{ value.instanceCpu.min }}</li>
|
||||
<li> <span>Max: </span>{{ value.instanceCpu.max }}</li>
|
||||
<li><span>Avg: </span>{{ value.instanceCpu.avg }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="stats-table"
|
||||
>
|
||||
<table class="mt-15">
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th>Min</th>
|
||||
<th>Max</th>
|
||||
<th>Avg</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr>
|
||||
<td>{{ t('tableHeaders.memory') }}</td>
|
||||
<td>{{ value.instanceMemory.min }}</td>
|
||||
<td>{{ value.instanceMemory.max }}</td>
|
||||
<td>{{ value.instanceMemory.avg }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ t('tableHeaders.cpu') }}</td>
|
||||
<td>{{ value.instanceCpu.min }}</td>
|
||||
<td>{{ value.instanceCpu.max }}</td>
|
||||
<td>{{ value.instanceCpu.avg }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</SimpleBox>
|
||||
<SimpleBox v-if="value.sourceInfo">
|
||||
<h4 class="mb-10">
|
||||
{{ t('epinio.applications.detail.deployment.details.label') }}
|
||||
</h4>
|
||||
|
||||
<div
|
||||
v-if="gitSource"
|
||||
class="repo-info"
|
||||
>
|
||||
<h4>{{ d.label }}</h4>
|
||||
<span v-if="d.value && d.value.startsWith('http')">
|
||||
<a
|
||||
:href="d.value"
|
||||
target="_blank"
|
||||
>{{ formatURL(d.value) }}</a>
|
||||
</span>
|
||||
<span v-else>{{ d.value }}</span>
|
||||
</li>
|
||||
<AppGitHubDeployment
|
||||
:git-deployment="gitDeployment"
|
||||
:git-source="gitSource"
|
||||
:commit-position="commitPosition"
|
||||
/>
|
||||
</div>
|
||||
<hr class="mt-10 mb-10">
|
||||
<div class="deployment__origin__list">
|
||||
<ul>
|
||||
<li>
|
||||
<h4>{{ t('epinio.applications.detail.deployment.details.origin') }}</h4>
|
||||
<span>{{ value.sourceInfo.label }}</span>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<h4>{{ t('epinio.applications.tableHeaders.deployedBy') }}</h4>
|
||||
<span> {{ value.deployment.username }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<li
|
||||
v-for="d of value.sourceInfo.details"
|
||||
:key="d.label"
|
||||
>
|
||||
<h4>{{ d.label }}</h4>
|
||||
<span v-if="d.value && d.value.startsWith('http')">
|
||||
<a
|
||||
:href="d.value"
|
||||
target="_blank"
|
||||
>{{ formatURL(d.value) }}</a>
|
||||
</span>
|
||||
<span v-else-if="gitSource && d.value && d.value.match(/^[a-f0-9]{40}$/)">
|
||||
<a
|
||||
:href="`${gitSource.html_url}/commit/${d.value}`"
|
||||
target="_blank"
|
||||
>{{ d.value }}</a>
|
||||
</span>
|
||||
<span v-else>{{ d.value }}</span>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<h4>{{ t('epinio.applications.tableHeaders.deployedBy') }}</h4>
|
||||
<span> {{ value.deployment.username }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</SimpleBox>
|
||||
</div>
|
||||
</SimpleBox>
|
||||
<SimpleBox>
|
||||
<div class="deployment__origin__row">
|
||||
<h4>Application Metrics</h4>
|
||||
<table class="stats mt-15">
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th>Min</th>
|
||||
<th>Max</th>
|
||||
<th>Avg</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr>
|
||||
<td>{{ t('tableHeaders.memory') }}</td>
|
||||
<td>{{ value.instanceMemory.min }}</td>
|
||||
<td>{{ value.instanceMemory.max }}</td>
|
||||
<td>{{ value.instanceMemory.avg }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ t('tableHeaders.cpu') }}</td>
|
||||
<td>{{ value.instanceCpu.min }}</td>
|
||||
<td>{{ value.instanceCpu.max }}</td>
|
||||
<td>{{ value.instanceCpu.avg }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</SimpleBox>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab
|
||||
v-if="gitSource"
|
||||
label-key="epinio.applications.detail.tables.githubCommits"
|
||||
name="githubCommits"
|
||||
:weight="2"
|
||||
>
|
||||
<SortableTable
|
||||
v-if="gitDeployment.commitsArray"
|
||||
:rows="prepareCommitArray"
|
||||
:headers="commitsTableHeaders"
|
||||
mode="view"
|
||||
key-field="sha"
|
||||
:search="true"
|
||||
:paging="true"
|
||||
:table-actions="false"
|
||||
:row-actions="false"
|
||||
:rows-per-page="10"
|
||||
>
|
||||
<template #cell:author="{row}">
|
||||
<div class="sortable-table-avatar">
|
||||
<template v-if="row.author">
|
||||
<img
|
||||
:src="row.author.avatar_url"
|
||||
alt=""
|
||||
>
|
||||
<a
|
||||
:href="row.author.html_url"
|
||||
target="_blank"
|
||||
rel="nofollow noopener noreferrer"
|
||||
>
|
||||
{{ row.author.login }}
|
||||
</a>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ t('githubPicker.tableHeaders.author.unknown') }}
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #cell:sha="{row}">
|
||||
<div class="sortable-table-commit">
|
||||
<Link
|
||||
:row="row"
|
||||
url-key="html_url"
|
||||
:value="row.sha"
|
||||
/>
|
||||
<i
|
||||
v-if="row.sha === gitDeployment.deployedCommit.short"
|
||||
v-tooltip="t('epinio.applications.detail.deployment.details.gitHub.deployed')"
|
||||
class="icon icon-fw icon-commit"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</SortableTable>
|
||||
</Tab>
|
||||
</Tabbed>
|
||||
</div>
|
||||
|
||||
<h3 class="mt-20">
|
||||
|
|
@ -295,6 +534,9 @@ export default Vue.extend<Data, any, any, any>({
|
|||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
max-width: 1600px;
|
||||
}
|
||||
.simple-box-row {
|
||||
display: grid;
|
||||
grid-auto-columns: minmax(0, 1fr);
|
||||
|
|
@ -308,7 +550,6 @@ export default Vue.extend<Data, any, any, any>({
|
|||
width: 100%;
|
||||
ul {
|
||||
word-break: break-all;
|
||||
padding-left: 20px;
|
||||
}
|
||||
&:not(:last-of-type) {
|
||||
margin-right: 20px;
|
||||
|
|
@ -332,28 +573,22 @@ export default Vue.extend<Data, any, any, any>({
|
|||
tr {
|
||||
th {
|
||||
text-align: left;
|
||||
color: #c4c4c4;
|
||||
color: var(--muted);
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.deployment__origin__list {
|
||||
ul {
|
||||
margin: 20px 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
li {
|
||||
margin: 5px;
|
||||
list-style: none;
|
||||
h4 {
|
||||
color: #c4c4c4;
|
||||
font-weight: 300;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
.scale-instances {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.plus-minus {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -394,22 +629,97 @@ export default Vue.extend<Data, any, any, any>({
|
|||
}
|
||||
}
|
||||
|
||||
.deployment {
|
||||
.simple-box {
|
||||
.stats-table {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.app-instances {
|
||||
tr td {
|
||||
min-width: 58px;
|
||||
padding: 5px 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
margin: 12px 0;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
border-right: 1px solid var(--default);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
& > div:nth-child(2) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
.scale-instances {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
// For the second div in stats, style the ul differently
|
||||
& > div:nth-child(2) ul {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
}
|
||||
.deployment__origin__list {
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
li {
|
||||
margin: 5px;
|
||||
list-style: none;
|
||||
|
||||
h4 {
|
||||
color: var(--default-text);
|
||||
font-weight: 300;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sortable-table {
|
||||
&-avatar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: var(--border-radius);
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&-commit {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -98,13 +98,26 @@ epinio:
|
|||
label: Deployment
|
||||
summary: Summary
|
||||
instances: Instances
|
||||
metrics: Metrics
|
||||
memory: Memory
|
||||
cpu: CPU
|
||||
details:
|
||||
label: Deployment Details
|
||||
origin: Origin
|
||||
gitHub:
|
||||
created: Created
|
||||
updated: Updated
|
||||
deployed: Deployed
|
||||
tables:
|
||||
label: Resources
|
||||
instances: Instances
|
||||
services: Services
|
||||
configs: Configurations
|
||||
overview: Overview
|
||||
githubCommits: Github commits
|
||||
gitSource:
|
||||
latestCommit: Latest commit deployed
|
||||
behindCommits: Commits behind
|
||||
create:
|
||||
title: Application
|
||||
titleSubText: Epinio
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import Resource from '@shell/plugins/dashboard-store/resource-class';
|
||||
import Vue from 'vue';
|
||||
import { APPLICATION_ACTION_STATE, APPLICATION_MANIFEST_SOURCE_TYPE, APPLICATION_SOURCE_TYPE, EPINIO_PRODUCT_NAME } from '../types';
|
||||
import { epinioExceptionToErrorsArray } from '../utils/errors';
|
||||
import Vue from 'vue';
|
||||
|
||||
export const APPLICATION_ACTION_TYPE = {
|
||||
CREATE_NS: 'create_namespace',
|
||||
|
|
@ -166,7 +166,8 @@ export default class ApplicationActionResource extends Resource {
|
|||
kind: APPLICATION_MANIFEST_SOURCE_TYPE.GIT_HUB,
|
||||
git: {
|
||||
revision: source.github.commit,
|
||||
repository: source.github.url
|
||||
repository: source.github.url,
|
||||
branch: source.github.branch
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { APPLICATION_MANIFEST_SOURCE_TYPE, EPINIO_PRODUCT_NAME, EPINIO_TYPES } from '../types';
|
||||
import { formatSi } from '@shell/utils/units';
|
||||
import { classify } from '@shell/plugins/dashboard-store/classify';
|
||||
import EpinioMetaResource from './epinio-namespaced-resource';
|
||||
import { downloadFile } from '@shell/utils/download';
|
||||
import { createEpinioRoute } from '../utils/custom-routing';
|
||||
import { formatSi } from '@shell/utils/units';
|
||||
import { epiniofy } from '../store/epinio-store/actions';
|
||||
import { APPLICATION_ACTION_STATE, APPLICATION_MANIFEST_SOURCE_TYPE, EPINIO_PRODUCT_NAME, EPINIO_TYPES } from '../types';
|
||||
import { createEpinioRoute } from '../utils/custom-routing';
|
||||
import EpinioMetaResource from './epinio-namespaced-resource';
|
||||
|
||||
// See https://github.com/epinio/epinio/blob/00684bc36780a37ab90091498e5c700337015a96/pkg/api/core/v1/models/app.go#L11
|
||||
const STATES = {
|
||||
|
|
@ -206,6 +206,10 @@ export default class EpinioApplicationModel extends EpinioMetaResource {
|
|||
return Object.keys(this.configuration?.environment || []).length;
|
||||
}
|
||||
|
||||
get envDetails() {
|
||||
return this.configuration?.environment;
|
||||
}
|
||||
|
||||
get routeCount() {
|
||||
return this.configuration?.routes.length;
|
||||
}
|
||||
|
|
@ -258,9 +262,10 @@ export default class EpinioApplicationModel extends EpinioMetaResource {
|
|||
value: this.origin.git.repository
|
||||
}, {
|
||||
label: 'Revision',
|
||||
icon: 'icon-github',
|
||||
icon: 'icon-commit',
|
||||
value: this.origin.git.revision
|
||||
}]
|
||||
},
|
||||
]
|
||||
};
|
||||
case APPLICATION_MANIFEST_SOURCE_TYPE.CONTAINER:
|
||||
return {
|
||||
|
|
@ -272,6 +277,20 @@ export default class EpinioApplicationModel extends EpinioMetaResource {
|
|||
value: this.origin.Container || this.origin.container
|
||||
}]
|
||||
};
|
||||
case APPLICATION_MANIFEST_SOURCE_TYPE.GIT_HUB:
|
||||
return {
|
||||
label: 'GitHub',
|
||||
icon: 'icon-github',
|
||||
details: [
|
||||
appChart, {
|
||||
label: 'Url',
|
||||
value: this.origin.git.repository
|
||||
}, {
|
||||
label: 'Revision',
|
||||
icon: 'icon-github',
|
||||
value: this.origin.git.revision
|
||||
}]
|
||||
};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -627,7 +646,7 @@ export default class EpinioApplicationModel extends EpinioMetaResource {
|
|||
// 'deployed' status. Unfortunately we don't have that... so wait for ready === desired replica sets instead
|
||||
const fresh = this.$getters['byId'](EPINIO_TYPES.APP, `${ this.meta.namespace }/${ this.meta.name }`);
|
||||
|
||||
if (fresh.deployment?.readyreplicas === fresh.deployment?.desiredreplicas) {
|
||||
if (fresh.deployment?.readyreplicas === fresh.deployment?.desiredreplicas && fresh.deployment.state === APPLICATION_ACTION_STATE.SUCCESS) {
|
||||
return true;
|
||||
}
|
||||
// This is an async fn, but we're in a sync fn. It might create a backlog if previous requests don't complete in time
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
import EpinioMetaResource from '~/pkg/epinio/models/epinio-namespaced-resource';
|
||||
|
||||
export default class GithubCommits extends EpinioMetaResource {
|
||||
get availableActions() {
|
||||
return [{
|
||||
action: 'github-commits',
|
||||
label: this.t('epinio.applications.actions.shell.label'),
|
||||
icon: 'icon icon-fw icon-chevron-right',
|
||||
enabled: true,
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import Application from '../../../../../models/applications';
|
|||
import CreateEditView from '@shell/mixins/create-edit-view/impl';
|
||||
import Loading from '@shell/components/Loading.vue';
|
||||
import Wizard from '@shell/components/Wizard.vue';
|
||||
import { EPINIO_TYPES } from '../../../../../types';
|
||||
import { APPLICATION_ENV_VAR, APPLICATION_SOURCE_TYPE, EPINIO_APP_ENV_VAR_GITHUB, EPINIO_TYPES } from '../../../../../types';
|
||||
import { _CREATE } from '@shell/config/query-params';
|
||||
import AppInfo, { EpinioAppInfo } from '../../../../../components/application/AppInfo.vue';
|
||||
import AppSource, { EpinioAppSource } from '../../../../../components/application/AppSource.vue';
|
||||
|
|
@ -107,12 +107,29 @@ export default Vue.extend<Data, any, any, any>({
|
|||
this.source = {};
|
||||
const { appChart, ...cleanChanges } = changes;
|
||||
|
||||
this.value.configuration = this.value.configuration || {};
|
||||
|
||||
if (appChart) {
|
||||
// app chart actually belongs in config, so stick it in there
|
||||
this.value.configuration = this.value.configuration || {};
|
||||
this.set(this.value.configuration, { appchart: appChart });
|
||||
}
|
||||
|
||||
if (changes.type === APPLICATION_SOURCE_TYPE.GIT_HUB) {
|
||||
this.value.configuration.environment = this.value.configuration.environment || {};
|
||||
const githubEnvVar: EPINIO_APP_ENV_VAR_GITHUB = {
|
||||
usernameOrOrg: changes.github.usernameOrOrg as string,
|
||||
repo: changes.github.repo,
|
||||
branch: changes.github.branch,
|
||||
};
|
||||
|
||||
this.set(this.value.configuration.environment, {
|
||||
...this.value.configuration.environment,
|
||||
[APPLICATION_ENV_VAR]: JSON.stringify(githubEnvVar)
|
||||
});
|
||||
} else {
|
||||
delete this.value.configuration?.environment?.[APPLICATION_ENV_VAR];
|
||||
}
|
||||
|
||||
this.set(this.source, cleanChanges);
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,13 @@ export const APPLICATION_ACTION_STATE = {
|
|||
PENDING: 'pending',
|
||||
};
|
||||
|
||||
export const APPLICATION_ENV_VAR = 'EPINIO_APP_DATA';
|
||||
export interface EPINIO_APP_ENV_VAR_GITHUB {
|
||||
usernameOrOrg: string,
|
||||
repo: string,
|
||||
branch: string,
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
// Temporary code until models are typed
|
||||
interface EpinioMeta {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import Collapse from '@shell/components/Collapse.vue';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
describe('component: Collapse.vue', () => {
|
||||
describe('closed', () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import SimpleBox from '@shell/components/SimpleBox.vue';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
describe('component: SimpleBox.vue', () => {
|
||||
const wrapper = mount(SimpleBox, { propsData: { title: 'Simple box title' } });
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export default {
|
|||
width: 220,
|
||||
label: this.t('githubPicker.tableHeaders.date.label'),
|
||||
value: 'date',
|
||||
sort: 'date:desc',
|
||||
sort: ['date:desc'],
|
||||
formatter: 'Date',
|
||||
defaultSort: true,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,6 +3,13 @@ const GITHUB_BASE_API = 'https://api.github.com';
|
|||
const fetchGithubAPI = async(endpoint) => {
|
||||
const response = await fetch(`${ GITHUB_BASE_API }/${ endpoint }`);
|
||||
|
||||
// If rate-limit is exceeded, we should wait until the rate limit is reset
|
||||
if (response.status === 403) {
|
||||
const resetTime = new Date(response.headers.get('X-RateLimit-Reset') * 1000);
|
||||
|
||||
throw new Error(`Rate limit exceeded. Try again at ${ resetTime }`);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw response;
|
||||
}
|
||||
|
|
@ -10,6 +17,8 @@ const fetchGithubAPI = async(endpoint) => {
|
|||
return await response.json();
|
||||
};
|
||||
|
||||
export const getters = {};
|
||||
|
||||
export const actions = {
|
||||
async apiList(ctx, {
|
||||
username, endpoint, repo, branch
|
||||
|
|
@ -19,6 +28,9 @@ export const actions = {
|
|||
case 'branches': {
|
||||
return await fetchGithubAPI(`repos/${ username }/${ repo }/branches?sort=updated&per_page=100&direction=desc`);
|
||||
}
|
||||
case 'repo': {
|
||||
return await fetchGithubAPI(`repos/${ username }/${ repo }`);
|
||||
}
|
||||
case 'commits': {
|
||||
return await fetchGithubAPI(`repos/${ username }/${ repo }/commits?sha=${ branch }&sort=updated&per_page=100`);
|
||||
}
|
||||
|
|
@ -51,6 +63,14 @@ export const actions = {
|
|||
return res;
|
||||
},
|
||||
|
||||
async fetchRepoDetails({ commit, dispatch }, { username, repo } = {}) {
|
||||
const res = await dispatch('apiList', {
|
||||
username, endpoint: 'repo', repo
|
||||
});
|
||||
|
||||
return res;
|
||||
},
|
||||
|
||||
async fetchBranches({ commit, dispatch }, { repo, username }) {
|
||||
const res = await dispatch('apiList', {
|
||||
username, endpoint: 'branches', repo
|
||||
|
|
@ -59,7 +79,8 @@ export const actions = {
|
|||
return res;
|
||||
},
|
||||
|
||||
async fetchCommits({ commit, dispatch }, { repo, username, branch }) {
|
||||
async fetchCommits(ctx, { repo, username, branch }) {
|
||||
const { dispatch } = ctx;
|
||||
const res = await dispatch('apiList', {
|
||||
username, endpoint: 'commits', repo, branch
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue