mirror of
https://github.com/m1k1o/neko-rooms.git
synced 2026-11-03 07:01:35 -06:00
Compare commits
3 Commits
v1.6.0
...
room_snapshots
| Author | SHA1 | Date | |
|---|---|---|---|
| c45f17fa1f | |||
| 1205dc9587 | |||
| fa401571e0 |
+31
-1
@@ -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:
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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: {
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user