Resource management software for Fab Labs. REST API built with the BeeGo framework. Front end clients currently build with AngularJS and ReactJS.
- Quick-start
- Versioning
- Runmode
- Development
- API Documentation
- Deployment
- Deployment using Docker
- Troubleshooting
- TODO
Quickly install Localmachines to get it running on Linux or OS X. For more specialized/detailed setups, see here.
-
Install Go. You can download binaries from the Go Download Page. For OS X you can get the .pkg file. On Ubuntu you can just enter
sudo apt-get install golang-go
. -
Set Go environment variables:
export GOROOT=/usr/local/go
andexport GOPATH=$HOME/go
(you may needmkdir $HOME/go
beforehands) andexport PATH=$PATH:$HOME/go/bin
Most users add both lines to their
$HOME/.bash_profile
file. -
Clone the repository:
mkdir -p $GOPATH/src/github.com/FabLabBerlin cd $GOPATH/src/github.com/FabLabBerlin git clone [email protected]:FabLabBerlin/localmachines.git
4a. Install Glide (optional)
curl https://glide.sh/get | sh
The Glide Github page shows alternative installation methods.
4b. Install the Go dependencies either using Glide:
cd localmachines
glide install
But you can also install them manually if you wish.
ImageMagick is also needed.
-
Initialize database
mysql -u user -p fabsmith < fabsmith_template.sql bee migrate -conn="root:@tcp(127.0.0.1:3306)/fabsmith"
-
Setup the backend server:
cp conf/app.example.conf conf/app.conf
-
Install Node.js LTS
-
npm -g install bower npm -g install grunt
-
Setup React frontend:
cd clients/machines npm install cd $GOPATH/src/github.com/FabLabBerlin/localmachines
-
Setup Angular frontends:
cd clients/admin
npm install
bower install
cd clients/signup
npm install
bower install
cd $GOPATH/src/github.com/FabLabBerlin/localmachines
- Now you should be able to start the server with:
bee run
or alternatively:
go build
./localmachines
Detailed instruction on how to install Localmachines on OS X are here.
Instructions on how to install Localmachines on a Raspberry Pi are here.
localmachines uses versioning scheme that is loosely following the rules of Semantic Versioning: [MAJOR].[MINOR].[PATCH]
clients/ HTML5 Clients for Admins and End-Users
conf/ Config files
controllers/ Backend Controllers
database/ Database Migrations
docs/ Documentation
files/ Invoices
gateway/ Lab "IoT" Gateway Server
icons/ Icons
lib/ Reusable general purpose functions
models/ Backend Models
netswitch/ NetSwitch TestKit
pinger/ Ping tool
routers/ Backend Routes
scripts/ Scripts
swagger/ Swagger Documentation Generator
tests/ Backend Unit Tests
dev/ Development files
dev/assets/ Static Assets
dev/assets/css/ Generated CSS (do not edit)
dev/assets/img/ Images
dev/assets/js/ Misc. Javascript (deprecated)
dev/assets/less/ Less files (edit CSS from here)
dev/ng-components/ External Angular Modules
dev/ng-components/version/
dev/ng-modules/ Angular Modules
dev/ng-modules/activations/ Activations Page
dev/ng-modules/api/ HTTP API helper functions
dev/ng-modules/bookings/ Bookings (obsolete)
dev/ng-modules/coworking/ Co-Working
dev/ng-modules/dashboard/ Dashboard with Metrics
dev/ng-modules/invoices/ Invoices
dev/ng-modules/login/ Login
dev/ng-modules/machine/ Edit a Machine
dev/ng-modules/machines/ Machine List
dev/ng-modules/mainmenu/ Menu in the Header
dev/ng-modules/membership/ Edit a Memberhip
dev/ng-modules/memberships/ Memberships List
dev/ng-modules/priceunit/ Select box for Price Unit
dev/ng-modules/productlist/ Generic Product List Table
dev/ng-modules/randomtoken/ "Are you sure?" generator
dev/ng-modules/reservation/ Edit a Reservation
dev/ng-modules/reservations/ Reservations List
dev/ng-modules/settings/ Settings View
dev/ng-modules/space/ Edit a Space
dev/ng-modules/spacepurchase/ Create a Space Purchase
dev/ng-modules/spaces/ Space List
dev/ng-modules/tutoring/ Tutoring
dev/ng-modules/user/ Create/Edit a User
dev/ng-modules/users/ Users List
prod/ Production files ("`grunt prod`"")
...
- Create directory
dev/ng-modules/foobar
- Create a file
dev/ng-modules/foobar/foobar.js
A basic controller looks like this:
(function(){
'use strict';
var app = angular.module('fabsmith.admin.foobar',
['ngRoute', 'ngCookies']);
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/foobar', {
templateUrl: 'ng-modules/foobar/foobar.html',
controller: 'FoobarCtrl'
});
}]); // app.config
app.controller('FoobarCtrl', ['$scope', '$http', '$location', 'randomToken',
function($scope, $http, $location, randomToken) {
$http({
method: 'GET',
url: '/api/my_data'
})
.success(function(myData) {
$scope.myData = myData;
toastr.info('My Data successfully loaded.');
})
.error(function() {
toastr.error('Error loading data');
});
$scope.hello = function() {
toastr.info('Hello World!');
};
}]); // app.controller
})(); // closure
- Create a file
dev/ng-modules/foobar/foobar.html
:
<div id="admin-global-config" class="container-fluid">
<h1>Foobar</h1>
<h2>My Data: {{myData.Bar}}</h2>
<button class="btn btn-primary btn-lg"
ng-click="hello()">
Say hello
</button>
</div> <!-- /container -->
- Load the JS file in
dev/index.html
:
Add it after the other module files:
...
<script src="/admin/ng-modules/tutoring/tutoring.js"></script>
<script src="/admin/ng-modules/tutoring/tutor.js"></script>
<script src="/admin/ng-modules/tutoring/purchase.js"></script>
<script src="/admin/ng-modules/foobar/foobar.js"></script>
- Load the module in
dev/ng-main.js
:
Add it after the other modules:
var app = angular.module('fabsmith', [
'ngRoute',
'fabsmith.admin.login',
'fabsmith.admin.api',
'fabsmith.admin.coworking',
...
'fabsmith.admin.foobar'
]);
The source code is within the src/
subdirectory:
assets/ Static Assets
assets/less/ Less files (edit CSS from here)
js/ JS code
js/actions/ Flux Actions
js/actions/__test__/
js/components/ React Components
js/components/Feedback/ Feedback Form
js/components/Feedback/__test__/
js/components/Login/ Login Pages
js/components/Login/__test__/
js/components/MachinePage/ Machine Page
js/components/MachinePage/__test__/
js/components/MachinePage/Machine/ Machine in the List
js/components/Reservations/ Reservations Module
js/components/Reservations/__test__/
js/components/UserProfile/ User Profile Page
js/components/UserProfile/__test__/
js/stores/ Flux Stores
js/stores/__test__/
- Create a new file
js/actions/FooActions.js
:
var $ = require('jquery');
var actionTypes = require('../actionTypes');
var reactor = require('../reactor');
var FooActions = {
loadFoos() {
$.ajax({
url: '/api/foos',
success(foos) {
reactor.dispatch(actionTypes.SET_FOOS, { foos });
},
error(xhr, status, err) {
toastr.error('Error loading locations');
}
});
}
};
export default FooActions;
Note that we make a lot of use of ECMAScript 6 here. When calling a function
function foobar({data}) {...}
, the function must be called with
foobar({data})
. If you call it with foobar({baz})
, data
will be
undefined. So parameters names must match!
- Declare the new Action Type
SET_FOOS
injs/actionTypes.js
:
var keyMirror = require('react/lib/keyMirror');
export default keyMirror({
API_GET_LOGOUT: null,
SUCCESS_LOGIN: null,
...
SET_FOOS: null
});
- Create a Flux store in
js/stores/FooStore.js
:
var actionTypes = require('../actionTypes');
var Nuclear = require('nuclear-js');
var toImmutable = Nuclear.toImmutable;
const initialState = toImmutable({
foos: undefined
});
var FooStore = new Nuclear.Store({
getInitialState() {
return initialState;
},
initialize() {
this.on(actionTypes.SET_FOOS, setFoos);
}
});
function setFoos(state, { foos }) {
return state.set('foos', foos);
}
export default FooStore;
- Register the Flux store in
js/main.js
:
Add
var FooStore = require('./stores/FooStore');
to the imports and register the actual store:
reactor.registerStores({
feedbackStore: FeedbackStore,
globalStore: GlobalStore,
loginStore: LoginStore,
machineStore: MachineStore,
reservationsStore: ReservationsStore,
reservationRulesStore: ReservationRulesStore,
scrollNavStore: ScrollNavStore,
tutoringsStore: TutoringsStore,
userStore: UserStore,
locationStore: LocationStore,
fooStore: FooStore
});
- Write a getter in
js/getters.js
:
const getFoos = [
['fooStore'],
(fooStore) => {
return fooStore.get('foos');
}
];
and export it at the bottom of the file:
export default {
...
getTutorings,
getFoos
};
- Write the React Class in
js/components/Foo.js
:
var _ = require('lodash');
var FooActions = require('../../actions/FooActions');
var getters = require('../../getters');
var React = require('react');
var reactor = require('../../reactor');
var toastr = require('../../toastr');
var FooWidget = React.createClass({
mixins: [ reactor.ReactMixin ],
getDataBindings() {
return {
foos: getters.getFoos
};
},
componentDidMount() {
FooActions.loadFoos();
},
click() {
toastr.info('Click!');
},
render() {
return (
<div className="container">
{
_.isUndefined(this.state.foos) ?
(
<div>Please be patient, loading data...</div>
) : (
<div>
<h2>Foos</h2>
{this.state.foos.map((foo, i) => {
<h3>Foo No. {i} is named {foo.Name}</h3>
})}
<button onClick={this.click}>Click me</button>
</div>
)
}
</div>
);
}
});
export default FooWidget;
- Now the component can be used in other components:
var Foo = require('./Foo');
...
<Foo/>
Go uses packages to encapsulate code and data. In a package, only the functions and variables with uppercase names are "public", the others are "private". Declare functions as private to keep the APIs lean.
You can get an overview with the godoc
tool:
godoc -http=:6060
Now navigate with your Web Browser to the localmachines pkg on localhost:6060. There you get an interactive overview of the whole Backend code.
The development environment is a constant work in progress and does not implement a decent test-driven development workflow yet. Soon.
When ready to move the system to prod
runmode, run grunt prod
to compile the production mode of the Angular JS applications.
More info about the development workflow will be added to Wiki.
Go files should have a maximum of 80 characters per lines, JS files 100 characters per line maximum.
Local variables should be as short as possible. Public variables and functions must have terse but quickly comprehendable names.
Use ./testall
to run all tests in the project. Make sure that the file
./tests/conf/app.conf
exists.
Core / API development. Everything that is no in the clients
directory is relevant to this part.
There is a clean separation between the Fabsmith core engine written in GoLang and the clients that operate with the REST API of the core. Clients are HTML-based and reside under the /clients
directory.
To switch to development mode, you have to change the runmode
in conf/app.conf
to dev
. Each of the clients have a dev
and prod
directory with the same hierarchy. When the runmode is dev
, clients are loaded from their dev
directory. If the runmode is prod
, clients are loaded from their prod
directories. The prod
directories of the clients contain minified and optimized files for the client interfaces.
Each of the clients is a separate Angular JS application with a Grunt-based workflow. You need to run the following commands from within each client directory separately to download all the development dependencies and be able to use the clients in dev
mode:
cd clients/machines
npm instal && bower install
cd ../admin
npm instal && bower install
Make sure that you have Bower installed (it has been added as one of the npm dependencies though) - if not, follow the instructions on Bower website.
Whenever you are finished with the client development, remember to run grunt prod
for each of the clients to compile the prod
version of the client. Also remember to edit the Gruntfile.js
before running grunt to add extra configuration in case extra libraries have been added and new modules have to be copied.
We use Automated API Document feature of the Beego framework. It makes use of Beego router namespaces and documentation comments in the router and controller files.
- Compile the documentation by using the following command
bee generate docs
- Run the server
bee run
- Access API documentation via:
http://localhost:8080/swagger/
We're providing an early-stage Docker base image for developing and running EasyLab server. This image can be found at Docker Hub.
To use this file in a CI setup, check out the '.gitlab-ci.yml' in this repository.
To use it manually with an interactive bash shell, use:
docker run -it syso/easylab-env /bin/bash
Beego watches a lot of files to automatically re-compile the code. On OS X the maximum number of open files is quite low by default. See here how to solve this: http://unix.stackexchange.com/a/221988
You need to set the timezone
export TZ=Europe/Berlin
- Merge functionality of clients/admin to clients/machines
- Merge functionality of clients/signup to clients/machines
- Flatten directory structure in clients