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">
|
<script lang="ts">
|
||||||
|
import day from 'dayjs';
|
||||||
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
import Vue, { PropType } from 'vue';
|
import Vue, { PropType } from 'vue';
|
||||||
import Application from '../models/applications';
|
import Application from '../models/applications';
|
||||||
import SimpleBox from '@shell/components/SimpleBox.vue';
|
import SimpleBox from '@shell/components/SimpleBox.vue';
|
||||||
import ConsumptionGauge from '@shell/components/ConsumptionGauge.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 ResourceTable from '@shell/components/ResourceTable.vue';
|
||||||
import PlusMinus from '@shell/components/form/PlusMinus.vue';
|
import PlusMinus from '@shell/components/form/PlusMinus.vue';
|
||||||
import { epinioExceptionToErrorsArray } from '../utils/errors';
|
import { epinioExceptionToErrorsArray } from '../utils/errors';
|
||||||
import ApplicationCard from '@shell/components/cards/ApplicationCard.vue';
|
import ApplicationCard from '@shell/components/cards/ApplicationCard.vue';
|
||||||
import Tabbed from '@shell/components/Tabbed/index.vue';
|
import Tabbed from '@shell/components/Tabbed/index.vue';
|
||||||
import Tab from '@shell/components/Tabbed/Tab.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 {
|
interface Data {
|
||||||
}
|
}
|
||||||
|
|
@ -19,11 +24,14 @@ export default Vue.extend<Data, any, any, any>({
|
||||||
components: {
|
components: {
|
||||||
SimpleBox,
|
SimpleBox,
|
||||||
ConsumptionGauge,
|
ConsumptionGauge,
|
||||||
|
SortableTable,
|
||||||
ResourceTable,
|
ResourceTable,
|
||||||
PlusMinus,
|
PlusMinus,
|
||||||
ApplicationCard,
|
ApplicationCard,
|
||||||
|
AppGitHubDeployment,
|
||||||
Tabbed,
|
Tabbed,
|
||||||
Tab,
|
Tab,
|
||||||
|
Link
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
|
|
@ -42,8 +50,8 @@ export default Vue.extend<Data, any, any, any>({
|
||||||
fetch() {
|
fetch() {
|
||||||
this.$store.dispatch(`epinio/findAll`, { type: EPINIO_TYPES.SERVICE_INSTANCE });
|
this.$store.dispatch(`epinio/findAll`, { type: EPINIO_TYPES.SERVICE_INSTANCE });
|
||||||
this.$store.dispatch(`epinio/findAll`, { type: EPINIO_TYPES.CONFIGURATION });
|
this.$store.dispatch(`epinio/findAll`, { type: EPINIO_TYPES.CONFIGURATION });
|
||||||
|
this.fetchRepoDetails();
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
const appInstanceSchema = this.$store.getters[`${ EPINIO_PRODUCT_NAME }/schemaFor`](EPINIO_TYPES.APP_INSTANCE);
|
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);
|
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);
|
const configsHeaders: [] = this.$store.getters['type-map/headersFor'](configsSchema);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
saving: false,
|
saving: false,
|
||||||
|
gitSource: null,
|
||||||
|
gitDeployment: {
|
||||||
|
deployedCommit: null,
|
||||||
|
commitsArray: null,
|
||||||
|
},
|
||||||
appInstance: {
|
appInstance: {
|
||||||
schema: appInstanceSchema,
|
schema: appInstanceSchema,
|
||||||
headers: this.$store.getters['type-map/headersFor'](appInstanceSchema),
|
headers: this.$store.getters['type-map/headersFor'](appInstanceSchema),
|
||||||
|
|
@ -64,10 +77,37 @@ export default Vue.extend<Data, any, any, any>({
|
||||||
configs: {
|
configs: {
|
||||||
schema: configsSchema,
|
schema: configsSchema,
|
||||||
headers: configsHeaders.filter((h: any) => !['namespace', 'boundApps', 'service'].includes(h.name)),
|
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: {
|
methods: {
|
||||||
async updateInstances(newInstances: number) {
|
async updateInstances(newInstances: number) {
|
||||||
this.$set(this, 'saving', true);
|
this.$set(this, 'saving', true);
|
||||||
|
|
@ -84,19 +124,112 @@ export default Vue.extend<Data, any, any, any>({
|
||||||
const matchGithub = str.match('^(https|git)(:\/\/|@)([^\/:]+)[\/:]([^\/:]+)\/(.+)(.git)*$');
|
const matchGithub = str.match('^(https|git)(:\/\/|@)([^\/:]+)[\/:]([^\/:]+)\/(.+)(.git)*$');
|
||||||
|
|
||||||
return `${ matchGithub?.[4] }/${ matchGithub?.[5] }`;
|
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: {
|
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 {
|
sourceIcon(): string {
|
||||||
return this.value.sourceInfo?.icon || 'icon-epinio';
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="content">
|
||||||
<div class="application-details">
|
<div class="application-details">
|
||||||
<ApplicationCard>
|
<ApplicationCard>
|
||||||
<!-- Icon slot -->
|
<!-- Icon slot -->
|
||||||
|
|
@ -159,90 +292,196 @@ export default Vue.extend<Data, any, any, any>({
|
||||||
v-if="value.deployment"
|
v-if="value.deployment"
|
||||||
class="deployment"
|
class="deployment"
|
||||||
>
|
>
|
||||||
<div class="simple-box-row app-instances">
|
<!-- Source information -->
|
||||||
<SimpleBox>
|
<Tabbed>
|
||||||
<ConsumptionGauge
|
<Tab
|
||||||
:resource-name="t('epinio.applications.detail.deployment.instances')"
|
label-key="epinio.applications.detail.tables.overview"
|
||||||
:capacity="value.desiredInstances"
|
name="overview"
|
||||||
:used="value.readyInstances"
|
:weight="3"
|
||||||
:used-as-resource-name="true"
|
>
|
||||||
:color-stops="{ 70: '--success', 30: '--warning', 0: '--error' }"
|
<div class="simple-box-row app-instances">
|
||||||
/>
|
<SimpleBox>
|
||||||
<div class="scale-instances">
|
<ConsumptionGauge
|
||||||
<PlusMinus
|
:resource-name="t('epinio.applications.detail.deployment.instances')"
|
||||||
class="mt-15 mb-10"
|
:capacity="value.desiredInstances"
|
||||||
:value="value.desiredInstances"
|
:used="value.readyInstances"
|
||||||
:disabled="saving"
|
:used-as-resource-name="true"
|
||||||
@minus="updateInstances(value.desiredInstances - 1)"
|
:color-stops="{ 70: '--success', 30: '--warning', 0: '--error' }"
|
||||||
@plus="updateInstances(value.desiredInstances + 1)"
|
/>
|
||||||
/>
|
<div class="scale-instances">
|
||||||
</div>
|
<PlusMinus
|
||||||
</SimpleBox>
|
class="mt-15 mb-10"
|
||||||
<!-- Source information -->
|
:value="value.desiredInstances"
|
||||||
<SimpleBox v-if="value.sourceInfo">
|
:disabled="saving"
|
||||||
<div class="deployment__origin__row">
|
@minus="updateInstances(value.desiredInstances - 1)"
|
||||||
<h4>Deployment Details</h4>
|
@plus="updateInstances(value.desiredInstances + 1)"
|
||||||
</div>
|
/>
|
||||||
<div class="deployment__origin__list">
|
</div>
|
||||||
<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>
|
|
||||||
|
|
||||||
<li
|
<div class="deployment__origin__row">
|
||||||
v-for="d of value.sourceInfo.details"
|
<hr class="mt-10 mb-10">
|
||||||
:key="d.label"
|
<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>
|
<AppGitHubDeployment
|
||||||
<span v-if="d.value && d.value.startsWith('http')">
|
:git-deployment="gitDeployment"
|
||||||
<a
|
:git-source="gitSource"
|
||||||
:href="d.value"
|
:commit-position="commitPosition"
|
||||||
target="_blank"
|
/>
|
||||||
>{{ formatURL(d.value) }}</a>
|
</div>
|
||||||
</span>
|
<hr class="mt-10 mb-10">
|
||||||
<span v-else>{{ d.value }}</span>
|
<div class="deployment__origin__list">
|
||||||
</li>
|
<ul>
|
||||||
|
<li>
|
||||||
|
<h4>{{ t('epinio.applications.detail.deployment.details.origin') }}</h4>
|
||||||
|
<span>{{ value.sourceInfo.label }}</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li>
|
<li
|
||||||
<h4>{{ t('epinio.applications.tableHeaders.deployedBy') }}</h4>
|
v-for="d of value.sourceInfo.details"
|
||||||
<span> {{ value.deployment.username }}</span>
|
:key="d.label"
|
||||||
</li>
|
>
|
||||||
</ul>
|
<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>
|
</div>
|
||||||
</SimpleBox>
|
</Tab>
|
||||||
<SimpleBox>
|
<Tab
|
||||||
<div class="deployment__origin__row">
|
v-if="gitSource"
|
||||||
<h4>Application Metrics</h4>
|
label-key="epinio.applications.detail.tables.githubCommits"
|
||||||
<table class="stats mt-15">
|
name="githubCommits"
|
||||||
<thead>
|
:weight="2"
|
||||||
<tr>
|
>
|
||||||
<th />
|
<SortableTable
|
||||||
<th>Min</th>
|
v-if="gitDeployment.commitsArray"
|
||||||
<th>Max</th>
|
:rows="prepareCommitArray"
|
||||||
<th>Avg</th>
|
:headers="commitsTableHeaders"
|
||||||
</tr>
|
mode="view"
|
||||||
</thead>
|
key-field="sha"
|
||||||
<tr>
|
:search="true"
|
||||||
<td>{{ t('tableHeaders.memory') }}</td>
|
:paging="true"
|
||||||
<td>{{ value.instanceMemory.min }}</td>
|
:table-actions="false"
|
||||||
<td>{{ value.instanceMemory.max }}</td>
|
:row-actions="false"
|
||||||
<td>{{ value.instanceMemory.avg }}</td>
|
:rows-per-page="10"
|
||||||
</tr>
|
>
|
||||||
<tr>
|
<template #cell:author="{row}">
|
||||||
<td>{{ t('tableHeaders.cpu') }}</td>
|
<div class="sortable-table-avatar">
|
||||||
<td>{{ value.instanceCpu.min }}</td>
|
<template v-if="row.author">
|
||||||
<td>{{ value.instanceCpu.max }}</td>
|
<img
|
||||||
<td>{{ value.instanceCpu.avg }}</td>
|
:src="row.author.avatar_url"
|
||||||
</tr>
|
alt=""
|
||||||
</table>
|
>
|
||||||
</div>
|
<a
|
||||||
</SimpleBox>
|
:href="row.author.html_url"
|
||||||
</div>
|
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>
|
</div>
|
||||||
|
|
||||||
<h3 class="mt-20">
|
<h3 class="mt-20">
|
||||||
|
|
@ -295,6 +534,9 @@ export default Vue.extend<Data, any, any, any>({
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.content {
|
||||||
|
max-width: 1600px;
|
||||||
|
}
|
||||||
.simple-box-row {
|
.simple-box-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-columns: minmax(0, 1fr);
|
grid-auto-columns: minmax(0, 1fr);
|
||||||
|
|
@ -308,7 +550,6 @@ export default Vue.extend<Data, any, any, any>({
|
||||||
width: 100%;
|
width: 100%;
|
||||||
ul {
|
ul {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
padding-left: 20px;
|
|
||||||
}
|
}
|
||||||
&:not(:last-of-type) {
|
&:not(:last-of-type) {
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
|
|
@ -332,28 +573,22 @@ export default Vue.extend<Data, any, any, any>({
|
||||||
tr {
|
tr {
|
||||||
th {
|
th {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
color: #c4c4c4;
|
color: var(--muted);
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.deployment__origin__list {
|
|
||||||
ul {
|
|
||||||
margin: 20px 0;
|
|
||||||
padding: 0;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
|
|
||||||
li {
|
.scale-instances {
|
||||||
margin: 5px;
|
display: flex;
|
||||||
list-style: none;
|
align-items: center;
|
||||||
h4 {
|
|
||||||
color: #c4c4c4;
|
.plus-minus {
|
||||||
font-weight: 300;
|
width: 100%;
|
||||||
margin: 0;
|
display: flex;
|
||||||
}
|
align-items: center;
|
||||||
}
|
justify-content: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -394,22 +629,97 @@ export default Vue.extend<Data, any, any, any>({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.deployment {
|
.stats-table {
|
||||||
.simple-box {
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
.app-instances {
|
}
|
||||||
tr td {
|
|
||||||
min-width: 58px;
|
.stats {
|
||||||
padding: 5px 0;
|
display: grid;
|
||||||
font-size: 1.1rem;
|
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;
|
// For the second div in stats, style the ul differently
|
||||||
justify-content: center;
|
& > 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>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -98,13 +98,26 @@ epinio:
|
||||||
label: Deployment
|
label: Deployment
|
||||||
summary: Summary
|
summary: Summary
|
||||||
instances: Instances
|
instances: Instances
|
||||||
|
metrics: Metrics
|
||||||
memory: Memory
|
memory: Memory
|
||||||
cpu: CPU
|
cpu: CPU
|
||||||
|
details:
|
||||||
|
label: Deployment Details
|
||||||
|
origin: Origin
|
||||||
|
gitHub:
|
||||||
|
created: Created
|
||||||
|
updated: Updated
|
||||||
|
deployed: Deployed
|
||||||
tables:
|
tables:
|
||||||
label: Resources
|
label: Resources
|
||||||
instances: Instances
|
instances: Instances
|
||||||
services: Services
|
services: Services
|
||||||
configs: Configurations
|
configs: Configurations
|
||||||
|
overview: Overview
|
||||||
|
githubCommits: Github commits
|
||||||
|
gitSource:
|
||||||
|
latestCommit: Latest commit deployed
|
||||||
|
behindCommits: Commits behind
|
||||||
create:
|
create:
|
||||||
title: Application
|
title: Application
|
||||||
titleSubText: Epinio
|
titleSubText: Epinio
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import Resource from '@shell/plugins/dashboard-store/resource-class';
|
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 { APPLICATION_ACTION_STATE, APPLICATION_MANIFEST_SOURCE_TYPE, APPLICATION_SOURCE_TYPE, EPINIO_PRODUCT_NAME } from '../types';
|
||||||
import { epinioExceptionToErrorsArray } from '../utils/errors';
|
import { epinioExceptionToErrorsArray } from '../utils/errors';
|
||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
export const APPLICATION_ACTION_TYPE = {
|
export const APPLICATION_ACTION_TYPE = {
|
||||||
CREATE_NS: 'create_namespace',
|
CREATE_NS: 'create_namespace',
|
||||||
|
|
@ -166,7 +166,8 @@ export default class ApplicationActionResource extends Resource {
|
||||||
kind: APPLICATION_MANIFEST_SOURCE_TYPE.GIT_HUB,
|
kind: APPLICATION_MANIFEST_SOURCE_TYPE.GIT_HUB,
|
||||||
git: {
|
git: {
|
||||||
revision: source.github.commit,
|
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 { classify } from '@shell/plugins/dashboard-store/classify';
|
||||||
import EpinioMetaResource from './epinio-namespaced-resource';
|
|
||||||
import { downloadFile } from '@shell/utils/download';
|
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 { 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
|
// See https://github.com/epinio/epinio/blob/00684bc36780a37ab90091498e5c700337015a96/pkg/api/core/v1/models/app.go#L11
|
||||||
const STATES = {
|
const STATES = {
|
||||||
|
|
@ -206,6 +206,10 @@ export default class EpinioApplicationModel extends EpinioMetaResource {
|
||||||
return Object.keys(this.configuration?.environment || []).length;
|
return Object.keys(this.configuration?.environment || []).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get envDetails() {
|
||||||
|
return this.configuration?.environment;
|
||||||
|
}
|
||||||
|
|
||||||
get routeCount() {
|
get routeCount() {
|
||||||
return this.configuration?.routes.length;
|
return this.configuration?.routes.length;
|
||||||
}
|
}
|
||||||
|
|
@ -258,9 +262,10 @@ export default class EpinioApplicationModel extends EpinioMetaResource {
|
||||||
value: this.origin.git.repository
|
value: this.origin.git.repository
|
||||||
}, {
|
}, {
|
||||||
label: 'Revision',
|
label: 'Revision',
|
||||||
icon: 'icon-github',
|
icon: 'icon-commit',
|
||||||
value: this.origin.git.revision
|
value: this.origin.git.revision
|
||||||
}]
|
},
|
||||||
|
]
|
||||||
};
|
};
|
||||||
case APPLICATION_MANIFEST_SOURCE_TYPE.CONTAINER:
|
case APPLICATION_MANIFEST_SOURCE_TYPE.CONTAINER:
|
||||||
return {
|
return {
|
||||||
|
|
@ -272,6 +277,20 @@ export default class EpinioApplicationModel extends EpinioMetaResource {
|
||||||
value: this.origin.Container || this.origin.container
|
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:
|
default:
|
||||||
return undefined;
|
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
|
// '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 }`);
|
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;
|
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
|
// 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 CreateEditView from '@shell/mixins/create-edit-view/impl';
|
||||||
import Loading from '@shell/components/Loading.vue';
|
import Loading from '@shell/components/Loading.vue';
|
||||||
import Wizard from '@shell/components/Wizard.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 { _CREATE } from '@shell/config/query-params';
|
||||||
import AppInfo, { EpinioAppInfo } from '../../../../../components/application/AppInfo.vue';
|
import AppInfo, { EpinioAppInfo } from '../../../../../components/application/AppInfo.vue';
|
||||||
import AppSource, { EpinioAppSource } from '../../../../../components/application/AppSource.vue';
|
import AppSource, { EpinioAppSource } from '../../../../../components/application/AppSource.vue';
|
||||||
|
|
@ -107,12 +107,29 @@ export default Vue.extend<Data, any, any, any>({
|
||||||
this.source = {};
|
this.source = {};
|
||||||
const { appChart, ...cleanChanges } = changes;
|
const { appChart, ...cleanChanges } = changes;
|
||||||
|
|
||||||
|
this.value.configuration = this.value.configuration || {};
|
||||||
|
|
||||||
if (appChart) {
|
if (appChart) {
|
||||||
// app chart actually belongs in config, so stick it in there
|
// app chart actually belongs in config, so stick it in there
|
||||||
this.value.configuration = this.value.configuration || {};
|
|
||||||
this.set(this.value.configuration, { appchart: appChart });
|
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);
|
this.set(this.source, cleanChanges);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,13 @@ export const APPLICATION_ACTION_STATE = {
|
||||||
PENDING: 'pending',
|
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
|
// Temporary code until models are typed
|
||||||
interface EpinioMeta {
|
interface EpinioMeta {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { mount } from '@vue/test-utils';
|
|
||||||
import Collapse from '@shell/components/Collapse.vue';
|
import Collapse from '@shell/components/Collapse.vue';
|
||||||
|
import { mount } from '@vue/test-utils';
|
||||||
|
|
||||||
describe('component: Collapse.vue', () => {
|
describe('component: Collapse.vue', () => {
|
||||||
describe('closed', () => {
|
describe('closed', () => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { mount } from '@vue/test-utils';
|
|
||||||
import SimpleBox from '@shell/components/SimpleBox.vue';
|
import SimpleBox from '@shell/components/SimpleBox.vue';
|
||||||
|
import { mount } from '@vue/test-utils';
|
||||||
|
|
||||||
describe('component: SimpleBox.vue', () => {
|
describe('component: SimpleBox.vue', () => {
|
||||||
const wrapper = mount(SimpleBox, { propsData: { title: 'Simple box title' } });
|
const wrapper = mount(SimpleBox, { propsData: { title: 'Simple box title' } });
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export default {
|
||||||
width: 220,
|
width: 220,
|
||||||
label: this.t('githubPicker.tableHeaders.date.label'),
|
label: this.t('githubPicker.tableHeaders.date.label'),
|
||||||
value: 'date',
|
value: 'date',
|
||||||
sort: 'date:desc',
|
sort: ['date:desc'],
|
||||||
formatter: 'Date',
|
formatter: 'Date',
|
||||||
defaultSort: true,
|
defaultSort: true,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,13 @@ const GITHUB_BASE_API = 'https://api.github.com';
|
||||||
const fetchGithubAPI = async(endpoint) => {
|
const fetchGithubAPI = async(endpoint) => {
|
||||||
const response = await fetch(`${ GITHUB_BASE_API }/${ 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) {
|
if (!response.ok) {
|
||||||
throw response;
|
throw response;
|
||||||
}
|
}
|
||||||
|
|
@ -10,6 +17,8 @@ const fetchGithubAPI = async(endpoint) => {
|
||||||
return await response.json();
|
return await response.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getters = {};
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
async apiList(ctx, {
|
async apiList(ctx, {
|
||||||
username, endpoint, repo, branch
|
username, endpoint, repo, branch
|
||||||
|
|
@ -19,6 +28,9 @@ export const actions = {
|
||||||
case 'branches': {
|
case 'branches': {
|
||||||
return await fetchGithubAPI(`repos/${ username }/${ repo }/branches?sort=updated&per_page=100&direction=desc`);
|
return await fetchGithubAPI(`repos/${ username }/${ repo }/branches?sort=updated&per_page=100&direction=desc`);
|
||||||
}
|
}
|
||||||
|
case 'repo': {
|
||||||
|
return await fetchGithubAPI(`repos/${ username }/${ repo }`);
|
||||||
|
}
|
||||||
case 'commits': {
|
case 'commits': {
|
||||||
return await fetchGithubAPI(`repos/${ username }/${ repo }/commits?sha=${ branch }&sort=updated&per_page=100`);
|
return await fetchGithubAPI(`repos/${ username }/${ repo }/commits?sha=${ branch }&sort=updated&per_page=100`);
|
||||||
}
|
}
|
||||||
|
|
@ -51,6 +63,14 @@ export const actions = {
|
||||||
return res;
|
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 }) {
|
async fetchBranches({ commit, dispatch }, { repo, username }) {
|
||||||
const res = await dispatch('apiList', {
|
const res = await dispatch('apiList', {
|
||||||
username, endpoint: 'branches', repo
|
username, endpoint: 'branches', repo
|
||||||
|
|
@ -59,7 +79,8 @@ export const actions = {
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchCommits({ commit, dispatch }, { repo, username, branch }) {
|
async fetchCommits(ctx, { repo, username, branch }) {
|
||||||
|
const { dispatch } = ctx;
|
||||||
const res = await dispatch('apiList', {
|
const res = await dispatch('apiList', {
|
||||||
username, endpoint: 'commits', repo, branch
|
username, endpoint: 'commits', repo, branch
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue