mirror of https://github.com/rancher/dashboard.git
commit
c05ec33160
|
|
@ -15,9 +15,12 @@ BODY {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
|
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
|
||||||
|
|
||||||
|
&.overflow-hidden {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,12 @@ const LABEL = {
|
||||||
waiting: 'Saving…',
|
waiting: 'Saving…',
|
||||||
success: 'Saved',
|
success: 'Saved',
|
||||||
error: 'Error',
|
error: 'Error',
|
||||||
|
},
|
||||||
|
done: {
|
||||||
|
action: 'Done',
|
||||||
|
waiting: 'Saving…',
|
||||||
|
success: 'Saved',
|
||||||
|
error: 'Error',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex';
|
import { mapState } from 'vuex';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { addParam, parseLinkHeader } from '@/utils/url';
|
import { findBy } from '../utils/array';
|
||||||
import { addObjects, isArray } from '@/utils/array';
|
import { EXTENDED_SCOPES } from '@/store/github';
|
||||||
import { EXTENDED_SCOPES } from '@/store/auth';
|
|
||||||
|
|
||||||
const API_BASE = 'https://api.github.com/';
|
export const FILE_PATTERNS = {
|
||||||
|
'dockerfile': /^Dockerfile(\..*)?$/i,
|
||||||
|
'yaml': /^.*\.ya?ml?$/i,
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -14,19 +16,20 @@ export default {
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// filter files displayed in dropdown - default to .yml and Dockerfile files
|
// Filter files displayed in dropdown by keys in FILE_PATTERNS
|
||||||
filePattern: {
|
filePattern: {
|
||||||
type: RegExp,
|
type: String,
|
||||||
default: () => {
|
default: null,
|
||||||
return new RegExp('\.ya?ml$|^Dockerfile(\..*)?', 'i');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
preferredFile: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
scopes: [],
|
|
||||||
recentRepos: [],
|
|
||||||
repos: [],
|
repos: [],
|
||||||
branches: [],
|
branches: [],
|
||||||
files: [],
|
files: [],
|
||||||
|
|
@ -42,8 +45,45 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
scopes: state => state.github.scopes,
|
||||||
|
recentRepos: state => state.github.repos,
|
||||||
|
}),
|
||||||
|
|
||||||
hasPrivate() {
|
hasPrivate() {
|
||||||
return this.scopes.includes('repo');
|
return this.scopes.includes('repo');
|
||||||
|
},
|
||||||
|
|
||||||
|
repoPlaceholder() {
|
||||||
|
if ( this.loadingRecentRepos ) {
|
||||||
|
return 'Loading...';
|
||||||
|
} else {
|
||||||
|
return 'Select a Repository...';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
branchPlaceholder() {
|
||||||
|
if ( this.selectedRepo ) {
|
||||||
|
if ( this.loadingBranches ) {
|
||||||
|
return 'Loading...';
|
||||||
|
} else {
|
||||||
|
return 'Select a Branch...';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 'Select a Repository First';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
filePlaceholder() {
|
||||||
|
if ( this.selectedBranch ) {
|
||||||
|
if ( this.loadingFiles ) {
|
||||||
|
return 'Loading...';
|
||||||
|
} else {
|
||||||
|
return 'Select a File...';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 'Select a Branch First';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -56,14 +96,22 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
update() {
|
||||||
|
if ( this.selectedRepo && this.selectedBranch && this.selectedFile ) {
|
||||||
|
// Do something
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
selectRepo(repo) {
|
selectRepo(repo) {
|
||||||
|
this.selectedFile = null;
|
||||||
this.selectedBranch = null;
|
this.selectedBranch = null;
|
||||||
this.selectedRepo = repo;
|
this.selectedRepo = repo;
|
||||||
this.$emit('selectedRepo', repo);
|
|
||||||
this.fetchBranches(repo);
|
this.fetchBranches(repo);
|
||||||
|
this.update();
|
||||||
},
|
},
|
||||||
|
|
||||||
selectBranch(branch) {
|
selectBranch(branch) {
|
||||||
|
this.selectedFile = null;
|
||||||
this.selectedBranch = branch;
|
this.selectedBranch = branch;
|
||||||
this.$emit('selectedBranch', branch);
|
this.$emit('selectedBranch', branch);
|
||||||
this.fetchFiles(this.selectedRepo, branch);
|
this.fetchFiles(this.selectedRepo, branch);
|
||||||
|
|
@ -96,7 +144,8 @@ export default {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
loading(true);
|
loading(true);
|
||||||
const res = await this.apiList(`/search/repositories?q=${ escape(search) }`, { depaginate: false });
|
|
||||||
|
const res = await this.$store.dispatch('github/searchRepos', { search });
|
||||||
|
|
||||||
this.repos = res;
|
this.repos = res;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -108,12 +157,9 @@ export default {
|
||||||
|
|
||||||
async fetchRepos() {
|
async fetchRepos() {
|
||||||
try {
|
try {
|
||||||
if ( !this.recentRepos.length) {
|
const res = await this.$store.dispatch('github/fetchRecentRepos');
|
||||||
const res = await this.apiList('/user/repos?sort=updated', { depaginate: false });
|
|
||||||
|
|
||||||
this.recentRepos = res;
|
this.repos = res;
|
||||||
this.repos = res.slice();
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
this.loadingRecentRepos = false;
|
this.loadingRecentRepos = false;
|
||||||
}
|
}
|
||||||
|
|
@ -122,69 +168,43 @@ export default {
|
||||||
async fetchBranches(repo) {
|
async fetchBranches(repo) {
|
||||||
this.loadingBranches = true;
|
this.loadingBranches = true;
|
||||||
|
|
||||||
const url = repo.branches_url.replace('{/branch}', '');
|
try {
|
||||||
|
const res = await this.$store.dispatch('github/fetchBranches', { repo: this.selectedRepo });
|
||||||
|
|
||||||
const res = await this.apiList(url);
|
this.branches = res;
|
||||||
|
|
||||||
this.branches = res;
|
if ( !this.selectedBranch ) {
|
||||||
this.loadingBranches = false;
|
const master = findBy(this.branches, 'name', 'master');
|
||||||
|
|
||||||
|
if ( master ) {
|
||||||
|
this.selectBranch(master);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.loadingBranches = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchFiles(repo, branch) {
|
async fetchFiles(repo, branch) {
|
||||||
let url = repo.trees_url.replace('{/sha}', `/${ branch.commit.sha }`);
|
try {
|
||||||
|
const res = await this.$store.dispatch('github/fetchFiles', {
|
||||||
|
repo: this.selectedRepo,
|
||||||
|
branch: this.selectedBranch,
|
||||||
|
pattern: FILE_PATTERNS[(this.filePattern || '').toLowerCase()],
|
||||||
|
});
|
||||||
|
|
||||||
url = addParam(url, 'recursive', 1);
|
this.files = res;
|
||||||
|
|
||||||
const res = await this.apiList(url, { objectKey: 'tree' });
|
if ( !this.selecteeFile && this.preferredFile ) {
|
||||||
|
const file = findBy(this.files, 'path', this.preferredFile);
|
||||||
|
|
||||||
this.files = res.filter(file => file.type === 'blob' && file.path.match(this.filePattern));
|
if ( file ) {
|
||||||
this.loadingFiles = false;
|
this.selectFile(file);
|
||||||
},
|
}
|
||||||
|
}
|
||||||
proxifyUrl(url) {
|
} finally {
|
||||||
// Strip off absolute links to github API
|
this.loadingFiles = false;
|
||||||
if ( url.startsWith(API_BASE) ) {
|
|
||||||
url = url.substr(API_BASE.length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add our proxy prefix
|
|
||||||
url = `/v1/github/${ url.replace(/^\/+/, '') }`;
|
|
||||||
|
|
||||||
// Less pages please
|
|
||||||
addParam(url, 'per_page', 100);
|
|
||||||
|
|
||||||
return url;
|
|
||||||
},
|
|
||||||
|
|
||||||
async apiList(url, { depaginate = true, onPageFn = null, objectKey = 'items' } = {}) {
|
|
||||||
const out = [];
|
|
||||||
|
|
||||||
url = this.proxifyUrl(url);
|
|
||||||
|
|
||||||
while ( true ) {
|
|
||||||
console.log('Github Request:', url);
|
|
||||||
const res = await this.$store.dispatch('rancher/request', { url });
|
|
||||||
const links = parseLinkHeader(res._headers['link']);
|
|
||||||
const scopes = res._headers['x-oauth-scopes'];
|
|
||||||
|
|
||||||
if ( scopes ) {
|
|
||||||
this.scopes = scopes;
|
|
||||||
}
|
|
||||||
|
|
||||||
addObjects(out, isArray(res) ? res : res[objectKey]);
|
|
||||||
|
|
||||||
if ( onPageFn ) {
|
|
||||||
onPageFn(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( depaginate && links.next ) {
|
|
||||||
url = this.proxifyUrl(links.next);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -206,7 +226,7 @@ export default {
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col span-4">
|
<div class="col span-4">
|
||||||
<v-select
|
<v-select
|
||||||
:placeholder="loadingRecentRepos ? 'Loading...' : 'Choose a Repository...'"
|
:placeholder="repoPlaceholder"
|
||||||
:disabled="loadingRecentRepos"
|
:disabled="loadingRecentRepos"
|
||||||
:options="repos"
|
:options="repos"
|
||||||
label="full_name"
|
label="full_name"
|
||||||
|
|
@ -240,7 +260,7 @@ export default {
|
||||||
<div class="col span-4">
|
<div class="col span-4">
|
||||||
<v-select
|
<v-select
|
||||||
:disabled="!selectedRepo || loadingBranches"
|
:disabled="!selectedRepo || loadingBranches"
|
||||||
placeholder="Choose branch"
|
:placeholder="branchPlaceholder"
|
||||||
:options="branches"
|
:options="branches"
|
||||||
label="name"
|
label="name"
|
||||||
:value="selectedBranch"
|
:value="selectedBranch"
|
||||||
|
|
@ -252,7 +272,7 @@ export default {
|
||||||
<div class="col span-4">
|
<div class="col span-4">
|
||||||
<v-select
|
<v-select
|
||||||
:disabled="!selectedBranch"
|
:disabled="!selectedBranch"
|
||||||
placeholder="Choose file"
|
:placeholder="filePlaceholder"
|
||||||
:options="files"
|
:options="files"
|
||||||
label="path"
|
label="path"
|
||||||
:value="selectedFile"
|
:value="selectedFile"
|
||||||
|
|
|
||||||
|
|
@ -138,18 +138,18 @@ export default {
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col span-12">
|
<div class="col span-12">
|
||||||
<label class="radio">
|
<label class="radio">
|
||||||
<input type="radio" value="forwardOne">
|
<input type="radio" value="forwardOne">
|
||||||
Forward to Service
|
Forward to Service
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="radio">
|
<label class="radio">
|
||||||
<input type="radio" value="forwardMany">
|
<input type="radio" value="forwardMany">
|
||||||
Forward to Multiple Services
|
Forward to Multiple Services
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="radio">
|
<label class="radio">
|
||||||
<input type="radio" value="redirect">
|
<input type="radio" value="redirect">
|
||||||
Redirect
|
Redirect
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -205,15 +205,15 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label>
|
<label>
|
||||||
<input v-model="shouldMirror" type="checkbox" />
|
<input v-model="shouldMirror" type="checkbox" />
|
||||||
Mirror
|
Mirror
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="ml-20">
|
<label class="ml-20">
|
||||||
<input v-model="shouldFault" type="checkbox" />
|
<input v-model="shouldFault" type="checkbox" />
|
||||||
Fault
|
Fault
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="shouldMirror" class="row">
|
<div v-if="shouldMirror" class="row">
|
||||||
<div class="col span-12">
|
<div class="col span-12">
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,12 @@ export default {
|
||||||
|
|
||||||
<div v-if="buildMode === 'github'" class="row">
|
<div v-if="buildMode === 'github'" class="row">
|
||||||
<div class="col span-12">
|
<div class="col span-12">
|
||||||
<GithubPicker v-model="spec.build" file-key="dockefile" />
|
<GithubPicker
|
||||||
|
v-model="spec.build"
|
||||||
|
file-key="dockefile"
|
||||||
|
file-pattern="Dockerfile"
|
||||||
|
preferred-file="Dockerfile"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ export default {
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<AsyncButton v-if="isEdit" key="edit" mode="edit" @click="save" />
|
<AsyncButton v-if="isEdit" key="edit" mode="edit" @click="save" />
|
||||||
<AsyncButton v-if="isCreate" key="create" mode="create" @click="save" />
|
<AsyncButton v-if="isCreate" key="create" mode="create" @click="save" />
|
||||||
<button v-if="!isView" class="btn bg-transparent" @click="done">
|
<button v-if="!isView" type="button" class="btn bg-transparent" @click="done">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
col: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
bestLink() {
|
||||||
|
if ( this.value && this.value.length ) {
|
||||||
|
return this.value[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
protocol() {
|
||||||
|
const link = this.bestLink;
|
||||||
|
|
||||||
|
if ( link ) {
|
||||||
|
const match = link.match(/^([^:]+):\/\//);
|
||||||
|
|
||||||
|
if ( match ) {
|
||||||
|
return match[1];
|
||||||
|
} else {
|
||||||
|
return 'link';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span>
|
||||||
|
<a v-if="bestLink" :href="bestLink" target="_blank" rel="nofollow noopener noreferrer">
|
||||||
|
{{ protocol }}
|
||||||
|
</a>
|
||||||
|
<span v-else class="text-muted">
|
||||||
|
—
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
@ -25,18 +25,24 @@ export default {
|
||||||
|
|
||||||
let desired = 0;
|
let desired = 0;
|
||||||
let current = 0;
|
let current = 0;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
for ( const service of forThisApp ) {
|
for ( const service of forThisApp ) {
|
||||||
const weights = service.weights;
|
const weights = service.weights;
|
||||||
|
|
||||||
desired += weights.desired || 0;
|
desired += weights.desired || 0;
|
||||||
current += weights.current || 0;
|
current += weights.current || 0;
|
||||||
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
desired = Math.max(1, desired);
|
desired = Math.max(1, desired);
|
||||||
current = Math.max(1, current);
|
current = Math.max(1, current);
|
||||||
|
|
||||||
return { desired, current };
|
return {
|
||||||
|
desired,
|
||||||
|
current,
|
||||||
|
count
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
desired() {
|
desired() {
|
||||||
|
|
@ -67,7 +73,10 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<span v-trim-whitespace :class="{'text-muted': current === 100 && desired === 100}">
|
<span v-if="total.count === 1" class="text-muted">
|
||||||
|
—
|
||||||
|
</span>
|
||||||
|
<span v-else v-trim-whitespace :class="{'text-muted': current === 0 && desired === 0}">
|
||||||
{{ current }}%
|
{{ current }}%
|
||||||
</span>
|
</span>
|
||||||
<div v-if="showDesired">
|
<div v-if="showDesired">
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { CONFIG_MAP, SECRET, RIO } from '@/config/types';
|
||||||
import {
|
import {
|
||||||
STATE, NAME, NAMESPACE_NAME, AGE,
|
STATE, NAME, NAMESPACE_NAME, AGE,
|
||||||
RIO_IMAGE, WEIGHT, SCALE,
|
RIO_IMAGE, WEIGHT, SCALE,
|
||||||
KEYS,
|
KEYS, ENDPOINTS,
|
||||||
TARGET, TARGET_KIND,
|
TARGET, TARGET_KIND,
|
||||||
} from '@/config/table-headers';
|
} from '@/config/table-headers';
|
||||||
|
|
||||||
|
|
@ -58,6 +58,7 @@ export const FRIENDLY = {
|
||||||
STATE,
|
STATE,
|
||||||
NAMESPACE_NAME,
|
NAMESPACE_NAME,
|
||||||
RIO_IMAGE,
|
RIO_IMAGE,
|
||||||
|
ENDPOINTS,
|
||||||
WEIGHT,
|
WEIGHT,
|
||||||
SCALE,
|
SCALE,
|
||||||
AGE,
|
AGE,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
// Github repo cache
|
||||||
|
export const GITHUB_REPOS = 'githubRepos';
|
||||||
|
export const _DATE = 'Updated';
|
||||||
|
|
@ -5,7 +5,7 @@ export const STATE = {
|
||||||
label: 'State',
|
label: 'State',
|
||||||
sort: ['stateSort', 'nameSort'],
|
sort: ['stateSort', 'nameSort'],
|
||||||
value: 'stateDisplay',
|
value: 'stateDisplay',
|
||||||
width: 90,
|
width: 75,
|
||||||
default: 'unknown',
|
default: 'unknown',
|
||||||
formatter: 'BadgeState',
|
formatter: 'BadgeState',
|
||||||
};
|
};
|
||||||
|
|
@ -55,8 +55,8 @@ export const AGE = {
|
||||||
value: 'metadata.creationTimestamp',
|
value: 'metadata.creationTimestamp',
|
||||||
sort: ['createdTs', 'nameSort'],
|
sort: ['createdTs', 'nameSort'],
|
||||||
search: false,
|
search: false,
|
||||||
width: 75,
|
|
||||||
formatter: 'LiveDate',
|
formatter: 'LiveDate',
|
||||||
|
width: 75,
|
||||||
align: 'right'
|
align: 'right'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -67,13 +67,22 @@ export const RIO_IMAGE = {
|
||||||
sort: ['imageDisplay', 'nameSort'],
|
sort: ['imageDisplay', 'nameSort'],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ENDPOINTS = {
|
||||||
|
name: 'endpoint',
|
||||||
|
label: 'Endpoint',
|
||||||
|
value: 'status.endpoints',
|
||||||
|
formatter: 'Endpoints',
|
||||||
|
width: 60,
|
||||||
|
align: 'center',
|
||||||
|
};
|
||||||
|
|
||||||
export const SCALE = {
|
export const SCALE = {
|
||||||
name: 'scale',
|
name: 'scale',
|
||||||
label: 'Scale',
|
label: 'Scale',
|
||||||
value: 'scales.desired',
|
value: 'scales.desired',
|
||||||
sort: ['scales.desired', 'nameSort'],
|
sort: ['scales.desired', 'nameSort'],
|
||||||
width: 100,
|
|
||||||
formatter: 'Scale',
|
formatter: 'Scale',
|
||||||
|
width: 60,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -81,9 +90,10 @@ export const WEIGHT = {
|
||||||
name: 'weight',
|
name: 'weight',
|
||||||
label: 'Weight',
|
label: 'Weight',
|
||||||
value: 'status.computedWeight',
|
value: 'status.computedWeight',
|
||||||
width: 100,
|
sort: 'status.computedWeight',
|
||||||
align: 'center',
|
|
||||||
formatter: 'Weight',
|
formatter: 'Weight',
|
||||||
|
width: 60,
|
||||||
|
align: 'center',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SUCCESS = {
|
export const SUCCESS = {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export default {
|
||||||
const theme = this.$store.getters['prefs/get'](THEME);
|
const theme = this.$store.getters['prefs/get'](THEME);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bodyAttrs: { class: `theme-${ theme }` },
|
bodyAttrs: { class: `theme-${ theme } overflow-hidden` },
|
||||||
title: 'Rio Dashboard',
|
title: 'Rio Dashboard',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,15 @@ export default {
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
principalType() {
|
||||||
|
const parts = this.id.replace(/:.*$/, '').split('_', 2);
|
||||||
|
|
||||||
|
if ( parts.length === 2 ) {
|
||||||
|
return parts[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -76,11 +76,11 @@ export default {
|
||||||
|
|
||||||
if ( global ) {
|
if ( global ) {
|
||||||
desired = current;
|
desired = current;
|
||||||
} else if ( this._local.pendingScale >= 0 ) {
|
} else if ( typeof this._local.pendingScale === 'number' ) {
|
||||||
desired = this._local.pendingScale;
|
desired = this._local.pendingScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
const missing = desired - available - unavailable;
|
const missing = Math.max(0, desired - available - unavailable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasStatus,
|
hasStatus,
|
||||||
|
|
@ -158,9 +158,11 @@ export default {
|
||||||
if ( this._local.scaleTimer ) {
|
if ( this._local.scaleTimer ) {
|
||||||
scale = this._local.pendingScale;
|
scale = this._local.pendingScale;
|
||||||
} else {
|
} else {
|
||||||
scale = this.spec.scale;
|
scale = this.scales.desired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scale = scale || 0;
|
||||||
|
|
||||||
this._local.pendingScale = scale + 1;
|
this._local.pendingScale = scale + 1;
|
||||||
this.saveScale();
|
this.saveScale();
|
||||||
};
|
};
|
||||||
|
|
@ -174,12 +176,14 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( this.this._local.scaleTimer ) {
|
if ( this._local.scaleTimer ) {
|
||||||
scale = this._local.pendingScale;
|
scale = this._local.pendingScale;
|
||||||
} else {
|
} else {
|
||||||
scale = this.spec.scale;
|
scale = this.scales.desired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scale = scale || 1;
|
||||||
|
|
||||||
this._local.pendingScale = Math.max(scale - 1, 0);
|
this._local.pendingScale = Math.max(scale - 1, 0);
|
||||||
this.saveScale();
|
this.saveScale();
|
||||||
};
|
};
|
||||||
|
|
@ -193,11 +197,11 @@ export default {
|
||||||
|
|
||||||
this._local.scaleTimer = setTimeout(async() => {
|
this._local.scaleTimer = setTimeout(async() => {
|
||||||
try {
|
try {
|
||||||
await this.patch({
|
await this.patch([{
|
||||||
op: 'replace',
|
op: 'replace',
|
||||||
path: '/spec/scale',
|
path: '/spec/replicas',
|
||||||
value: this._local.pendingScale
|
value: this._local.pendingScale
|
||||||
});
|
}]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.$dispatch('growl/fromError', { title: 'Error updating scale', err }, { root: true });
|
this.$dispatch('growl/fromError', { title: 'Error updating scale', err }, { root: true });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ export default {
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.login {
|
.login {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import AsyncButton from '@/components/AsyncButton';
|
||||||
import { SETUP, STEP, _DELETE } from '@/config/query-params';
|
import { SETUP, STEP, _DELETE } from '@/config/query-params';
|
||||||
import { RANCHER } from '@/config/types';
|
import { RANCHER } from '@/config/types';
|
||||||
import { open, popupWindowOptions } from '@/utils/window';
|
import { open, popupWindowOptions } from '@/utils/window';
|
||||||
|
import { findBy, filterBy, addObject } from '@/utils/array';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
layout: 'plain',
|
layout: 'plain',
|
||||||
|
|
@ -15,6 +16,15 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
telemetryTooltip() {
|
||||||
|
return `Rancher Labs would like to collect a bit of anonymized information<br/>
|
||||||
|
about the configuration of your installation to help make Rio better.<br/></br>
|
||||||
|
Your data will not be shared with anyone else, and no information about<br/>
|
||||||
|
what specific resources or endpoints you are deploying is included.<br/>
|
||||||
|
Once enabled you can view exactly what data will be sent at <code>/v1-telemetry</code>.<br/><br/>
|
||||||
|
<a href="https://rancher.com/docs/rancher/v2.x/en/faq/telemetry/" target="_blank">More Info</a>`;
|
||||||
|
},
|
||||||
|
|
||||||
passwordSubmitDisabled() {
|
passwordSubmitDisabled() {
|
||||||
if ( this.useRandom ) {
|
if ( this.useRandom ) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -38,6 +48,18 @@ export default {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
me() {
|
||||||
|
const out = findBy(this.prinicipals, 'me', true);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
},
|
||||||
|
|
||||||
|
orgs() {
|
||||||
|
const out = filterBy(this.principals, 'principalType', 'org');
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async asyncData({ route, req, store }) {
|
async asyncData({ route, req, store }) {
|
||||||
|
|
@ -56,12 +78,19 @@ export default {
|
||||||
opt: { url: '/v3/settings/telemetry-opt' }
|
opt: { url: '/v3/settings/telemetry-opt' }
|
||||||
});
|
});
|
||||||
|
|
||||||
const githubConfig = await store.dispatch('rancher/find', {
|
let githubConfig = await store.dispatch('rancher/find', {
|
||||||
type: RANCHER.AUTH_CONFIG,
|
type: RANCHER.AUTH_CONFIG,
|
||||||
id: 'github',
|
id: 'github',
|
||||||
opt: { url: '/v3/authConfigs/github' }
|
opt: { url: '/v3/authConfigs/github' }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
githubConfig = await store.dispatch('rancher/clone', githubConfig);
|
||||||
|
|
||||||
|
const principals = await store.dispatch('rancher/findAll', {
|
||||||
|
type: RANCHER.PRINCIPAL,
|
||||||
|
opt: { url: '/v3/principals' }
|
||||||
|
});
|
||||||
|
|
||||||
let origin;
|
let origin;
|
||||||
let serverUrl = serverUrlSetting.value;
|
let serverUrl = serverUrlSetting.value;
|
||||||
|
|
||||||
|
|
@ -110,6 +139,8 @@ export default {
|
||||||
hostname: githubConfig.hostname || 'github.com',
|
hostname: githubConfig.hostname || 'github.com',
|
||||||
tls: kind === 'public' || githubConfig.tls,
|
tls: kind === 'public' || githubConfig.tls,
|
||||||
githubError: null,
|
githubError: null,
|
||||||
|
|
||||||
|
principals
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -222,6 +253,25 @@ export default {
|
||||||
description: 'Initial setup session',
|
description: 'Initial setup session',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const githubConfig = await this.$store.dispatch('rancher/find', {
|
||||||
|
type: RANCHER.AUTH_CONFIG,
|
||||||
|
id: 'github',
|
||||||
|
opt: { url: '/v3/authConfigs/github' }
|
||||||
|
});
|
||||||
|
|
||||||
|
this.githubConfig = await this.$store.dispatch('rancher/clone', githubConfig);
|
||||||
|
|
||||||
|
this.githubConfig.allowedPrincipalIds = this.githubConfig.allowedPrincipalIds || [];
|
||||||
|
|
||||||
|
if ( this.me ) {
|
||||||
|
addObject(this.githubConfig.allowedPrincipalIds, this.me.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.principals = await this.$store.dispatch('rancher/findAll', {
|
||||||
|
type: RANCHER.PRINCIPAL,
|
||||||
|
opt: { url: '/v3/principals' }
|
||||||
|
});
|
||||||
|
|
||||||
buttonCb(true);
|
buttonCb(true);
|
||||||
this.step = 4;
|
this.step = 4;
|
||||||
this.$router.applyQuery({ [STEP]: this.step });
|
this.$router.applyQuery({ [STEP]: this.step });
|
||||||
|
|
@ -231,7 +281,19 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
skipGithub() {
|
async setAuthorized(buttonCb) {
|
||||||
|
try {
|
||||||
|
window.z = this.githubConfig;
|
||||||
|
console.log(this.githubConfig);
|
||||||
|
await this.githubConfig.save();
|
||||||
|
buttonCb(true);
|
||||||
|
this.done();
|
||||||
|
} catch (e) {
|
||||||
|
buttonCb(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
done() {
|
||||||
this.$router.replace('/');
|
this.$router.replace('/');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -341,16 +403,11 @@ export default {
|
||||||
<div class="col span-6 offset-3">
|
<div class="col span-6 offset-3">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label>
|
||||||
<input v-model="telemetry" type="checkbox" disabled />
|
<input v-model="telemetry" type="checkbox" />
|
||||||
Allow collection of anonymous statistics (required during beta period).
|
Allow collection of anonymous statistics
|
||||||
</label>
|
</label>
|
||||||
|
<i v-tooltip="{content: telemetryTooltip, placement: 'right', trigger: 'click'}" class="icon icon-info" />
|
||||||
</div>
|
</div>
|
||||||
<p>
|
|
||||||
Rancher Labs would like to collect anonymous information about
|
|
||||||
the configuration of your installation to help make Rio better.
|
|
||||||
Your data will not be shared with anyone else, and no specific
|
|
||||||
resource names or addresses are collected.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -452,7 +509,7 @@ export default {
|
||||||
|
|
||||||
<div class="row mt-20">
|
<div class="row mt-20">
|
||||||
<div class="col span-6 offset-3 text-center" style="font-size: 24pt">
|
<div class="col span-6 offset-3 text-center" style="font-size: 24pt">
|
||||||
<button type="button" class="btn bg-default" @click="skipGithub">
|
<button type="button" class="btn bg-default" @click="done">
|
||||||
Skip
|
Skip
|
||||||
</button>
|
</button>
|
||||||
<AsyncButton key="githubSubmit" mode="continue" :disabled="serverSubmitDisabled" @click="testGithub" />
|
<AsyncButton key="githubSubmit" mode="continue" :disabled="serverSubmitDisabled" @click="testGithub" />
|
||||||
|
|
@ -461,7 +518,58 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="step === 4">
|
<div v-if="step === 4">
|
||||||
Allowed Principals...
|
<div class="text-center mb-40">
|
||||||
|
<div style="width: 50%; margin: 50px auto; height: 300px; padding-top: 100px; border: 1px solid var(--border); text-align: center; background-color: var(--border);">
|
||||||
|
<h1>An even snazzier picture, with octocats</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1>GitHub Integration, Part Deux</h1>
|
||||||
|
<p class="m-20">
|
||||||
|
Who should be able to login?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col span-4 offset-4">
|
||||||
|
<label v-if="me" class="principal">
|
||||||
|
<input type="checkbox" checked disabled />
|
||||||
|
<img :src="me.avatarSrc" width="40" height="40" />
|
||||||
|
<div class="login">
|
||||||
|
{{ me.loginName }}
|
||||||
|
</div>
|
||||||
|
<div class="name">
|
||||||
|
{{ me.name }}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label v-for="org in orgs" :key="org.id" class="principal">
|
||||||
|
<input v-model="githubConfig.allowedPrincipalIds" type="checkbox" :value="org.id" />
|
||||||
|
<img :src="org.avatarSrc" width="40" height="40" />
|
||||||
|
<span class="login">
|
||||||
|
Members of <b>{{ org.loginName }}</b>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-20">
|
||||||
|
<div class="col span-6 offset-3 text-center" style="font-size: 24pt">
|
||||||
|
<AsyncButton key="githubSubmit" mode="done" @click="setAuthorized" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.principal {
|
||||||
|
display: block;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 10px;
|
||||||
|
line-height: 40px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -416,9 +416,9 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
delete this.__rehydrate;
|
|
||||||
|
|
||||||
return (opt = {}) => {
|
return (opt = {}) => {
|
||||||
|
delete this.__rehydrate;
|
||||||
|
|
||||||
if ( !opt.url ) {
|
if ( !opt.url ) {
|
||||||
opt.url = this.linkFor('self');
|
opt.url = this.linkFor('self');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,9 @@ import { randomStr } from '@/utils/string';
|
||||||
import { parse as parseUrl, addParam, addParams } from '@/utils/url';
|
import { parse as parseUrl, addParam, addParams } from '@/utils/url';
|
||||||
import { findBy, addObjects } from '@/utils/array';
|
import { findBy, addObjects } from '@/utils/array';
|
||||||
import { BACK_TO, SPA, AUTH_TEST, _FLAGGED } from '@/config/query-params';
|
import { BACK_TO, SPA, AUTH_TEST, _FLAGGED } from '@/config/query-params';
|
||||||
|
import { BASE_SCOPES } from '@/store/github';
|
||||||
|
|
||||||
const KEY = 'rc_nonce';
|
const KEY = 'rc_nonce';
|
||||||
const BASE_SCOPES = ['read:user', 'read:org', 'user:email'];
|
|
||||||
|
|
||||||
export const EXTENDED_SCOPES = ['repo'];
|
|
||||||
|
|
||||||
const ERR_NONCE = 'nonce';
|
const ERR_NONCE = 'nonce';
|
||||||
const ERR_CLIENT = 'client';
|
const ERR_CLIENT = 'client';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,182 @@
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { addParam, parseLinkHeader } from '@/utils/url';
|
||||||
|
import { addObjects, isArray } from '@/utils/array';
|
||||||
|
import { GITHUB_REPOS, _DATE } from '@/config/local-storage';
|
||||||
|
|
||||||
|
const API_BASE = 'https://api.github.com/';
|
||||||
|
|
||||||
|
export const BASE_SCOPES = ['read:user', 'read:org', 'user:email'];
|
||||||
|
export const EXTENDED_SCOPES = ['repo'];
|
||||||
|
|
||||||
|
export const DOCKERFILE = /^Dockerfile(\..*)?$/i;
|
||||||
|
export const YAML_FILE = /^.*\.ya?ml$/i;
|
||||||
|
|
||||||
|
function getCached() {
|
||||||
|
if ( process.server ) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const cached = window.localStorage.getItem(GITHUB_REPOS);
|
||||||
|
|
||||||
|
if ( cached ) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(cached);
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasCached() {
|
||||||
|
if ( process.server ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cached = window.localStorage.getItem(GITHUB_REPOS);
|
||||||
|
|
||||||
|
return !!cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cacheExpired() {
|
||||||
|
if ( process.server ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = window.localStorage.getItem(GITHUB_REPOS + _DATE);
|
||||||
|
|
||||||
|
if ( updated && dayjs().diff(updated) <= 60 * 60 * 1000 ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCache(repos) {
|
||||||
|
window.localStorage.setItem(GITHUB_REPOS, JSON.stringify(repos));
|
||||||
|
window.localStorage.setItem(GITHUB_REPOS + _DATE, (new Date()).toISOString());
|
||||||
|
}
|
||||||
|
|
||||||
|
function proxifyUrl(url) {
|
||||||
|
// Strip off absolute links to github API
|
||||||
|
if ( url.startsWith(API_BASE) ) {
|
||||||
|
url = url.substr(API_BASE.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add our proxy prefix
|
||||||
|
url = `/v1/github/${ url.replace(/^\/+/, '') }`;
|
||||||
|
|
||||||
|
// Less pages please
|
||||||
|
addParam(url, 'per_page', 100);
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const state = function() {
|
||||||
|
return {
|
||||||
|
repos: [],
|
||||||
|
scopes: []
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
async apiList({ commit, dispatch }, {
|
||||||
|
url = null, depaginate = true, onPageFn = null, objectKey = 'items'
|
||||||
|
} = {}) {
|
||||||
|
const out = [];
|
||||||
|
|
||||||
|
url = proxifyUrl(url);
|
||||||
|
|
||||||
|
while ( true ) {
|
||||||
|
console.log('Github Request:', url);
|
||||||
|
const res = await dispatch('rancher/request', { url }, { root: true });
|
||||||
|
const links = parseLinkHeader(res._headers['link']);
|
||||||
|
|
||||||
|
const scopes = res._headers['x-oauth-scopes'];
|
||||||
|
|
||||||
|
if ( scopes ) {
|
||||||
|
commit('setScopes', scopes);
|
||||||
|
}
|
||||||
|
|
||||||
|
addObjects(out, isArray(res) ? res : res[objectKey]);
|
||||||
|
|
||||||
|
if ( onPageFn ) {
|
||||||
|
onPageFn(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( depaginate && links.next ) {
|
||||||
|
url = proxifyUrl(links.next);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchRecentRepos({ commit, dispatch }, { allowCache = true } = {}) {
|
||||||
|
if ( allowCache && hasCached ) {
|
||||||
|
const cached = getCached();
|
||||||
|
|
||||||
|
if ( cacheExpired() ) {
|
||||||
|
dispatch('fetchRecentRepos', { allowCache: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await dispatch('apiList', { url: '/user/repos?sort=updated', depaginate: false });
|
||||||
|
|
||||||
|
commit('setRepos', res.slice());
|
||||||
|
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
|
||||||
|
async searchRepos({ state, dispatch }, { search }) {
|
||||||
|
if ( !search ) {
|
||||||
|
return state.repos.slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await dispatch('apiList', {
|
||||||
|
url: `/search/repositories?q=${ escape(search) }`,
|
||||||
|
depaginate: false
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchBranches({ dispatch }, { repo }) {
|
||||||
|
const url = repo.branches_url.replace('{/branch}', '');
|
||||||
|
const res = await dispatch('apiList', { url });
|
||||||
|
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchFiles({ dispatch }, { repo, branch, pattern = null }) {
|
||||||
|
let url = repo.trees_url.replace('{/sha}', `/${ branch.commit.sha }`);
|
||||||
|
|
||||||
|
url = addParam(url, 'recursive', 1);
|
||||||
|
|
||||||
|
const res = await dispatch('apiList', { url, objectKey: 'tree' });
|
||||||
|
|
||||||
|
if ( !pattern ) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
const out = res.filter(file => file.type === 'blob' && file.path.match(pattern));
|
||||||
|
|
||||||
|
return out;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mutations = {
|
||||||
|
setScopes(state, scopes) {
|
||||||
|
state.scopes = scopes;
|
||||||
|
},
|
||||||
|
|
||||||
|
setRepos(state, repos) {
|
||||||
|
state.repos = repos;
|
||||||
|
setCache(repos);
|
||||||
|
},
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue