mirror of
https://github.com/m1k1o/neko-rooms.git
synced 2026-11-03 07:01:35 -06:00
Compare commits
3 Commits
v1.6.4
...
room_snapshots
| Author | SHA1 | Date | |
|---|---|---|---|
| c45f17fa1f | |||
| 1205dc9587 | |||
| fa401571e0 |
+31
-1
@@ -220,7 +220,27 @@ paths:
|
|||||||
$ref: '#/components/schemas/RoomEntry'
|
$ref: '#/components/schemas/RoomEntry'
|
||||||
'500':
|
'500':
|
||||||
description: Internal server error
|
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:
|
/api/pull:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -477,6 +497,16 @@ components:
|
|||||||
type: boolean
|
type: boolean
|
||||||
example: false
|
example: false
|
||||||
|
|
||||||
|
RoomSnapshot:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
example: bc04dace10
|
||||||
|
snapshot:
|
||||||
|
type: string
|
||||||
|
example: image/tag:20220201102022
|
||||||
|
|
||||||
BrowserPolicy:
|
BrowserPolicy:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|||||||
@@ -597,6 +597,30 @@ export interface RoomsConfig {
|
|||||||
*/
|
*/
|
||||||
'uses_mux'?: boolean;
|
'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
|
* 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);
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
@@ -1382,6 +1439,17 @@ export const RoomsApiFp = function(configuration?: Configuration) {
|
|||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.roomsList(options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.roomsList(options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
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>> {
|
roomsList(options?: any): AxiosPromise<Array<RoomEntry>> {
|
||||||
return localVarFp.roomsList(options).then((request) => request(axios, basePath));
|
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) {
|
public roomsList(options?: AxiosRequestConfig) {
|
||||||
return RoomsApiFp(this.configuration).roomsList(options).then((request) => request(this.axios, this.basePath));
|
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',
|
color: 'red',
|
||||||
icon: 'mdi-trash-can-outline',
|
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
|
return undefined
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
<RoomActionBtn action="start" :roomId="item.id" :disabled="item.running" />
|
<RoomActionBtn action="start" :roomId="item.id" :disabled="item.running" />
|
||||||
<RoomActionBtn action="stop" :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="restart" :roomId="item.id" :disabled="!item.running" />
|
||||||
|
<RoomActionBtn action="snapshot" :roomId="item.id" :disabled="!item.running" />
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:[`item.destroy`]="{ item }">
|
<template v-slot:[`item.destroy`]="{ item }">
|
||||||
<RoomActionBtn action="remove" :roomId="item.id" />
|
<RoomActionBtn action="remove" :roomId="item.id" />
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
RoomsApi,
|
RoomsApi,
|
||||||
DefaultApi,
|
DefaultApi,
|
||||||
PullStatus,
|
PullStatus,
|
||||||
|
RoomSnapshot,
|
||||||
} from '@/api/index'
|
} from '@/api/index'
|
||||||
|
|
||||||
import { state, State } from './state'
|
import { state, State } from './state'
|
||||||
@@ -61,6 +62,9 @@ export default new Vuex.Store({
|
|||||||
PULL_STATUS(state: State, pullStatus: PullStatus) {
|
PULL_STATUS(state: State, pullStatus: PullStatus) {
|
||||||
Vue.set(state, 'pullStatus', pullStatus)
|
Vue.set(state, 'pullStatus', pullStatus)
|
||||||
},
|
},
|
||||||
|
ROOMS_SNAPSHOT_ADD(state: State, snapshot: RoomSnapshot) {
|
||||||
|
Vue.set(state, 'snapshot', snapshot)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async ROOMS_CONFIG({ commit }: ActionContext<State, State>) {
|
async ROOMS_CONFIG({ commit }: ActionContext<State, State>) {
|
||||||
@@ -135,6 +139,14 @@ export default new Vuex.Store({
|
|||||||
const res = await defaultApi.pullStop()
|
const res = await defaultApi.pullStop()
|
||||||
return res.data
|
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: {
|
modules: {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,5 +57,6 @@ func (manager *ApiManagerCtx) Mount(r chi.Router) {
|
|||||||
r.Post("/stop", manager.roomGenericAction(manager.rooms.Stop))
|
r.Post("/stop", manager.roomGenericAction(manager.rooms.Stop))
|
||||||
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.Post("/snapshot", manager.roomSnapshot)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,3 +150,15 @@ func (manager *ApiManagerCtx) roomGenericAction(action func(id string) error) fu
|
|||||||
w.WriteHeader(http.StatusNoContent)
|
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"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
dockerTypes "github.com/docker/docker/api/types"
|
dockerTypes "github.com/docker/docker/api/types"
|
||||||
|
containerTypes "github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
dockerMount "github.com/docker/docker/api/types/mount"
|
dockerMount "github.com/docker/docker/api/types/mount"
|
||||||
network "github.com/docker/docker/api/types/network"
|
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)
|
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) {
|
func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, error) {
|
||||||
if settings.Name != "" && !dockerNames.RestrictedNamePattern.MatchString(settings.Name) {
|
if settings.Name != "" && !dockerNames.RestrictedNamePattern.MatchString(settings.Name) {
|
||||||
return "", fmt.Errorf("invalid container name, must match %s", dockerNames.RestrictedNameChars)
|
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")
|
return "", fmt.Errorf("mounts cannot be specified, because storage is disabled or unavailable")
|
||||||
}
|
}
|
||||||
|
|
||||||
if in, _ := utils.ArrayIn(settings.NekoImage, manager.config.NekoImages); !in {
|
if !manager.IsValidImage(settings.NekoImage, manager.config.NekoImages) {
|
||||||
return "", fmt.Errorf("invalid neko image")
|
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.
|
// TODO: Check if path name exists.
|
||||||
roomName := settings.Name
|
roomName := settings.Name
|
||||||
@@ -637,3 +654,42 @@ func (manager *RoomManagerCtx) Restart(id string) error {
|
|||||||
// Restart the actual container
|
// Restart the actual container
|
||||||
return manager.client.ContainerRestart(context.Background(), id, nil)
|
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"`
|
BrowserPolicy *BrowserPolicy `json:"browser_policy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RoomSnapshot struct {
|
||||||
|
RoomId string `json:"roomId"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
func (settings *RoomSettings) ToEnv() []string {
|
func (settings *RoomSettings) ToEnv() []string {
|
||||||
env := []string{
|
env := []string{
|
||||||
fmt.Sprintf("NEKO_PASSWORD=%s", settings.UserPass),
|
fmt.Sprintf("NEKO_PASSWORD=%s", settings.UserPass),
|
||||||
@@ -263,4 +268,5 @@ 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
|
||||||
|
Snapshot(id string) (*RoomSnapshot, error)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user