3 Commits

Author SHA1 Message Date
Miroslav Šedivý c45f17fa1f fix api. 2022-10-30 11:33:38 +01:00
brian 1205dc9587 Merge branch 'master' of https://github.com/m1k1o/neko-rooms into room_snapshots 2022-10-15 04:23:37 +00:00
brian fa401571e0 Add snapshot command 2022-10-15 04:09:44 +00:00
9 changed files with 220 additions and 4 deletions
+31 -1
View File
@@ -220,7 +220,27 @@ paths:
$ref: '#/components/schemas/RoomEntry'
'500':
description: Internal server error
/api/rooms/{roomId}/snapshot:
post:
tags:
- rooms
summary: Creates a room snapshot
operationId: roomSnapshot
parameters:
- in: path
name: roomId
required: true
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/RoomSnapshot'
'500':
description: Internal server error
/api/pull:
get:
tags:
@@ -477,6 +497,16 @@ components:
type: boolean
example: false
RoomSnapshot:
type: object
properties:
id:
type: string
example: bc04dace10
snapshot:
type: string
example: image/tag:20220201102022
BrowserPolicy:
type: object
properties:
+90
View File
@@ -597,6 +597,30 @@ export interface RoomsConfig {
*/
'uses_mux'?: boolean;
}
/**
*
* @export
* @interface RoomSnapshot
*/
export interface RoomSnapshot {
/**
*
* @type {string}
* @memberof RoomSnapshot
*/
'id'?: string;
/**
*
* @type {string}
* @memberof RoomSnapshot
*/
'name'?: string;
/**
*
* @type {string}
* @memberof RoomSnapshot
*/
}
/**
* ConfigApi - axios parameter creator
@@ -1254,6 +1278,39 @@ export const RoomsApiAxiosParamCreator = function (configuration?: Configuration
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @summary Stop room
* @param {string} roomId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
roomSnapshot: async (roomId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'roomId' is not null or undefined
assertParamExists('roomSnapshot', 'roomId', roomId)
const localVarPath = `/api/rooms/{roomId}/snapshot`
.replace(`{${"roomId"}}`, encodeURIComponent(String(roomId)));
// 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;
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
@@ -1382,6 +1439,17 @@ export const RoomsApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.roomsList(options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @summary Creates a room snapshot
* @param {string} roomId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async roomSnapshot(roomId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<RoomEntry>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.roomSnapshot(roomId, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
}
};
@@ -1491,6 +1559,16 @@ export const RoomsApiFactory = function (configuration?: Configuration, basePath
roomsList(options?: any): AxiosPromise<Array<RoomEntry>> {
return localVarFp.roomsList(options).then((request) => request(axios, basePath));
},
/**
*
* @summary Creates a room snapshot
* @param {string} roomId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
roomSnapshot(roomId: string, options?: any): AxiosPromise<RoomEntry> {
return localVarFp.roomSnapshot(roomId, options).then((request) => request(axios, basePath));
},
};
};
@@ -1619,6 +1697,18 @@ export class RoomsApi extends BaseAPI {
public roomsList(options?: AxiosRequestConfig) {
return RoomsApiFp(this.configuration).roomsList(options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary Creates a room snapshot
* @param {string} roomId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof RoomsApi
*/
public roomSnapshot(roomId: string, options?: AxiosRequestConfig) {
return RoomsApiFp(this.configuration).roomSnapshot(roomId, options).then((request) => request(this.axios, this.basePath));
}
}
+8
View File
@@ -61,6 +61,14 @@ export default class RoomActionBtn extends Vue {
color: 'red',
icon: 'mdi-trash-can-outline',
}
case 'snapshot':
return {
dispatch: 'ROOMS_SNAPSHOT',
msg: 'Room snapshot created!',
tooltip: 'Snapshot',
color: 'blue',
icon: 'mdi-content-save',
}
}
return undefined
+1
View File
@@ -46,6 +46,7 @@
<RoomActionBtn action="start" :roomId="item.id" :disabled="item.running" />
<RoomActionBtn action="stop" :roomId="item.id" :disabled="!item.running" />
<RoomActionBtn action="restart" :roomId="item.id" :disabled="!item.running" />
<RoomActionBtn action="snapshot" :roomId="item.id" :disabled="!item.running" />
</template>
<template v-slot:[`item.destroy`]="{ item }">
<RoomActionBtn action="remove" :roomId="item.id" />
+12
View File
@@ -11,6 +11,7 @@ import {
RoomsApi,
DefaultApi,
PullStatus,
RoomSnapshot,
} from '@/api/index'
import { state, State } from './state'
@@ -61,6 +62,9 @@ export default new Vuex.Store({
PULL_STATUS(state: State, pullStatus: PullStatus) {
Vue.set(state, 'pullStatus', pullStatus)
},
ROOMS_SNAPSHOT_ADD(state: State, snapshot: RoomSnapshot) {
Vue.set(state, 'snapshot', snapshot)
},
},
actions: {
async ROOMS_CONFIG({ commit }: ActionContext<State, State>) {
@@ -135,6 +139,14 @@ export default new Vuex.Store({
const res = await defaultApi.pullStop()
return res.data
},
async ROOMS_SNAPSHOT({ commit }: ActionContext<State, State>, roomId: string) {
const res = await roomsApi.roomSnapshot(roomId)
commit('ROOMS_SNAPSHOT_ADD', {
roomId: roomId,
data: res.data
});
return res.data
},
},
modules: {
}
+1
View File
@@ -57,5 +57,6 @@ func (manager *ApiManagerCtx) Mount(r chi.Router) {
r.Post("/stop", manager.roomGenericAction(manager.rooms.Stop))
r.Post("/restart", manager.roomGenericAction(manager.rooms.Restart))
r.Post("/recreate", manager.roomRecreate)
r.Post("/snapshot", manager.roomSnapshot)
})
}
+12
View File
@@ -150,3 +150,15 @@ func (manager *ApiManagerCtx) roomGenericAction(action func(id string) error) fu
w.WriteHeader(http.StatusNoContent)
}
}
func (manager *ApiManagerCtx) roomSnapshot(w http.ResponseWriter, r *http.Request) {
roomId := chi.URLParam(r, "roomId")
response, err := manager.rooms.Snapshot(roomId)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
+59 -3
View File
@@ -8,8 +8,10 @@ import (
"path"
"path/filepath"
"strings"
"time"
dockerTypes "github.com/docker/docker/api/types"
containerTypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/container"
dockerMount "github.com/docker/docker/api/types/mount"
network "github.com/docker/docker/api/types/network"
@@ -87,6 +89,21 @@ func (manager *RoomManagerCtx) FindByName(name string) (*types.RoomEntry, error)
return manager.containerToEntry(*container)
}
func (manager *RoomManagerCtx) IsValidImage(imageName string, imageList []string) bool {
for _, _nekoImage := range imageList {
if _nekoImage == imageName {
return true
}
if strings.Contains(_nekoImage, "*") {
pattern := strings.Replace(_nekoImage, "*", "", -1)
if strings.Contains(imageName, pattern) {
return true
}
}
}
return false
}
func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, error) {
if settings.Name != "" && !dockerNames.RestrictedNamePattern.MatchString(settings.Name) {
return "", fmt.Errorf("invalid container name, must match %s", dockerNames.RestrictedNameChars)
@@ -96,11 +113,11 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro
return "", fmt.Errorf("mounts cannot be specified, because storage is disabled or unavailable")
}
if in, _ := utils.ArrayIn(settings.NekoImage, manager.config.NekoImages); !in {
return "", fmt.Errorf("invalid neko image")
if !manager.IsValidImage(settings.NekoImage, manager.config.NekoImages) {
return "", fmt.Errorf("invalid neko image: %q. valid are: %q", settings.NekoImage, manager.config.NekoImages)
}
isPrivilegedImage, _ := utils.ArrayIn(settings.NekoImage, manager.config.NekoPrivilegedImages)
isPrivilegedImage := manager.IsValidImage(settings.NekoImage, manager.config.NekoPrivilegedImages)
// TODO: Check if path name exists.
roomName := settings.Name
@@ -637,3 +654,42 @@ func (manager *RoomManagerCtx) Restart(id string) error {
// Restart the actual container
return manager.client.ContainerRestart(context.Background(), id, nil)
}
func (manager *RoomManagerCtx) Snapshot(id string) (*types.RoomSnapshot, error) {
container, err := manager.inspectContainer(id)
if err != nil {
return nil, err
}
imageListOps := dockerTypes.ImageListOptions{
All: true,
}
images, _ := manager.client.ImageList(context.Background(), imageListOps)
var containerImage string
for _, image := range images {
if image.ID == container.Image {
containerImage = image.RepoTags[0]
break
}
}
conatinerImage := strings.Split(containerImage, ":")[0]
tag := conatinerImage + ":" + time.Now().Format("20060102150405")
ops := dockerTypes.ContainerCommitOptions{
Pause: true,
Config: &containerTypes.Config{
Image: tag,
},
}
// Restart the actual container
_, err = manager.client.ContainerCommit(context.Background(), container.ID, ops)
if err != nil {
return nil, err
}
return &types.RoomSnapshot{
RoomId: id,
Name: tag,
}, nil
}
+6
View File
@@ -96,6 +96,11 @@ type RoomSettings struct {
BrowserPolicy *BrowserPolicy `json:"browser_policy,omitempty"`
}
type RoomSnapshot struct {
RoomId string `json:"roomId"`
Name string `json:"name"`
}
func (settings *RoomSettings) ToEnv() []string {
env := []string{
fmt.Sprintf("NEKO_PASSWORD=%s", settings.UserPass),
@@ -263,4 +268,5 @@ type RoomManager interface {
Start(id string) error
Stop(id string) error
Restart(id string) error
Snapshot(id string) (*RoomSnapshot, error)
}