mirror of
https://github.com/m1k1o/neko-rooms.git
synced 2026-11-02 22:51:34 -06:00
pull images.
This commit is contained in:
+97
@@ -221,6 +221,51 @@ paths:
|
|||||||
'500':
|
'500':
|
||||||
description: Internal server error
|
description: Internal server error
|
||||||
|
|
||||||
|
/api/pull:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- default
|
||||||
|
summary: Get pull status
|
||||||
|
operationId: pullStatus
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PullStatus'
|
||||||
|
'500':
|
||||||
|
description: Internal server error
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- default
|
||||||
|
summary: Start new pull
|
||||||
|
operationId: pullStart
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PullStart'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PullStatus'
|
||||||
|
'500':
|
||||||
|
description: Internal server error
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- default
|
||||||
|
summary: Stop existing pull in progress
|
||||||
|
operationId: pullStop
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: OK
|
||||||
|
'500':
|
||||||
|
description: Internal server error
|
||||||
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
RoomsConfig:
|
RoomsConfig:
|
||||||
@@ -462,3 +507,55 @@ components:
|
|||||||
url:
|
url:
|
||||||
type: string
|
type: string
|
||||||
example: https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi
|
example: https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi
|
||||||
|
|
||||||
|
PullStart:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
neko_image:
|
||||||
|
type: string
|
||||||
|
example: m1k1o/neko:latest
|
||||||
|
registry_user:
|
||||||
|
type: string
|
||||||
|
registry_pass:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
PullLayer:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
example: m1k1o/neko:latest
|
||||||
|
progressDetail:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
current:
|
||||||
|
type: number
|
||||||
|
total:
|
||||||
|
type: number
|
||||||
|
progress:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
PullStatus:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
active:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
started:
|
||||||
|
type: string
|
||||||
|
format: datetime
|
||||||
|
example: "2021-03-07T21:56:34Z"
|
||||||
|
layers:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
$ref: '#/components/schemas/PullLayer'
|
||||||
|
status:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
finished:
|
||||||
|
type: string
|
||||||
|
format: datetime
|
||||||
|
example: "2021-03-07T21:56:34Z"
|
||||||
|
|||||||
@@ -100,6 +100,118 @@ export interface BrowserPolicyExtension {
|
|||||||
*/
|
*/
|
||||||
'url'?: string;
|
'url'?: string;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface PullLayer
|
||||||
|
*/
|
||||||
|
export interface PullLayer {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PullLayer
|
||||||
|
*/
|
||||||
|
'status'?: string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {PullLayerProgressDetail}
|
||||||
|
* @memberof PullLayer
|
||||||
|
*/
|
||||||
|
'progressDetail'?: PullLayerProgressDetail;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PullLayer
|
||||||
|
*/
|
||||||
|
'progress'?: string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PullLayer
|
||||||
|
*/
|
||||||
|
'id'?: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface PullLayerProgressDetail
|
||||||
|
*/
|
||||||
|
export interface PullLayerProgressDetail {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof PullLayerProgressDetail
|
||||||
|
*/
|
||||||
|
'current'?: number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof PullLayerProgressDetail
|
||||||
|
*/
|
||||||
|
'total'?: number;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface PullStart
|
||||||
|
*/
|
||||||
|
export interface PullStart {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PullStart
|
||||||
|
*/
|
||||||
|
'neko_image'?: string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PullStart
|
||||||
|
*/
|
||||||
|
'registry_user'?: string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PullStart
|
||||||
|
*/
|
||||||
|
'registry_pass'?: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface PullStatus
|
||||||
|
*/
|
||||||
|
export interface PullStatus {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof PullStatus
|
||||||
|
*/
|
||||||
|
'active'?: boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PullStatus
|
||||||
|
*/
|
||||||
|
'started'?: string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {{ [key: string]: PullLayer; }}
|
||||||
|
* @memberof PullStatus
|
||||||
|
*/
|
||||||
|
'layers'?: { [key: string]: PullLayer; };
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {Array<string>}
|
||||||
|
* @memberof PullStatus
|
||||||
|
*/
|
||||||
|
'status'?: Array<string>;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PullStatus
|
||||||
|
*/
|
||||||
|
'finished'?: string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
@@ -577,6 +689,231 @@ export class ConfigApi extends BaseAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DefaultApi - axios parameter creator
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Start new pull
|
||||||
|
* @param {PullStart} [pullStart]
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
pullStart: async (pullStart?: PullStart, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
const localVarPath = `/api/pull`;
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
localVarRequestOptions.data = serializeDataIfNeeded(pullStart, localVarRequestOptions, configuration)
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Get pull status
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
pullStatus: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
const localVarPath = `/api/pull`;
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Stop existing pull in progress
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
pullStop: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
const localVarPath = `/api/pull`;
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DefaultApi - functional programming interface
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const DefaultApiFp = function(configuration?: Configuration) {
|
||||||
|
const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration)
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Start new pull
|
||||||
|
* @param {PullStart} [pullStart]
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async pullStart(pullStart?: PullStart, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PullStatus>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.pullStart(pullStart, options);
|
||||||
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Get pull status
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async pullStatus(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PullStatus>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.pullStatus(options);
|
||||||
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Stop existing pull in progress
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async pullStop(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.pullStop(options);
|
||||||
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DefaultApi - factory interface
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
||||||
|
const localVarFp = DefaultApiFp(configuration)
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Start new pull
|
||||||
|
* @param {PullStart} [pullStart]
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
pullStart(pullStart?: PullStart, options?: any): AxiosPromise<PullStatus> {
|
||||||
|
return localVarFp.pullStart(pullStart, options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Get pull status
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
pullStatus(options?: any): AxiosPromise<PullStatus> {
|
||||||
|
return localVarFp.pullStatus(options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Stop existing pull in progress
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
pullStop(options?: any): AxiosPromise<void> {
|
||||||
|
return localVarFp.pullStop(options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DefaultApi - object-oriented interface
|
||||||
|
* @export
|
||||||
|
* @class DefaultApi
|
||||||
|
* @extends {BaseAPI}
|
||||||
|
*/
|
||||||
|
export class DefaultApi extends BaseAPI {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Start new pull
|
||||||
|
* @param {PullStart} [pullStart]
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof DefaultApi
|
||||||
|
*/
|
||||||
|
public pullStart(pullStart?: PullStart, options?: AxiosRequestConfig) {
|
||||||
|
return DefaultApiFp(this.configuration).pullStart(pullStart, options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Get pull status
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof DefaultApi
|
||||||
|
*/
|
||||||
|
public pullStatus(options?: AxiosRequestConfig) {
|
||||||
|
return DefaultApiFp(this.configuration).pullStatus(options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Stop existing pull in progress
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof DefaultApi
|
||||||
|
*/
|
||||||
|
public pullStop(options?: AxiosRequestConfig) {
|
||||||
|
return DefaultApiFp(this.configuration).pullStop(options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RoomsApi - axios parameter creator
|
* RoomsApi - axios parameter creator
|
||||||
* @export
|
* @export
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
<template>
|
||||||
|
<span>
|
||||||
|
<v-btn
|
||||||
|
@click="dialog = true"
|
||||||
|
:loading="loading"
|
||||||
|
color="info"
|
||||||
|
dark
|
||||||
|
>
|
||||||
|
<v-icon class="mr-2" color="white">mdi-cloud-download-outline</v-icon> Pull neko images
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-dialog v-model="dialog" max-width="920px">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title class="headline">
|
||||||
|
Pull neko images
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<template v-if="status.layers.length > 0">
|
||||||
|
<pre v-for="layer in status.layers" :key="layer.id">{{ layer.id }} {{ layer.progress && layer.progress + ' ' }}{{ layer.status }}</pre>
|
||||||
|
<br />
|
||||||
|
</template>
|
||||||
|
<template v-if="status.status.length > 0">
|
||||||
|
<pre v-for="text in status.status" :key="text">{{ text }}</pre>
|
||||||
|
</template>
|
||||||
|
<pre v-else>Preparing docker image pull</pre>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<template v-if="!status.active">
|
||||||
|
<v-select
|
||||||
|
v-model="nekoImage"
|
||||||
|
:items="nekoImages"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
hide-details
|
||||||
|
label="Neko image"
|
||||||
|
></v-select>
|
||||||
|
<v-btn color="green" class="ml-2" :loading="loading" @click="Start">
|
||||||
|
Start
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn v-if="status.active" color="red" text :loading="loading" @click="Stop">
|
||||||
|
Stop
|
||||||
|
</v-btn>
|
||||||
|
<v-btn v-else color="grey" text @click="dialog = false">
|
||||||
|
Close
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Vue, Component } from 'vue-property-decorator'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {}
|
||||||
|
})
|
||||||
|
export default class PUll extends Vue {
|
||||||
|
private dialog = false
|
||||||
|
private loading = false
|
||||||
|
private nekoImage = ''
|
||||||
|
|
||||||
|
get status() {
|
||||||
|
return this.$store.state.pullStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
get nekoImages() {
|
||||||
|
return this.$store.state.roomsConfig.neko_images
|
||||||
|
}
|
||||||
|
|
||||||
|
async Start() {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('PULL_START', this.nekoImage)
|
||||||
|
} catch(e) {
|
||||||
|
if (e.response) {
|
||||||
|
this.$swal({
|
||||||
|
title: 'Server error',
|
||||||
|
text: e.response.data,
|
||||||
|
icon: 'error',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.$swal({
|
||||||
|
title: 'Network error',
|
||||||
|
text: String(e),
|
||||||
|
icon: 'error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Stop() {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('PULL_STOP')
|
||||||
|
} catch(e) {
|
||||||
|
if (e.response) {
|
||||||
|
this.$swal({
|
||||||
|
title: 'Server error',
|
||||||
|
text: e.response.data,
|
||||||
|
icon: 'error',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.$swal({
|
||||||
|
title: 'Network error',
|
||||||
|
text: String(e),
|
||||||
|
icon: 'error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
this.dialog = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interval!: number
|
||||||
|
|
||||||
|
async mounted() {
|
||||||
|
await this.$store.dispatch('PULL_STATUS')
|
||||||
|
|
||||||
|
this.interval = window.setInterval(async () => {
|
||||||
|
if (!this.status.active) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('PULL_STATUS')
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
window.clearInterval(this.interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -9,6 +9,8 @@ import {
|
|||||||
RoomSettings,
|
RoomSettings,
|
||||||
RoomStats,
|
RoomStats,
|
||||||
RoomsApi,
|
RoomsApi,
|
||||||
|
DefaultApi,
|
||||||
|
PullStatus,
|
||||||
} from '@/api/index'
|
} from '@/api/index'
|
||||||
|
|
||||||
import { state, State } from './state'
|
import { state, State } from './state'
|
||||||
@@ -21,6 +23,7 @@ const configuration = new Configuration({
|
|||||||
|
|
||||||
const configApi = new ConfigApi(configuration)
|
const configApi = new ConfigApi(configuration)
|
||||||
const roomsApi = new RoomsApi(configuration)
|
const roomsApi = new RoomsApi(configuration)
|
||||||
|
const defaultApi = new DefaultApi(configuration)
|
||||||
|
|
||||||
export default new Vuex.Store({
|
export default new Vuex.Store({
|
||||||
state,
|
state,
|
||||||
@@ -55,6 +58,9 @@ export default new Vuex.Store({
|
|||||||
const roomEntries = state.rooms.filter(({ id }) => id != roomId)
|
const roomEntries = state.rooms.filter(({ id }) => id != roomId)
|
||||||
Vue.set(state, 'rooms', roomEntries)
|
Vue.set(state, 'rooms', roomEntries)
|
||||||
},
|
},
|
||||||
|
PULL_STATUS(state: State, pullStatus: PullStatus) {
|
||||||
|
Vue.set(state, 'pullStatus', pullStatus)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async ROOMS_CONFIG({ commit }: ActionContext<State, State>) {
|
async ROOMS_CONFIG({ commit }: ActionContext<State, State>) {
|
||||||
@@ -112,6 +118,23 @@ export default new Vuex.Store({
|
|||||||
commit('ROOMS_PUT', res.data)
|
commit('ROOMS_PUT', res.data)
|
||||||
return res.data
|
return res.data
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async PULL_START({ commit }: ActionContext<State, State>, nekoImage: string) {
|
||||||
|
const res = await defaultApi.pullStart({
|
||||||
|
// eslint-disable-next-line
|
||||||
|
neko_image: nekoImage,
|
||||||
|
})
|
||||||
|
commit('PULL_STATUS', res.data)
|
||||||
|
return res.data
|
||||||
|
},
|
||||||
|
async PULL_STATUS({ commit }: ActionContext<State, State>) {
|
||||||
|
const res = await defaultApi.pullStatus()
|
||||||
|
commit('PULL_STATUS', res.data)
|
||||||
|
},
|
||||||
|
async PULL_STOP() {
|
||||||
|
const res = await defaultApi.pullStop()
|
||||||
|
return res.data
|
||||||
|
},
|
||||||
},
|
},
|
||||||
modules: {
|
modules: {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
BrowserPolicyContent,
|
BrowserPolicyContent,
|
||||||
BrowserPolicyTypeEnum,
|
BrowserPolicyTypeEnum,
|
||||||
BrowserPolicyExtension,
|
BrowserPolicyExtension,
|
||||||
|
PullStatus,
|
||||||
} from '@/api/index'
|
} from '@/api/index'
|
||||||
|
|
||||||
export interface BrowserPolicyConfig {
|
export interface BrowserPolicyConfig {
|
||||||
@@ -208,6 +209,13 @@ export const state = {
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
persistent_data: false,
|
persistent_data: false,
|
||||||
} as BrowserPolicyContent,
|
} as BrowserPolicyContent,
|
||||||
|
|
||||||
|
//
|
||||||
|
// default
|
||||||
|
//
|
||||||
|
pullStatus: {
|
||||||
|
active: false,
|
||||||
|
} as PullStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State = typeof state
|
export type State = typeof state
|
||||||
|
|||||||
@@ -58,6 +58,10 @@
|
|||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
<RoomsList :loading="loading" />
|
<RoomsList :loading="loading" />
|
||||||
|
|
||||||
|
<div class="mt-5 text-center">
|
||||||
|
<Pull />
|
||||||
|
</div>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -66,6 +70,7 @@ import { Component, Vue, Watch } from 'vue-property-decorator'
|
|||||||
import RoomsList from '@/components/RoomsList.vue'
|
import RoomsList from '@/components/RoomsList.vue'
|
||||||
import RoomsQuick from '@/components/RoomsQuick.vue'
|
import RoomsQuick from '@/components/RoomsQuick.vue'
|
||||||
import RoomsCreate from '@/components/RoomsCreate.vue'
|
import RoomsCreate from '@/components/RoomsCreate.vue'
|
||||||
|
import Pull from '@/components/Pull.vue'
|
||||||
import { RoomEntry } from '@/api/index'
|
import { RoomEntry } from '@/api/index'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -73,6 +78,7 @@ import { RoomEntry } from '@/api/index'
|
|||||||
RoomsList,
|
RoomsList,
|
||||||
RoomsQuick,
|
RoomsQuick,
|
||||||
RoomsCreate,
|
RoomsCreate,
|
||||||
|
Pull,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
export default class Home extends Vue {
|
export default class Home extends Vue {
|
||||||
|
|||||||
@@ -41,4 +41,10 @@ func (manager *ApiManagerCtx) Mount(r chi.Router) {
|
|||||||
r.Post("/restart", manager.roomGenericAction(manager.rooms.Restart))
|
r.Post("/restart", manager.roomGenericAction(manager.rooms.Restart))
|
||||||
r.Post("/recreate", manager.roomRecreate)
|
r.Post("/recreate", manager.roomRecreate)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
r.Route("/pull", func(r chi.Router) {
|
||||||
|
r.Get("/", manager.roomPullStatus)
|
||||||
|
r.Post("/", manager.roomPullStart)
|
||||||
|
r.Delete("/", manager.roomPullStop)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,3 +169,38 @@ func (manager *ApiManagerCtx) roomGenericAction(action func(id string) error) fu
|
|||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (manager *ApiManagerCtx) roomPullStart(w http.ResponseWriter, r *http.Request) {
|
||||||
|
request := types.PullStart{}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||||
|
http.Error(w, err.Error(), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := manager.rooms.PullStart(request)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := manager.rooms.PullStatus()
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *ApiManagerCtx) roomPullStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response := manager.rooms.PullStatus()
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *ApiManagerCtx) roomPullStop(w http.ResponseWriter, r *http.Request) {
|
||||||
|
err := manager.rooms.PullStop()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ type RoomManagerCtx struct {
|
|||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
config *config.Room
|
config *config.Room
|
||||||
client *dockerClient.Client
|
client *dockerClient.Client
|
||||||
|
pull PullConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (manager *RoomManagerCtx) Config() types.RoomsConfig {
|
func (manager *RoomManagerCtx) Config() types.RoomsConfig {
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
package room
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"m1k1o/neko_rooms/internal/types"
|
||||||
|
"m1k1o/neko_rooms/internal/utils"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
dockerTypes "github.com/docker/docker/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PullConfig struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
cancel func()
|
||||||
|
status types.PullStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pull *PullConfig) TryInitialize(cancel func()) bool {
|
||||||
|
pull.mu.Lock()
|
||||||
|
defer pull.mu.Unlock()
|
||||||
|
|
||||||
|
if pull.status.Active {
|
||||||
|
cancel()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
pull.cancel = cancel
|
||||||
|
|
||||||
|
pull.status = types.PullStatus{
|
||||||
|
Active: true,
|
||||||
|
Started: &now,
|
||||||
|
Layers: map[string]types.PullLayer{},
|
||||||
|
Status: []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pull *PullConfig) SetDone() {
|
||||||
|
pull.mu.Lock()
|
||||||
|
defer pull.mu.Unlock()
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
pull.status.Active = false
|
||||||
|
pull.status.Finished = &now
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pull *PullConfig) Stop() bool {
|
||||||
|
pull.mu.Lock()
|
||||||
|
defer pull.mu.Unlock()
|
||||||
|
|
||||||
|
if pull.status.Active {
|
||||||
|
pull.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
return pull.status.Active
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pull *PullConfig) Get() types.PullStatus {
|
||||||
|
pull.mu.Lock()
|
||||||
|
defer pull.mu.Unlock()
|
||||||
|
|
||||||
|
return pull.status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *RoomManagerCtx) PullStart(request types.PullStart) error {
|
||||||
|
if in, _ := utils.ArrayIn(request.NekoImage, manager.config.NekoImages); !in {
|
||||||
|
return fmt.Errorf("unknown neko image")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
if !manager.pull.TryInitialize(cancel) {
|
||||||
|
return fmt.Errorf("pull is already in progess")
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle registry auth
|
||||||
|
var opts dockerTypes.ImagePullOptions
|
||||||
|
if request.RegistryUser != "" && request.RegistryPass != "" {
|
||||||
|
authConfig := dockerTypes.AuthConfig{
|
||||||
|
Username: request.RegistryUser,
|
||||||
|
Password: request.RegistryPass,
|
||||||
|
}
|
||||||
|
|
||||||
|
encodedJSON, err := json.Marshal(authConfig)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts = dockerTypes.ImagePullOptions{
|
||||||
|
RegistryAuth: base64.URLEncoding.EncodeToString(encodedJSON),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := manager.client.ImagePull(ctx, request.NekoImage, opts)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
manager.pull.SetDone()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
for scanner.Scan() {
|
||||||
|
data := scanner.Bytes()
|
||||||
|
|
||||||
|
layer := types.PullLayer{}
|
||||||
|
if err := json.Unmarshal(data, &layer); err != nil {
|
||||||
|
manager.pull.status.Status = append(
|
||||||
|
manager.pull.status.Status,
|
||||||
|
fmt.Sprintf("Error while parsing pull response: %s", err),
|
||||||
|
)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if layer.ProgressDetail != nil {
|
||||||
|
manager.pull.status.Layers[layer.ID] = layer
|
||||||
|
} else {
|
||||||
|
manager.pull.status.Status = append(
|
||||||
|
manager.pull.status.Status,
|
||||||
|
layer.Status,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
manager.pull.status.Status = append(
|
||||||
|
manager.pull.status.Status,
|
||||||
|
fmt.Sprintf("Error while reading pull response: %s", err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.Close()
|
||||||
|
manager.pull.SetDone()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *RoomManagerCtx) PullStop() error {
|
||||||
|
if !manager.pull.Stop() {
|
||||||
|
return fmt.Errorf("pull is not in progess")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *RoomManagerCtx) PullStatus() types.PullStatus {
|
||||||
|
return manager.pull.Get()
|
||||||
|
}
|
||||||
@@ -245,6 +245,30 @@ type RoomMember struct {
|
|||||||
Muted bool `json:"muted"`
|
Muted bool `json:"muted"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PullStart struct {
|
||||||
|
NekoImage string `json:"neko_image"`
|
||||||
|
RegistryUser string `json:"registry_user"`
|
||||||
|
RegistryPass string `json:"registry_pass"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PullLayer struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
ProgressDetail *struct {
|
||||||
|
Current int `json:"current"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
} `json:"progressDetail"`
|
||||||
|
Progress string `json:"progress"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PullStatus struct {
|
||||||
|
Active bool `json:"active"`
|
||||||
|
Started *time.Time `json:"started"`
|
||||||
|
Layers map[string]PullLayer `json:"layers"`
|
||||||
|
Status []string `json:"status"`
|
||||||
|
Finished *time.Time `json:"finished"`
|
||||||
|
}
|
||||||
|
|
||||||
type RoomManager interface {
|
type RoomManager interface {
|
||||||
Config() RoomsConfig
|
Config() RoomsConfig
|
||||||
List() ([]RoomEntry, error)
|
List() ([]RoomEntry, error)
|
||||||
@@ -259,4 +283,8 @@ type RoomManager interface {
|
|||||||
Start(id string) error
|
Start(id string) error
|
||||||
Stop(id string) error
|
Stop(id string) error
|
||||||
Restart(id string) error
|
Restart(id string) error
|
||||||
|
|
||||||
|
PullStart(request PullStart) error
|
||||||
|
PullStop() error
|
||||||
|
PullStatus() PullStatus
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user