Nuclio as a plugin in CVAT, improved system to check installed plugins (#2192)

* allow to run cvat without nuclio

* fix new line

* fix comments

* Updated core version

* refactoring

* minor refactoring, fixed eslint issues, added documentation to cvat-core, updated ui version, updated changelog

* move plugins to serverViewSet

Co-authored-by: Boris Sekachev <boris.sekachev@yandex.ru>
main
Dmitry Agapov 5 years ago committed by GitHub
parent 6370f980eb
commit f2c84a2653
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- UI models (like DEXTR) were redesigned to be more interactive (<https://github.com/opencv/cvat/pull/2054>) - UI models (like DEXTR) were redesigned to be more interactive (<https://github.com/opencv/cvat/pull/2054>)
- Used Ubuntu:20.04 as a base image for CVAT Dockerfile (<https://github.com/opencv/cvat/pull/2101>) - Used Ubuntu:20.04 as a base image for CVAT Dockerfile (<https://github.com/opencv/cvat/pull/2101>)
- Right colors of label tags in label mapping when a user runs automatic detection (<https://github.com/openvinotoolkit/cvat/pull/2162>) - Right colors of label tags in label mapping when a user runs automatic detection (<https://github.com/openvinotoolkit/cvat/pull/2162>)
- Nuclio became an optional component of CVAT (<https://github.com/openvinotoolkit/cvat/pull/2192>)
- A key to remove a point from a polyshape [Ctrl => Alt] (<https://github.com/openvinotoolkit/cvat/pull/2204>) - A key to remove a point from a polyshape [Ctrl => Alt] (<https://github.com/openvinotoolkit/cvat/pull/2204>)
### Deprecated ### Deprecated

@ -63,6 +63,7 @@ services:
DJANGO_LOG_SERVER_PORT: 5000 DJANGO_LOG_SERVER_PORT: 5000
DJANGO_LOG_VIEWER_HOST: kibana DJANGO_LOG_VIEWER_HOST: kibana
DJANGO_LOG_VIEWER_PORT: 5601 DJANGO_LOG_VIEWER_PORT: 5601
CVAT_ANALYTICS: 1
no_proxy: kibana,logstash,nuclio,${no_proxy} no_proxy: kibana,logstash,nuclio,${no_proxy}
volumes: volumes:

@ -0,0 +1,7 @@
## Serverless for Computer Vision Annotation Tool (CVAT)
### Run docker container
```bash
# From project root directory
docker-compose -f docker-compose.yml -f components/serverless/docker-compose.serverless.yml up -d
```

@ -0,0 +1,28 @@
version: '2.3'
services:
serverless:
container_name: nuclio
image: quay.io/nuclio/dashboard:1.4.8-amd64
restart: always
networks:
default:
aliases:
- nuclio
volumes:
- /tmp:/tmp
- /var/run/docker.sock:/var/run/docker.sock
environment:
http_proxy:
https_proxy:
no_proxy: 172.28.0.1,${no_proxy}
NUCLIO_CHECK_FUNCTION_CONTAINERS_HEALTHINESS: "true"
ports:
- "8070:8070"
cvat:
environment:
CVAT_SERVERLESS: 1
no_proxy: kibana,logstash,nuclio,${no_proxy}
volumes:
cvat_events:

@ -1,6 +1,6 @@
{ {
"name": "cvat-core", "name": "cvat-core",
"version": "3.7.1", "version": "3.8.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

@ -1,6 +1,6 @@
{ {
"name": "cvat-core", "name": "cvat-core",
"version": "3.7.1", "version": "3.8.0",
"description": "Part of Computer Vision Tool which presents an interface for client-side integration", "description": "Part of Computer Vision Tool which presents an interface for client-side integration",
"main": "babel.config.js", "main": "babel.config.js",
"scripts": { "scripts": {

@ -223,6 +223,11 @@
return tasks; return tasks;
}; };
cvat.server.installedApps.implementation = async () => {
const result = await serverProxy.server.installedApps();
return result;
};
return cvat; return cvat;
} }

@ -136,7 +136,6 @@ function build() {
return result; return result;
}, },
/** /**
* Method allows to register on a server * Method allows to register on a server
* @method register * @method register
* @async * @async
@ -272,6 +271,20 @@ function build() {
.apiWrapper(cvat.server.request, url, data); .apiWrapper(cvat.server.request, url, data);
return result; return result;
}, },
/**
* Method returns apps that are installed on the server
* @method installedApps
* @async
* @memberof module:API.cvat.server
* @returns {Object} map {installedApp: boolean}
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
async installedApps() {
const result = await PluginRegistry.apiWrapper(cvat.server.installedApps);
return result;
},
}, },
/** /**
* Namespace is used for getting tasks * Namespace is used for getting tasks
@ -470,34 +483,35 @@ function build() {
return result; return result;
}, },
/** /**
* Install plugin to CVAT * Install plugin to CVAT
* @method register * @method register
* @async * @async
* @memberof module:API.cvat.plugins * @memberof module:API.cvat.plugins
* @param {Plugin} [plugin] plugin for registration * @param {Plugin} [plugin] plugin for registration
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
*/ */
async register(plugin) { async register(plugin) {
const result = await PluginRegistry const result = await PluginRegistry
.apiWrapper(cvat.plugins.register, plugin); .apiWrapper(cvat.plugins.register, plugin);
return result; return result;
}, },
}, },
/** /**
* Namespace is used for serverless functions management (mainly related with DL models) * Namespace is used for serverless functions management (mainly related with DL models)
* @namespace lambda * @namespace lambda
* @memberof module:API.cvat * @memberof module:API.cvat
*/ */
lambda: { lambda: {
/** /**
* Method returns list of available serverless models * Method returns list of available serverless models
* @method list * @method list
* @async * @async
* @memberof module:API.cvat.lambda * @memberof module:API.cvat.lambda
* @returns {module:API.cvat.classes.MLModel[]} * @returns {module:API.cvat.classes.MLModel[]}
* @throws {module:API.cvat.exceptions.ServerError} * @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
*/ */
async list() { async list() {
const result = await PluginRegistry const result = await PluginRegistry
.apiWrapper(cvat.lambda.list); .apiWrapper(cvat.lambda.list);

@ -812,6 +812,18 @@
} }
} }
async function installedApps() {
const { backendAPI } = config;
try {
const response = await Axios.get(`${backendAPI}/server/plugins`, {
proxy: config.proxy,
});
return response.data;
} catch (errorData) {
throw generateError(errorData);
}
}
Object.defineProperties(this, Object.freeze({ Object.defineProperties(this, Object.freeze({
server: { server: {
value: Object.freeze({ value: Object.freeze({
@ -828,6 +840,7 @@
register, register,
request: serverRequest, request: serverRequest,
userAgreements, userAgreements,
installedApps,
}), }),
writable: false, writable: false,
}, },

@ -3,43 +3,35 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import { SupportedPlugins } from 'reducers/interfaces'; import { PluginsList } from 'reducers/interfaces';
import PluginChecker from 'utils/plugin-checker'; import getCore from '../cvat-core-wrapper';
const core = getCore();
export enum PluginsActionTypes { export enum PluginsActionTypes {
CHECK_PLUGINS = 'CHECK_PLUGINS', GET_PLUGINS = 'GET_PLUGINS',
CHECKED_ALL_PLUGINS = 'CHECKED_ALL_PLUGINS', GET_PLUGINS_SUCCESS = 'GET_PLUGINS_SUCCESS',
GET_PLUGINS_FAILED = 'GET_PLUGINS_FAILED',
} }
type PluginObjects = Record<SupportedPlugins, boolean>;
const pluginActions = { const pluginActions = {
checkPlugins: () => createAction(PluginsActionTypes.CHECK_PLUGINS), checkPlugins: () => createAction(PluginsActionTypes.GET_PLUGINS),
checkedAllPlugins: (list: PluginObjects) => ( checkPluginsSuccess: (list: PluginsList) => createAction(
createAction(PluginsActionTypes.CHECKED_ALL_PLUGINS, { PluginsActionTypes.GET_PLUGINS_SUCCESS, { list },
list, ),
}) checkPluginsFailed: (error: any) => createAction(
PluginsActionTypes.GET_PLUGINS_FAILED, { error },
), ),
}; };
export type PluginActions = ActionUnion<typeof pluginActions>; export type PluginActions = ActionUnion<typeof pluginActions>;
export function checkPluginsAsync(): ThunkAction { export const getPluginsAsync = (): ThunkAction => async (dispatch): Promise<void> => {
return async (dispatch): Promise<void> => { dispatch(pluginActions.checkPlugins());
dispatch(pluginActions.checkPlugins()); try {
const plugins: PluginObjects = { const list: PluginsList = await core.server.installedApps();
ANALYTICS: false, dispatch(pluginActions.checkPluginsSuccess(list));
GIT_INTEGRATION: false, } catch (error) {
}; dispatch(pluginActions.checkPluginsFailed(error));
}
const promises: Promise<boolean>[] = [ };
// check must return true/false with no exceptions
PluginChecker.check(SupportedPlugins.ANALYTICS),
PluginChecker.check(SupportedPlugins.GIT_INTEGRATION),
];
const values = await Promise.all(promises);
[plugins.ANALYTICS, plugins.GIT_INTEGRATION] = values;
dispatch(pluginActions.checkedAllPlugins(plugins));
};
}

@ -65,6 +65,7 @@ interface CVATAppProps {
authActionsInitialized: boolean; authActionsInitialized: boolean;
notifications: NotificationsState; notifications: NotificationsState;
user: any; user: any;
isModelPluginActive: boolean;
} }
class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentProps> { class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentProps> {
@ -115,6 +116,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
userAgreementsInitialized, userAgreementsInitialized,
authActionsFetching, authActionsFetching,
authActionsInitialized, authActionsInitialized,
isModelPluginActive,
} = this.props; } = this.props;
this.showErrors(); this.showErrors();
@ -150,7 +152,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
loadAbout(); loadAbout();
} }
if (!modelsInitialized && !modelsFetching) { if (isModelPluginActive && !modelsInitialized && !modelsFetching) {
initModels(); initModels();
} }
@ -248,11 +250,12 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
switchSettingsDialog, switchSettingsDialog,
user, user,
keyMap, keyMap,
isModelPluginActive,
} = this.props; } = this.props;
const readyForRender = (userInitialized && (user == null || !user.isVerified)) const readyForRender = (userInitialized && (user == null || !user.isVerified))
|| (userInitialized && formatsInitialized || (userInitialized && formatsInitialized && pluginsInitialized
&& pluginsInitialized && usersInitialized && aboutInitialized); && usersInitialized && aboutInitialized);
const subKeyMap = { const subKeyMap = {
SWITCH_SHORTCUTS: keyMap.SWITCH_SHORTCUTS, SWITCH_SHORTCUTS: keyMap.SWITCH_SHORTCUTS,
@ -316,7 +319,8 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
<Route exact path='/tasks/create' component={CreateTaskPageContainer} /> <Route exact path='/tasks/create' component={CreateTaskPageContainer} />
<Route exact path='/tasks/:id' component={TaskPageContainer} /> <Route exact path='/tasks/:id' component={TaskPageContainer} />
<Route exact path='/tasks/:tid/jobs/:jid' component={AnnotationPageContainer} /> <Route exact path='/tasks/:tid/jobs/:jid' component={AnnotationPageContainer} />
<Route exact path='/models' component={ModelsPageContainer} /> { isModelPluginActive
&& <Route exact path='/models' component={ModelsPageContainer} /> }
<Redirect push to='/tasks' /> <Redirect push to='/tasks' />
</Switch> </Switch>
</GlobalHotKeys> </GlobalHotKeys>

@ -22,7 +22,7 @@ import { CVATLogo, AccountIcon } from 'icons';
import ChangePasswordDialog from 'components/change-password-modal/change-password-modal'; import ChangePasswordDialog from 'components/change-password-modal/change-password-modal';
import { switchSettingsDialog as switchSettingsDialogAction } from 'actions/settings-actions'; import { switchSettingsDialog as switchSettingsDialogAction } from 'actions/settings-actions';
import { logoutAsync, authActions } from 'actions/auth-actions'; import { logoutAsync, authActions } from 'actions/auth-actions';
import { SupportedPlugins, CombinedState } from 'reducers/interfaces'; import { CombinedState } from 'reducers/interfaces';
import SettingsModal from './settings-modal/settings-modal'; import SettingsModal from './settings-modal/settings-modal';
const core = getCore(); const core = getCore();
@ -53,8 +53,10 @@ interface StateToProps {
changePasswordDialogShown: boolean; changePasswordDialogShown: boolean;
changePasswordFetching: boolean; changePasswordFetching: boolean;
logoutFetching: boolean; logoutFetching: boolean;
installedAnalytics: boolean;
renderChangePasswordItem: boolean; renderChangePasswordItem: boolean;
isAnalyticsPluginActive: boolean;
isModelsPluginActive: boolean;
isGitPluginActive: boolean;
} }
interface DispatchToProps { interface DispatchToProps {
@ -111,8 +113,10 @@ function mapStateToProps(state: CombinedState): StateToProps {
changePasswordDialogShown, changePasswordDialogShown,
changePasswordFetching, changePasswordFetching,
logoutFetching, logoutFetching,
installedAnalytics: list[SupportedPlugins.ANALYTICS],
renderChangePasswordItem, renderChangePasswordItem,
isAnalyticsPluginActive: list.ANALYTICS,
isModelsPluginActive: list.MODELS,
isGitPluginActive: list.GIT_INTEGRATION,
}; };
} }
@ -132,7 +136,6 @@ function HeaderContainer(props: Props): JSX.Element {
const { const {
user, user,
tool, tool,
installedAnalytics,
logoutFetching, logoutFetching,
changePasswordFetching, changePasswordFetching,
settingsDialogShown, settingsDialogShown,
@ -141,6 +144,8 @@ function HeaderContainer(props: Props): JSX.Element {
switchSettingsDialog, switchSettingsDialog,
switchChangePasswordDialog, switchChangePasswordDialog,
renderChangePasswordItem, renderChangePasswordItem,
isAnalyticsPluginActive,
isModelsPluginActive,
} = props; } = props;
const { const {
@ -276,38 +281,40 @@ function HeaderContainer(props: Props): JSX.Element {
> >
Tasks Tasks
</Button> </Button>
<Button
className='cvat-header-button' {isModelsPluginActive && (
type='link' <Button
value='models' className='cvat-header-button'
href='/models' type='link'
onClick={ value='models'
(event: React.MouseEvent): void => { href='/models'
event.preventDefault(); onClick={
history.push('/models'); (event: React.MouseEvent): void => {
event.preventDefault();
history.push('/models');
}
} }
} >
> Models
Models </Button>
</Button> )}
{ installedAnalytics {isAnalyticsPluginActive && (
&& ( <Button
<Button className='cvat-header-button'
className='cvat-header-button' type='link'
type='link' href={`${tool.server.host}/analytics/app/kibana`}
href={`${tool.server.host}/analytics/app/kibana`} onClick={
onClick={ (event: React.MouseEvent): void => {
(event: React.MouseEvent): void => { event.preventDefault();
event.preventDefault(); // false positive
// false positive // eslint-disable-next-line
// eslint-disable-next-line window.open(`${tool.server.host}/analytics/app/kibana`, '_blank');
window.open(`${tool.server.host}/analytics/app/kibana`, '_blank');
}
} }
> }
Analytics >
</Button> Analytics
)} </Button>
)}
</div> </div>
<div className='cvat-right-header'> <div className='cvat-right-header'>
<Button <Button

@ -19,22 +19,15 @@ import {
loadAuthActionsAsync, loadAuthActionsAsync,
} from 'actions/auth-actions'; } from 'actions/auth-actions';
import { getFormatsAsync } from 'actions/formats-actions'; import { getFormatsAsync } from 'actions/formats-actions';
import { checkPluginsAsync } from 'actions/plugins-actions'; import { getPluginsAsync } from 'actions/plugins-actions';
import { getUsersAsync } from 'actions/users-actions'; import { getUsersAsync } from 'actions/users-actions';
import { getAboutAsync } from 'actions/about-actions'; import { getAboutAsync } from 'actions/about-actions';
import { getModelsAsync } from 'actions/models-actions'; import { getModelsAsync } from 'actions/models-actions';
import { getUserAgreementsAsync } from 'actions/useragreements-actions'; import { getUserAgreementsAsync } from 'actions/useragreements-actions';
import { shortcutsActions } from 'actions/shortcuts-actions'; import { shortcutsActions } from 'actions/shortcuts-actions';
import { switchSettingsDialog } from 'actions/settings-actions'; import { switchSettingsDialog } from 'actions/settings-actions';
import { import { resetErrors, resetMessages } from './actions/notification-actions';
resetErrors, import { CombinedState, NotificationsState } from './reducers/interfaces';
resetMessages,
} from './actions/notification-actions';
import {
CombinedState,
NotificationsState,
} from './reducers/interfaces';
createCVATStore(createRootReducer); createCVATStore(createRootReducer);
const cvatStore = getCVATStore(); const cvatStore = getCVATStore();
@ -61,6 +54,7 @@ interface StateToProps {
notifications: NotificationsState; notifications: NotificationsState;
user: any; user: any;
keyMap: Record<string, ExtendedKeyMapOptions>; keyMap: Record<string, ExtendedKeyMapOptions>;
isModelPluginActive: boolean;
} }
interface DispatchToProps { interface DispatchToProps {
@ -110,6 +104,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
notifications: state.notifications, notifications: state.notifications,
user: auth.user, user: auth.user,
keyMap: shortcuts.keyMap, keyMap: shortcuts.keyMap,
isModelPluginActive: plugins.list.MODELS,
}; };
} }
@ -118,7 +113,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
loadFormats: (): void => dispatch(getFormatsAsync()), loadFormats: (): void => dispatch(getFormatsAsync()),
verifyAuthorized: (): void => dispatch(authorizedAsync()), verifyAuthorized: (): void => dispatch(authorizedAsync()),
loadUserAgreements: (): void => dispatch(getUserAgreementsAsync()), loadUserAgreements: (): void => dispatch(getUserAgreementsAsync()),
initPlugins: (): void => dispatch(checkPluginsAsync()), initPlugins: (): void => dispatch(getPluginsAsync()),
initModels: (): void => dispatch(getModelsAsync()), initModels: (): void => dispatch(getModelsAsync()),
loadUsers: (): void => dispatch(getUsersAsync()), loadUsers: (): void => dispatch(getUsersAsync()),
loadAbout: (): void => dispatch(getAboutAsync()), loadAbout: (): void => dispatch(getAboutAsync()),

@ -80,14 +80,17 @@ export interface FormatsState {
export enum SupportedPlugins { export enum SupportedPlugins {
GIT_INTEGRATION = 'GIT_INTEGRATION', GIT_INTEGRATION = 'GIT_INTEGRATION',
ANALYTICS = 'ANALYTICS', ANALYTICS = 'ANALYTICS',
MODELS = 'MODELS',
} }
export type PluginsList = {
[name in SupportedPlugins]: boolean;
};
export interface PluginsState { export interface PluginsState {
fetching: boolean; fetching: boolean;
initialized: boolean; initialized: boolean;
list: { list: PluginsList;
[name in SupportedPlugins]: boolean;
};
} }
export interface UsersState { export interface UsersState {
@ -478,6 +481,14 @@ export interface ShortcutsState {
normalizedKeyMap: Record<string, string>; normalizedKeyMap: Record<string, string>;
} }
export interface MetaState {
initialized: boolean;
fetching: boolean;
showTasksButton: boolean;
showAnalyticsButton: boolean;
showModelsButton: boolean;
}
export interface CombinedState { export interface CombinedState {
auth: AuthState; auth: AuthState;
tasks: TasksState; tasks: TasksState;
@ -492,4 +503,5 @@ export interface CombinedState {
annotation: AnnotationState; annotation: AnnotationState;
settings: SettingsState; settings: SettingsState;
shortcuts: ShortcutsState; shortcuts: ShortcutsState;
meta: MetaState;
} }

@ -12,6 +12,7 @@ const defaultState: PluginsState = {
list: { list: {
GIT_INTEGRATION: false, GIT_INTEGRATION: false,
ANALYTICS: false, ANALYTICS: false,
MODELS: false,
}, },
}; };
@ -20,14 +21,14 @@ export default function (
action: PluginActions, action: PluginActions,
): PluginsState { ): PluginsState {
switch (action.type) { switch (action.type) {
case PluginsActionTypes.CHECK_PLUGINS: { case PluginsActionTypes.GET_PLUGINS: {
return { return {
...state, ...state,
initialized: false, initialized: false,
fetching: true, fetching: true,
}; };
} }
case PluginsActionTypes.CHECKED_ALL_PLUGINS: { case PluginsActionTypes.GET_PLUGINS_SUCCESS: {
const { list } = action.payload; const { list } = action.payload;
if (!state.list.GIT_INTEGRATION && list.GIT_INTEGRATION) { if (!state.list.GIT_INTEGRATION && list.GIT_INTEGRATION) {
@ -41,6 +42,13 @@ export default function (
list, list,
}; };
} }
case PluginsActionTypes.GET_PLUGINS_FAILED: {
return {
...state,
initialized: true,
fetching: false,
};
}
default: default:
return state; return state;
} }

@ -17,6 +17,7 @@ import settingsReducer from './settings-reducer';
import shortcutsReducer from './shortcuts-reducer'; import shortcutsReducer from './shortcuts-reducer';
import userAgreementsReducer from './useragreements-reducer'; import userAgreementsReducer from './useragreements-reducer';
export default function createRootReducer(): Reducer { export default function createRootReducer(): Reducer {
return combineReducers({ return combineReducers({
auth: authReducer, auth: authReducer,

@ -1,29 +0,0 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import getCore from 'cvat-core-wrapper';
import { SupportedPlugins } from 'reducers/interfaces';
import isReachable from './url-checker';
const core = getCore();
// Easy plugin checker to understand what plugins supports by a server
class PluginChecker {
public static async check(plugin: SupportedPlugins): Promise<boolean> {
const serverHost = core.config.backendAPI.slice(0, -7);
switch (plugin) {
case SupportedPlugins.GIT_INTEGRATION: {
return isReachable(`${serverHost}/git/repository/meta/get`, 'OPTIONS');
}
case SupportedPlugins.ANALYTICS: {
return isReachable(`${serverHost}/analytics/app/kibana`, 'GET');
}
default:
return false;
}
}
}
export default PluginChecker;

@ -398,6 +398,11 @@ class FrameMetaSerializer(serializers.Serializer):
height = serializers.IntegerField() height = serializers.IntegerField()
name = serializers.CharField(max_length=1024) name = serializers.CharField(max_length=1024)
class PluginsSerializer(serializers.Serializer):
GIT_INTEGRATION = serializers.BooleanField()
ANALYTICS = serializers.BooleanField()
MODELS = serializers.BooleanField()
class DataMetaSerializer(serializers.ModelSerializer): class DataMetaSerializer(serializers.ModelSerializer):
frames = FrameMetaSerializer(many=True, allow_null=True) frames = FrameMetaSerializer(many=True, allow_null=True)
image_quality = serializers.IntegerField(min_value=0, max_value=100) image_quality = serializers.IntegerField(min_value=0, max_value=100)

@ -7,9 +7,11 @@ import os.path as osp
import shutil import shutil
import traceback import traceback
from datetime import datetime from datetime import datetime
from distutils.util import strtobool
from tempfile import mkstemp from tempfile import mkstemp
import django_rq import django_rq
from django.apps import apps
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import IntegrityError from django.db import IntegrityError
@ -40,7 +42,8 @@ from cvat.apps.engine.serializers import (
DataMetaSerializer, DataSerializer, ExceptionSerializer, DataMetaSerializer, DataSerializer, ExceptionSerializer,
FileInfoSerializer, JobSerializer, LabeledDataSerializer, FileInfoSerializer, JobSerializer, LabeledDataSerializer,
LogEventSerializer, ProjectSerializer, RqStatusSerializer, LogEventSerializer, ProjectSerializer, RqStatusSerializer,
TaskSerializer, UserSerializer) TaskSerializer, UserSerializer, PluginsSerializer,
)
from cvat.apps.engine.utils import av_scan_paths from cvat.apps.engine.utils import av_scan_paths
from . import models, task from . import models, task
@ -168,6 +171,23 @@ class ServerViewSet(viewsets.ViewSet):
data = dm.views.get_all_formats() data = dm.views.get_all_formats()
return Response(DatasetFormatsSerializer(data).data) return Response(DatasetFormatsSerializer(data).data)
@staticmethod
@swagger_auto_schema(method='get', operation_summary='Method provides allowed plugins.',
responses={'200': PluginsSerializer()})
@action(detail=False, methods=['GET'], url_path='plugins', serializer_class=PluginsSerializer)
def plugins(request):
response = {
'GIT_INTEGRATION': apps.is_installed('cvat.apps.git'),
'ANALYTICS': False,
'MODELS': False,
}
if strtobool(os.environ.get("CVAT_ANALYTICS", '0')):
response['ANALYTICS'] = True
if strtobool(os.environ.get("CVAT_SERVERLESS", '0')):
response['MODELS'] = True
return Response(response)
class ProjectFilter(filters.FilterSet): class ProjectFilter(filters.FilterSet):
name = filters.CharFilter(field_name="name", lookup_expr="icontains") name = filters.CharFilter(field_name="name", lookup_expr="icontains")
owner = filters.CharFilter(field_name="owner__username", lookup_expr="icontains") owner = filters.CharFilter(field_name="owner__username", lookup_expr="icontains")

@ -2,9 +2,9 @@
# #
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
from django.urls import path from django.urls import include, path
from rest_framework import routers from rest_framework import routers
from django.urls import include
from . import views from . import views
router = routers.DefaultRouter(trailing_slash=False) router = routers.DefaultRouter(trailing_slash=False)
@ -18,7 +18,7 @@ router.register('requests', views.RequestViewSet, basename='request')
# GET /api/v1/lambda/functions - get list of functions # GET /api/v1/lambda/functions - get list of functions
# GET /api/v1/lambda/functions/<int:fid> - get information about the function # GET /api/v1/lambda/functions/<int:fid> - get information about the function
# POST /api/v1/labmda/requests - call a function # POST /api/v1/lambda/requests - call a function
# { "function": "<id>", "mode": "online|offline", "job": "<jid>", "frame": "<n>", # { "function": "<id>", "mode": "online|offline", "job": "<jid>", "frame": "<n>",
# "points": [...], } # "points": [...], }
# GET /api/v1/lambda/requests - get list of requests # GET /api/v1/lambda/requests - get list of requests
@ -26,4 +26,4 @@ router.register('requests', views.RequestViewSet, basename='request')
# DEL /api/v1/lambda/requests/<int:rid> - cancel a request (don't delete) # DEL /api/v1/lambda/requests/<int:rid> - cancel a request (don't delete)
urlpatterns = [ urlpatterns = [
path('api/v1/lambda/', include((router.urls, 'cvat'), namespace='v1')) path('api/v1/lambda/', include((router.urls, 'cvat'), namespace='v1'))
] ]

@ -1,4 +1,3 @@
# Copyright (C) 2018-2019 Intel Corporation # Copyright (C) 2018-2019 Intel Corporation
# #
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
@ -19,9 +18,9 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.apps import apps
from django.contrib import admin from django.contrib import admin
from django.urls import path, include from django.urls import path, include
from django.apps import apps
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),

@ -95,25 +95,6 @@ services:
- ./cvat_proxy/conf.d/cvat.conf.template:/etc/nginx/conf.d/cvat.conf.template:ro - ./cvat_proxy/conf.d/cvat.conf.template:/etc/nginx/conf.d/cvat.conf.template:ro
command: /bin/sh -c "envsubst '$$CVAT_HOST' < /etc/nginx/conf.d/cvat.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" command: /bin/sh -c "envsubst '$$CVAT_HOST' < /etc/nginx/conf.d/cvat.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
serverless:
container_name: nuclio
image: quay.io/nuclio/dashboard:1.4.8-amd64
restart: always
networks:
default:
aliases:
- nuclio
volumes:
- /tmp:/tmp
- /var/run/docker.sock:/var/run/docker.sock
environment:
http_proxy:
https_proxy:
no_proxy: 172.28.0.1,${no_proxy}
NUCLIO_CHECK_FUNCTION_CONTAINERS_HEALTHINESS: "true"
ports:
- "8070:8070"
networks: networks:
default: default:
ipam: ipam:

Loading…
Cancel
Save