FreeNAS 10 development is currently supported on FreeBSD, Mac OS X, and some Linux distributions. Windows users might be able to get it working, but it's not and will not be an officially supported platform for development.
If you already have node.js installed and the repo checked out, great! Skip to "Using the FreeNAS 10 Development Environment" below.
Otherwise, to begin developing for the FreeNAS 10 GUI on one of these platforms, simply do:
curl -o- https://raw.githubusercontent.com/freenas/gui/master/bootstrap.sh | sh
This will analyze your development environment and bootstrap the development toolchain onto it as necessary, also checking out a working copy of this git repository for you. It will also install node and npm if they aren't already available.
Alternatively, if you have already cloned this repo then just do
sh bootstrap.sh
from the root of it to accomplish the same thing.
If you used the bootstrap script, you'll already be ready to run 'gulp'. If not, run
npm install -g bower gulp forever jshint jscs esprima-fb@15001.1.0-dev-harmony-fb
and npm install
from the root of the repo first.
Once your development environment is initialized, run 'gulp' to start the FreeNAS 10 SDK app.
You will need to choose whether to target a real FreeNAS instance in order to interact with the middleware, or to operate in "dumb mode", in which case it will run a local copy of the GUI webapp that simulates interaction with real data. 'gulp --connect FreenasIPorHostname' will start you in live development mode with a real connection. 'gulp' alone will start you with a local webserver only, so you can work on UI elements for as-yet-unavailable middleware functionality.
Once the app is running, it will monitor your source files and automatically rebuild and restart the GUI every time a file changes.
This section will explain how to implement a complete FreeNAS 10 GUI section. It will rely heavily on the FreeNAS 10 GUI Architecture documentation. We strongly suggest you read that first. We will also assume you are already familiar with React and JSX.
Code for the actual FreeNAS webapp resides in the app/src/scripts directory. We will be using the Accounts view for this guide. It is one of the primary views, and it touches on nearly every part of the FreeNAS GUI codebase.
The main sections of the FreeNAS UI are the views, reachable from the primary navigation sidebar on the left side of the webapp. Accounts is one of the primary views, providing access to all the users and groups on the system.
The main JSX file for a given view resides in the app/src/scripts/react/views directory. Any related sub-components are placed in a corresponding sub-directory within app/src/scripts/react/views. This same pattern holds all the way down. For example, Users.jsx and Groups.jx can be within the app/src/scripts/react/views/Accounts directory, and GroupItem.jsx, GroupEdit.jsx, GroupView.jsx, and GroupAdd.jsx can be found within app/src/scripts/react/views/Accounts/Groups. This mirrors the route nesting in routes.js.
Accounts.jsx is primarily just a wrapper for its subsections, Users and Groups. It provides the tabbed navigation structure at the top of the view using the SectionNav component. Accounts just renders the SectionNav and then the RouteHandler determined by the active route, which can be either /accounts/users or /accouts/groups. For more on routing in the FreeNAS 10 GUI, see the Routing section of the architecture docs.
Next we'll look at a nontrivial component, Groups.
Groups is a component that manages the display of system- and user- created groups. It uses the Viewer component to construct various different modes of displaying the data, but its main role is managing the live data for display to the end user. This places it at the React View position in the Flux data lifecycle. We will use Groups as an example to demonstrate the Flux data lifecycle in FreeNAS 10 in detail.
Any component that wants to acquire data from the middleware must use a number of standard lifecycle functions. During componentDidMount, it must:
Correspondingly, during componentWillUnmount, it must:
In Groups, this looks like:
, componentDidMount: function () {
GS.addChangeListener( this.handleGroupsChange );
GM.requestGroupsList();
GM.subscribe( this.constructor.displayName );
US.addChangeListener( this.handleUsersChange );
UM.requestUsersList();
UM.subscribe( tthis.constructor.displayName );
}
, componentWillUnmount: function () {
GS.removeChangeListener( this.handleGroupsChange );
GM.unsubscribe( this.constructor.displayName );
US.removeChangeListener( this.handleUsersChange );
UM.unsubscribe( this.constructor.displayName );
}
This is straightforward, but any missing parts of this process will result in either missing steps in the data lifecycle or broken app behavior. Groups also defines some simple functions to use for populating state and as changeListeners.
The functions being called in the lifecycle functions above come from two main groups: The Middleware Utility Classes and the Flux Stores. We'll look at a middleware utility class next, as that's the next step in the flux data lifecycle.
As described in the Middleware Utility Class section of the architecture documentation, a middleware utility class provides an abstraction for all calls to the middleware client. Middleware utility classes should generally be focused on a particular namespace in the middleware. In this case, it's groups. All middleware utility classes should live in the TBD3 directory and be named according to the namespace the cover.
As FreeNAS GUI development proceeds against features without corresponding middleware functionality, middleware utility classes will also need to be adapted to TBD HOW WE HANDLE THE NEW LIFECYCLE
All middleware utility classes reside in app/src/scripts/flux/middleware .
The basic functions nearly any middleware utility class will need are
subscribe
, unsubscribe
, and at least one
request
function. These functions simply wrap common
middleware calls. subscribe
and unsubscribe
also take a string as the name of the function to subscribe. This is
used only for recordkeeping and debugging - it does not change the
behavior of any responses. GroupsMiddleware implements these functions
as follows:
static subscribe ( componentID ) {
MC.subscribe( [ "groups.changed" ], componentID );
MC.subscribe( [ "task.*" ], componentID );
}
static unsubscribe ( componentID ) {
MC.unsubscribe( [ "groups.changed" ], componentID );
MC.unsubscribe( [ "task.*" ], componentID );
}
static requestGroupsList () {
MC.request( "groups.query"
, []
, GAC.receiveGroupsList
);
}
The rest of the functions in a middleware utility class wrap other
middleware calls as necessary. Functions that anticipate a response
from the server (including the request
function above)
should pass an appropriate action creator function as a callback. See
the next section for an example of an action creator.
static createGroup ( newGroupProps ) {
MC.request( "task.submit"
, [ "groups.create" , [ newGroupProps ] ]
, GAC.receiveGroupUpdateTask
);
}
static updateGroup ( groupID, props ) {
MC.request( "task.submit"
, [ "groups.update", [ groupID, props ] ]
, GAC.receiveGroupUpdateTask.bind( GAC, groupID )
);
}
static deleteGroup ( groupID ) {
MC.request( "task.submit"
, [ "groups.delete", [ groupID ] ]
, GAC.receiveGroupUpdateTask.bind( GAC, groupID )
);
}
As described in the
Action Creators section of
the architecture documentation, the action creator packages
responses from the middleware using pre-defined event types, and then
calls FreeNASDispatcher.handleMiddlewareAction()
. This
helps Flux Stores to identify relevant middleware events and respond
appropriately, without overloading the Dispatcher to both tag and
dispatch events.
All action creators reside in app/src/scripts/flux/actions .
Most action creators will be very short - they simply have a function for each type of event they anticipate receiving from the middleware and tag each event accordingly.
static receiveGroupsList ( groupsList, timestamp ) {
FreeNASDispatcher.handleMiddlewareAction(
{ type: ActionTypes.RECEIVE_GROUPS_LIST
, timestamp
, groupsList
}
);
}
static receiveGroupUpdateTask ( groupID, taskID, timestamp ) {
FreeNASDispatcher.handleMiddlewareAction(
{ type: ActionTypes.RECEIVE_GROUP_UPDATE_TASK
, timestamp
, taskID
, groupID
}
);
}
Action creator functions should only be passed as callbacks by middleware utility classes anticipating responses of that type. Once an event is received and tagged, it is passed to the Dispatcher, which in turn broadcasts it to all Flux stores.
Action types for ActionCreators are defined in FreeNASConstants . A quick side trip there produces the following:
// Groups
, RECEIVE_GROUPS_LIST: null
, RECEIVE_GROUP_UPDATE_TASK: null
FreeNAS 10 GUI uses a strict style guide and a highly encouraged set of other best practices for all JavaScript and JSX code. The goal of these rules is to make contibuting to FreeNAS 10 easier, by reducing the need to grasp and duplicate multiple code styles in different parts of the codebase. Additionally, several of the rules are aimed toward increasing readability and making simple errors immediately obvious.
The tool used for testing for and displaying style guide deviations is
JSCS. For the exact JSCS rules used in FreeNAS 10, look at the
.jscsrc
file in the root of the FreeNAS 10 source code.
The FreeNAS 10 Frontend Development Environment includes a tool for automatically checking compliance with all style guide rules. When a GUI rebuild is triggered, it will scan the entire codebase and print all the style guide violations it detects. Eventually the tool will bail out and refuse to update the GUI if it detects any style guide violations at all. There are also JSCS Plugins for popular editors that show JavaScript errors, warnings, and style guide deviations live as you develop.
Our style rules are based largely on the rules from the node style guide , with selected rules added from the npm style guide . This guide is largely adapted from those two sources. Accordingly, this style guide is licenced under CC-BY-SA.
This is typically for consideration to those using terminal editors with limited space. However, the real reason we’re requiring this is to force you to keep your code relatively concise. If you find yourself in need of 120 characters to express a single statement, consider rewriting it. A number of other rules in this guide will help with keeping your lines short.
When you use a keyword that requires a bracket to contain the subsequent statement, put the opening brace of that statement on the same line as the keyword.
let goodTimes = true;
if ( goodTimes ) { // Good
console.log( "That's the way uh huh uh huh we like it." );
} else if ( "pls no" ) // Bad
{
console.log( "KNF has its place, but it is not here." );
}
If you must chain method after method, put one on each line, leading with the dot. This is your way around the 80-character limit when you really need that long statement.
User
.findOne({ name: 'foo' })
.populate( "bar" )
.exec( function( err, user ) {
return true;
});
Declaring a bunch of variables on one line may be appealing, but it makes finding where a variable is declared annoying and can easily lead to simple mistakes (like missing commas). Type var, const, or let once per variable.
// Good:
let foo;
let bar;
let baz;
//Bad:
let
foo
, bar
, baz;
// Very Bad:
let foo, bar. baz; //oops!
Fuzzy comparisons result in fuzzy bugs. Use ===, or lodash
isEqual
, rather than ==. != is right out.
This is a bit different. Basically, when you’re listing a bunch of things on multiple lines, each line should start with a comma except the first one. This lets you line up all the commas under the opening brace or bracket, as a bonus.
The chief benefit of this is that it’s immediately obvious when you’ve forgotten to put a comma between two items. It also makes those long arrays and objects much easier to read.
// Good: let bestArray = [ foo , bar , baz ]; // Bad: let badArray = [ foo, bar, baz ]; // Very Bad: let uncoolObject { foo: "Don't", bar: "do" baz: "this" }; // Broke it again!
All GUI code must use two-space indentation. Not two-space tabs - two spaces. On the bright side, that will give you some extra space to work with compared to 4-space or 8-space tabs, because we also use 80-column lines.
Whitespace at the end of a line has no reason to exist. This also means that when a line is just a newline, there shouldn’t be any spaces or tabs in it.
For just about any keyword that is followed by a parenthesized statement, put a single space before the opening parenthesis. Function calls are once case where you should not use a space before a parenthesis.
let youDoTheGoodThing = true;
let youDoTheBadThing = { please: "don't" };
if ( youDoTheGoodThing ) {
console.log( "Everyone will be happy!" );
} else if( youDoTheBadThing ){
console.log( "Everyone, especially you, will be sad when your "
+ "code is full of warnings." );
}
Don’t press your braces, brackets, and parentheses up against their contents. The only exception is when it’s an array or object and the very next character is another brace or bracket. This is mostly for readability.
// Good:
let floor = { room: "for activities" };
let hardwareStore = [ "look"
, "at"
, "all"
, "this"
, "stuff" ];
// Bad:
let iStealPets = [{ I: "have"
, so: "many" }
, { friends: null }];
let musicalChairs = ["the" // eww, it doesn't line up
, "music"
, "stops"];
Whenever you have parentheses around something, put spaces between each parenthesis and what it contains.
let youWantToDoItRight = true;
let youDontWantToDoItRight = "WHY?";
if ( youWantToDoItRight ) {
console.log( "You'll do it like this:"
, { haha: I'm printing an object" }
);
console.log( [ "check"
, "out"
, "this"
, "array"
]
);
} else if (youDontWantToDoItRight) {
console.log("Oh Man I Am Not Good With Computer"
, [ "pls"
, "to"
, "help" ]);
}
These rules apply to JSX-specific syntax. These rules are NOT
automatically enforced by JSCS, and are not yet implemented throughout
the UI. However, they will still help with readability and reasonable
line lengths, so they may be considered strongly recommended best
practices. As a matter of convention, any file that includes JSX
should have the .jsx
extension and reside in an
appropriate place within the scripts directory.
When rendering a Component with more than one prop, put each prop on a new line. The props should be indented two spaces, measuring from the “<” that starts the Component.
render: function () {
// ...
return (
<TWBS.Grid fluid> // One prop, one line.
// ...
<TWBS.Row>
TWBS.Col // Many props, many lines.
xs={3}
className="text-center">
<viewerUtil.ItemIcon
primaryString = { this.props.item["name"] }
fallbackString = { this.props.item["id"] }
seedNumber = { this.props.item["id"] } />
</TWBS.Col>
</TWBS.Row>
</TWBS.Grid>
);
}
There are JSCS plugins for a number of popular editors. This guide will cover only editors known to be in popular use among FreeNAS 10 developers.
For a list of other plugins and tools, see the JSCS website .
TODO
This was done on PC-BSD 10.1. The process for installing and configuring Syntastic may differ on your OS or distribution of choice. It assumes you have node and npm installed already.
sudo npm install -g jscs
sudo npm install -g esprima-fb
cd ~/.vim
mkdir bundle
mkdir plugin
mkdir autoload
curl -LSso ~/.vim/autoload/pathogen.vim
https://tpo.pe/pathogen.vim
cd ~/.vim/bundle
git clone https://github.com/scrooloose/syntastic.git
~/.vimrc
and add these lines to the end of it
(copy the default one over from
/usr/local/share/vim/vim74/vimrc_example.vim
if you
don't already have one):
call pathogen#infect()
set statusline+=%#warningmsg#
set statusline+=%{SyntasticStatuslineFlag()}
set statusline+=%*
let g:syntastic_always_populate_loc_list = 1
let g:syntastic_auto_loc_list = 1
let g:syntastic_check_on_open = 1
let g:syntastic_check_on_wq = 0
autocmd FileType javascript let b:syntastic_checkers = findfil('.jscsrc', '.;') != '' ? ['jscs'] : ['jshint']
This configuration will make JSCS work so long as you open files from within a terminal in the FreeNAS build directory. If you want it to work a little more universally (i.e. opening files in gVim from a file manager) you can create a symbolic link from your home directory to the .jscsrc in your FreeNAS source directory.
For more information on the Syntastic vim plugin please visit their GitHub page: Syntastic GitHub
Not yet written. Feel free to take this!
Not yet written. Feel free to take this!