Code Monkey home page Code Monkey logo

mission-control's Introduction

Mission Control

Opinionated JavaScript framework for building real-time dashboards

Project Homepage  |  Docs


Beginning a real-time web project, like building your own home dashboard, can be a tedious and time-consuming process. Coding lots of boilerplate for authentication, authorization and data communication isn’t fun at all. By the time you get to building the thing you wanted you got bored and moved on to the next. Mission Control aims to provide all those features out-of-the-box so you can get to creating what matters right away.

Features

  • Real-time state sync between server and client (service-based API)
  • Plugin API for creating your own services
  • Role-based permission API
  • Built-in User Management
  • HTTP & WebSocket Transports
  • Usecases:
    • Home Automation UIs / dashboards
    • Analytics dashboard combining different data sources
  • Available plugins for:
    • HomeKit light control
    • Spotify player
    • YouTube downloader (youtube-dl integration)
    • Filebrowser
    • etc.
  • Kinda pretty UI (at least I like it... you decide or build your own!)

Installation

Note: 2.0.0 is currently in pre-release but more stable than 0.x.x versions.

$ npm install -g @capevace/mission-control@next

Usage

You can now start the server like you would any binary.

$ mission-control --version
v2.0.0-rc8

Options

Usage: mission-control [options]

Options:
  -V, --version       output the version number
  -u, --url <url>     the url mission control is reachable at
  -p, --port <port>   the port to use for mission control
  -h, --help          display help for command

Config

A config file for mission-control will be created at $HOME_DIR/.mission-control/config. This can also be used to configure mission-control. However, options passed as command line arguments override settings in this file.

Screenshots

Mission Control Screenshot

Changelog

Version 2.0.0 (pre-release)

  • Completely redesigned dashboard UI
  • Introduction of a flexible plugin system
  • Authentication is now handled by Mission Control itself, single-sign-on is no longer required
  • Users now have profiles, which will be integrated with a solid permission system
  • Tons of bug fixes

Version 0.5.4

  • Rewrote logging system

Version 0.5.3

  • Added COVID widget to dashboard

Version 0.5.2

  • Fixed bahn algorithm skipping not displaying trains with SEV present

Version 0.5.1

  • Fixed dashboard layout now being loaded from database correctly

Version 0.5.0

  • New Dashboard is now customizeable

Version 0.4.2

  • Fixes error preventing the auth proxy from returning properly

Version 0.4.1

  • Fixes some minor security issues with dependencies

Version 0.4.0

  • The SSO server is now being proxied by default. This can be disabled with the --no-proxy option or by disabling it in the config file.
  • Instead of localhost, the default url is now the local ip

FAQ

Running Mission Control on port 80

On Linux, running an http server on port 80 requires root priviliges. Generally this isn't a problem as you can simply sudo mission-control -p 80 which works, but this approach falls apart when using systemd.

I found this workaround which seems to be the safest option to use instead:

sudo apt-get install libcap2-bin
sudo setcap 'cap_net_bind_service=+ep' /usr/bin/node # Replace path to node binary

Notes about internal package updating

  • public-ip kept at 4.0.4 until we move to ESM
  • internal-ip kept at 6.1.0 until we move to ESM
  • auto-bind kept at 4.0.0 until we move to ESM
  • on-change kept at 3.0.2 until we move to ESM
  • passport kept at 0.4 until we're sure, JWT-based caddy login still works

Packages by internal plugins

  • db-hafas: bahn
  • internal-ip and public-ip: system-info
  • @oznu/hap-client: homebridge

Authors

Lukas Mateffy – @Capevacemateffy.me

Distributed under the MIT license. See LICENSE for more information.

Contributing

  1. Fork it (https://github.com/capevace/mission-control/fork)
  2. Create your feature branch (git checkout -b feature/fooBar)
  3. Commit your changes (git commit -am 'Add some fooBar')
  4. Push to the branch (git push origin feature/fooBar)
  5. Create a new Pull Request

mission-control's People

Contributors

capevace avatar dependabot[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

mission-control's Issues

User profile / settings

Bildschirmfoto 2021-05-18 um 21 53 49

Normal User:

  • change name
  • change profile picture
  • change password

Admins:

  • create user
  • modify user
  • delete user
  • change user role

Remove unwanted dependencies from package.json

Over time some packages have been installed and added to package.json that aren't actually used anymore.

Remove those and clean up the dependency tree.

Try to minimize and only depend on external things that are absolutely required / very stable.

Check for memory leaks

Memory usage on Linux seems to be ever so slowly increasing, check that out

Memory in percent

3906 mat       %3.6 node /home/mat/.nvm/versions/node/v15.14.0/bin/mission-control --noso

# ... long ass time

3906 mat       %5.8 node /home/mat/.nvm/versions/node/v15.14.0/bin/mission-control --noso

Homebridge Room Layout

Create plugin that supports rendering a room layout

  • Preferably SVG based graphics
    • Do animations look good on hover etc with that? Tailwind?
  • Select single lights
  • And select "hotboxes" e.g. "work" around desktop to go into scenes

Frame 1

Rewrite database backend (JSON File -> SQLite)

The current database backend of saving everything to a database.json file is not really nice nor safe.

  • Rewrite backend to use SQLite
    • Default data insertion
    • Database migrations
    • Store uploaded files in SQLite
    • Store sessions in SQLite
    • .mission-control folder should then only have config file and database.sqlite file

Responsive dashboard layouts

Allow the dashboard / widget editor to create responsive layouts.

The underlying layout engine does support breakpoint-specific layouts.

The editor should be able to change all different breakpoint versions, that are smaller or equal to the current one

Dedicated mobile UI

Mission Control (or rather it's first-class UI Framework) should have a mobile UI that's not just a responsive version of the Desktop, but rather an app like interface.

image

Spotify UI has a nice header and a sick fade into content as the UITabBar background

A similar approach could be taken for custom pages, and pages could have an option to enable a fullscreen-like mode that minimises the headers footprint.
The dynamic dashboard pages would probably fall into this category.

Add Hooks API so plugins can react to other service's actions etc.

Use a async hook system that an unlimited amount of plugins can subscribe to, that are decoupled from for example service state and action.

hooks
	.service('notifications')
	.onAction('create')
	.run(() => {
		// Do something
	});

hooks
	.service('notifications')
	.onSettingsChange('thing');

hooks
	.service('notifications')
	.trigger('create', notification);

Complete Documentation for 2.0.0

  • Landing page #19
  • Get started
  • Guides
    • Writing a plugin
    • Mission Control internals
    • Creating example API consument
  • API Reference
    • Proper code comments

Better Plugin API design

Some experimentation:
/**
* A file to experiment with different API designs.
*/

// Server Side Sync module

class ServiceController {
  constructor(name, initialState = {}) {
  	this.name = name;

  	// Service State
  	this.state = initialState;
  	this.handlers = {
  		'action': {
  			permissions: [],
  			handler
  		}
  	};

  	autoBind(this);
  }

  action(name, options = {}) {
  	options = {
  		handler: () => { throw new Error(`Action '${name}' in service '${this.name}' has no handler`); },
  		...options,
  		permissions: [
  			['update', 'service', 'any'],
  			...(options.permissions || [])
  		]
  	};

  	if (name in Object.keys(this.handlers)) {
  		throw new Error(`Action handler for '${name}' already exists on service ${this.name}`);
  	}

  	this.handlers[name] = {
  		permissions: options.permissions,
  		handler: options.handler
  	};

  	// We return the action builder, an easier to use api with lots of helper functions.
  	// 
  	let builder = {
  		handler(handler) {
  			if (typeof handler !== 'function') {
  				throw new Error(`Action handler for '${this.name}' has to be a function`);
  			}

  			this.handlers[name].handler = handler;

  			return builder;
  		},
  		requirePermission(crud, resource, scope = 'any') {
  			if (scope !== 'any' || scope !== 'own') {
  				throw new Error(`Permission scope has to be either 'any' or 'own'.`);
  			}

  			this.handlers[name].permissions.push([crud, resource, scope]);

  			return builder;
  		},
  		requirePermissions(perms) {
  			if (!Array.isArray(perms)) {
  				throw new Error(`You need to pass permissions as an array like this (e.g. [['read', 'user', 'any'], ['update', 'user', 'own']]`)
  			}

  			perms.forEach(permissionArray => builder.permission(...permissionArray));

  			return builder;
  		}
  	};

  	return builder;
  }
}

//// register action

const service = sync.createService('test');


permissions
  .grant('guest').readAny('video');

permissions
  .grant('user').createAny('video');

service.action('action:')
  .requirePermission('create', 'video', 'any')
  .handler(async (data, context) => {
  	// Custom data filtration
  	const { filter } = permissions.can(context.user.role).createAny('video');

  	// return some data
  	// filter based on permissions passed to action
  	return context.filter({ test: 1 });
  })

////
class ServiceClient {
  constructor(name, controller) {

  }

  subscribe(nameOrCb, cb) {

  }

  dispatch(name, data) {}
}

class CoreService extends {

}

const RESERVED_SERVICES = ['core'];

/**
* Sync is the main thing
*/
class Sync {
  constructor() {
  	/**
  	 * The services that sync manages
  	 * @type {Object<string, Service>}
  	 */
  	this.services = {
  		'core': new Service('core')
  	};
  }

  createService(name) {
  	if (name in this.services) {
  		throw new Error(`Service ${name} already exists`);
  	}


  	const service = new Service(name);
  	service.subscribe((state) => {});

  	return service;
  }

  service(name) {
  	if (!(name in this.services)) {
  		throw new UserError(`Unknown service ${service}`, 404);
  	}

  	const service = this.services[name];

  	return {
  		dispatch: (...args) => service.dispatch(...args),
  		subscribe: (...args) => service.subscribe(...args)
  	};
  }
}

const sync = ...;

io.on((socket) => {
  socket.on('sync:action', (data, done) => {
  	try {
  		try {
  			const { service, action, data } = validator.validate(Joi.object({
  				service: Joi.string()
  					.trim()
  					.alphanum()
  					.required(),
  				action: Joi.string()
  					.trim()
  					.alphanum()
  					.required(),
  				data: Joi.object({}).unknown(true)
  			}));
  		} catch (e) {
  			throw new UserError(e.message, 400);
  		}

  		const userRole = socket.user.role;
  		const servicePermissionName = `service:${service}`;
  		
  		// Can access any services
  		const { granted: canReadAnyService } = auth.permissions
  			.can(userRole)
  			.read('service');

  		if (!canReadAnyService) {
  			throw new UserError(`You don't have the required permissions to access services.`, 403);
  		}

  		// Can access specific service
  		const { granted: canReadService, filter: filterState } = auth.permissions
  			.can(userRole)
  			.read(servicePermissionName);

  		if (!canReadService) {
  			throw new UserError(`You don't have the required permissions to access service '${service}'.`, 403);
  		}

  		// Can 
  		const servicePermissionName = composePermissionNameForServiceAction(service);
  		const { granted: canCreateAction, filter: filterActionData } = auth.permissions
  			.can(userRole)
  			.update(servicePermissionName);

  		if (!canCreateAction) {
  			throw new UserError(`You don't have the required permissions to dispatch actions on service '${service}'.`, 403);
  		}

  		const service = sync.service(service);
  		service.dispatch(action, filterActionData(data));

  	} catch (e) {
  		// Log the error only if it's not a UserError
  		if (!e.isUserError) {
  			logger.error(e);
  		}

  		// We only send the error message to the user
  		// if it's either a UserError or the user has 
  		// permission to
  		if (e.isUserError ||
  			config.debug && auth.permissions.can('user').read('error', 'any').granted) {
  			return done({
  				error: {
  					message: e.message,
  					status: e.status || 500
  				}
  			});
  		}
  			
  		return done({
  			error: {
  				message: 'Internal Server Error',
  				status: 500
  			}
  		});
  	}
  });
})

// Client side
const sync = require('@sync');

// Core is always subscribed, every Mission Control Dashboard instance gets this state
const core = sync.service('core') || sync.core();

const service = sync.service('system-info');
// Check if user can access service

try {
  service.dispatch('action', {
  	shit: ''
  });
} catch (e) {

}

const unsubscribe = service.subscribe(() => {});
const unsubscribeText = service.subscribe('cpus.text', (text) => {

});

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.