├── relx ├── apps ├── web │ ├── priv │ │ ├── static │ │ │ ├── appstore │ │ │ │ ├── feed.less │ │ │ │ ├── carousel.less │ │ │ │ ├── appstore.less │ │ │ │ ├── buttons.less │ │ │ │ ├── navs.less │ │ │ │ ├── variables.less │ │ │ │ ├── account.less │ │ │ │ ├── common.less │ │ │ │ └── products.less │ │ │ ├── channel.html │ │ │ ├── img │ │ │ │ ├── p1.jpg │ │ │ │ ├── p2.jpg │ │ │ │ ├── crashsite.jpg │ │ │ │ ├── infinity.png │ │ │ │ ├── crysis3-bg1.png │ │ │ │ ├── crysis3-bg2.png │ │ │ │ ├── squad_jumping.jpg │ │ │ │ ├── theme │ │ │ │ │ ├── lghtmesh.png │ │ │ │ │ └── 45degreee_fabric.png │ │ │ │ ├── glyphicons-halflings.png │ │ │ │ ├── bootstrap-mdo-sfmoma-01.jpg │ │ │ │ ├── bootstrap-mdo-sfmoma-02.jpg │ │ │ │ ├── bootstrap-mdo-sfmoma-03.jpg │ │ │ │ ├── glyphicons-halflings-white.png │ │ │ │ ├── facebook.svg │ │ │ │ ├── twitter.svg │ │ │ │ └── google.svg │ │ │ ├── js │ │ │ │ ├── all.min.js │ │ │ │ ├── holder.js │ │ │ │ └── bootstrap.min.js │ │ │ └── css │ │ │ │ └── theme.css │ │ └── templates │ │ │ ├── prod.dtl │ │ │ ├── hd.dtl │ │ │ ├── tl.dtl │ │ │ ├── dev.dtl │ │ │ └── login.dtl │ ├── src │ │ ├── web_app.erl │ │ ├── web.app.src │ │ ├── login.erl │ │ ├── dashboard.erl │ │ ├── routes.erl │ │ ├── web_sup.erl │ │ ├── element_carousel.erl │ │ ├── reviews.erl │ │ ├── index.erl │ │ ├── chat.erl │ │ ├── account.erl │ │ ├── products.erl │ │ ├── feed.erl │ │ └── product.erl │ ├── rebar.config │ └── include │ │ ├── records.hrl │ │ └── wf.hrl └── rebar.config ├── rels └── web │ ├── files │ ├── vm.args │ ├── sys.config │ ├── erl │ ├── start_erl.cmd │ ├── install_upgrade.escript │ ├── node.cmd │ ├── nodetool │ └── node │ └── reltool.config ├── less.sh ├── Makefile ├── .gitignore ├── orderapps.erl ├── README.md ├── rebar.config ├── javascript.sh └── otp.mk /relx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erpuno/skyline/HEAD/relx -------------------------------------------------------------------------------- /apps/web/priv/static/appstore/feed.less: -------------------------------------------------------------------------------- 1 | .entry-type-tabs { 2 | } -------------------------------------------------------------------------------- /apps/web/priv/static/channel.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/priv/static/img/p1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erpuno/skyline/HEAD/apps/web/priv/static/img/p1.jpg -------------------------------------------------------------------------------- /apps/web/priv/static/img/p2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erpuno/skyline/HEAD/apps/web/priv/static/img/p2.jpg -------------------------------------------------------------------------------- /apps/web/priv/static/appstore/carousel.less: -------------------------------------------------------------------------------- 1 | .carousel-inner .item img{ 2 | margin:0 auto; 3 | width:100%; 4 | } -------------------------------------------------------------------------------- /apps/rebar.config: -------------------------------------------------------------------------------- 1 | {sub_dirs, [ 2 | "web" 3 | ]}. 4 | {lib_dirs, ["../apps"]}. 5 | {deps_dir, ["../deps"]}. 6 | -------------------------------------------------------------------------------- /apps/web/priv/static/img/crashsite.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erpuno/skyline/HEAD/apps/web/priv/static/img/crashsite.jpg -------------------------------------------------------------------------------- /apps/web/priv/static/img/infinity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erpuno/skyline/HEAD/apps/web/priv/static/img/infinity.png -------------------------------------------------------------------------------- /apps/web/priv/static/img/crysis3-bg1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erpuno/skyline/HEAD/apps/web/priv/static/img/crysis3-bg1.png -------------------------------------------------------------------------------- /apps/web/priv/static/img/crysis3-bg2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erpuno/skyline/HEAD/apps/web/priv/static/img/crysis3-bg2.png -------------------------------------------------------------------------------- /apps/web/priv/static/img/squad_jumping.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erpuno/skyline/HEAD/apps/web/priv/static/img/squad_jumping.jpg -------------------------------------------------------------------------------- /apps/web/priv/static/img/theme/lghtmesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erpuno/skyline/HEAD/apps/web/priv/static/img/theme/lghtmesh.png -------------------------------------------------------------------------------- /apps/web/priv/static/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erpuno/skyline/HEAD/apps/web/priv/static/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /apps/web/priv/static/img/theme/45degreee_fabric.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erpuno/skyline/HEAD/apps/web/priv/static/img/theme/45degreee_fabric.png -------------------------------------------------------------------------------- /rels/web/files/vm.args: -------------------------------------------------------------------------------- 1 | -sname skyline 2 | -setcookie node_runner 3 | +K true 4 | +A 5 5 | -env ERL_MAX_PORTS 4096 6 | -env ERL_FULLSWEEP_AFTER 10 7 | -------------------------------------------------------------------------------- /apps/web/priv/static/img/bootstrap-mdo-sfmoma-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erpuno/skyline/HEAD/apps/web/priv/static/img/bootstrap-mdo-sfmoma-01.jpg -------------------------------------------------------------------------------- /apps/web/priv/static/img/bootstrap-mdo-sfmoma-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erpuno/skyline/HEAD/apps/web/priv/static/img/bootstrap-mdo-sfmoma-02.jpg -------------------------------------------------------------------------------- /apps/web/priv/static/img/bootstrap-mdo-sfmoma-03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erpuno/skyline/HEAD/apps/web/priv/static/img/bootstrap-mdo-sfmoma-03.jpg -------------------------------------------------------------------------------- /apps/web/priv/static/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erpuno/skyline/HEAD/apps/web/priv/static/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /apps/web/src/web_app.erl: -------------------------------------------------------------------------------- 1 | -module(web_app). 2 | -behaviour(application). 3 | -export([start/2, stop/1]). 4 | 5 | start(_StartType, _StartArgs) -> web_sup:start_link(). 6 | stop(_State) ->ok. 7 | -------------------------------------------------------------------------------- /less.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | APPSTORE=apps/web/priv/static/appstore 4 | CSS=apps/web/priv/static/css 5 | 6 | NODE_APP=node 7 | 8 | $NODE_APP apps/web/priv/static/less/bin/lessc -x $APPSTORE/appstore.less > $CSS/appstore.css 9 | 10 | echo $? 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | RELEASE := web 2 | COOKIE := node_runner 3 | VER := 1.0.0 4 | FILES := apps/web/priv/static/n2o 5 | 6 | default: compile static-link 7 | static-link: 8 | rm -rf $(FILES) 9 | ln -sf ../../../../deps/n2o/priv $(FILES) 10 | 11 | include otp.mk 12 | -------------------------------------------------------------------------------- /apps/web/src/web.app.src: -------------------------------------------------------------------------------- 1 | {application, web, 2 | [ 3 | {description, "SKY VXZ Skyline N2O Demo"}, 4 | {vsn, "1"}, 5 | {registered, []}, 6 | {applications, [kernel, stdlib, xmerl, erlydtl, cowboy, n2o, kvs]}, 7 | {mod, { web_app, []}}, 8 | {env, []} 9 | ]}. 10 | -------------------------------------------------------------------------------- /apps/web/priv/static/appstore/appstore.less: -------------------------------------------------------------------------------- 1 | @import "variables.less"; 2 | @import "../bootstrap/less/bootstrap.less"; 3 | @import "../bootstrap/less/responsive.less"; 4 | @import "common.less"; 5 | @import "buttons.less"; 6 | @import "navs.less"; 7 | @import "carousel.less"; 8 | @import "products.less"; 9 | @import "feed.less"; 10 | @import "account.less"; -------------------------------------------------------------------------------- /apps/web/priv/static/appstore/buttons.less: -------------------------------------------------------------------------------- 1 | .btn-google-plus { 2 | .buttonBackground(@btnGooglePlusBackground, @btnGooglePlusBackgroundHighlight); 3 | & .icon-google-plus{margin-right:12px;} 4 | 5 | } 6 | 7 | .btn{& .icon-facebook{margin-right:12px;} & .icon-twitter{margin-right:12px;}} 8 | 9 | .btn-google-plus.active { 10 | color: rgba(255,255,255,.75); 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node 2 | ebin 3 | .applist 4 | .rebarinfo 5 | .dialyzer.plt 6 | *.dets 7 | *.gz 8 | *.dump 9 | apps/web/priv/static/n2o 10 | apps/web/priv/static/bootstrap 11 | apps/web/priv/static/font-awesome 12 | apps/web/priv/static/less 13 | apps/web/priv/static/tinymce 14 | deps 15 | bootstrap.min.js 16 | _rel 17 | kai 18 | log 19 | rels 20 | .idea/ 21 | apps/web/.rebar/ 22 | rebar 23 | skyline.iml -------------------------------------------------------------------------------- /apps/web/rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps_dir, ["../../deps"]}. 3 | {deps, [n2o,cowboy,{erlydtl,".*",{git,"git://github.com/evanmiller/erlydtl.git",{tag,"0.8.0"}}}]}. 4 | {erlydtl_opts, [ 5 | {doc_root, "priv/templates"}, 6 | {out_dir, "ebin"}, 7 | {compiler_options, [report, return, debug_info]}, 8 | {source_ext, ".dtl"}, 9 | {module_ext, "_view"} 10 | ]}. 11 | -------------------------------------------------------------------------------- /apps/web/include/records.hrl: -------------------------------------------------------------------------------- 1 | -include_lib("nitro/include/nitro.hrl"). 2 | 3 | -record(feed_entry, {?ELEMENT_BASE(product), entry}). 4 | -record(feed_comment, {?ELEMENT_BASE(product), comment}). 5 | -record(feed_media, {?ELEMENT_BASE(product), media, target, fid=0, cid=0, only_thumb}). 6 | -record(product_figure, {?ELEMENT_BASE(product), product}). 7 | 8 | -record(media, { 9 | id, 10 | title :: iolist(), 11 | width, 12 | height, 13 | html :: iolist(), 14 | url :: iolist(), 15 | version, 16 | thumbnail_url :: iolist(), 17 | type :: {atom(), atom() | string()}, 18 | thumbnail_height}). 19 | -------------------------------------------------------------------------------- /rels/web/files/sys.config: -------------------------------------------------------------------------------- 1 | [{n2o, [{port,8001},{transition_port,8001},{route,routes},{login,"/account"},{log_modules,index}]}, 2 | {web, 3 | [{http_address, "http://skyline.synrc.com"}, 4 | {fb_id, "385389014900491"}, 5 | {fb_secret, []}, 6 | {tw_consumer_key, "qfrLWd9KPpK8cz0pWmBg"}, 7 | {tw_consumer_secret, "FTaon5hzrucCrMGsULMc6Zd6sYEkuiZy36LLURTFaoU"}, 8 | {gplus_client_id, "146782506820.apps.googleusercontent.com"}, 9 | {gplus_cookiepolicy, "http://synrc.com"}]}, 10 | {kvs, 11 | [{pass_init_db,true}, 12 | {nodes,[]}, 13 | {log_modules,index}, 14 | {dba, store_mnesia}, 15 | {schema, [kvs_user, kvs_acl, kvs_feed, kvs_subscription ]}]} 16 | ]. 17 | -------------------------------------------------------------------------------- /apps/web/src/login.erl: -------------------------------------------------------------------------------- 1 | -module(login). 2 | -compile(export_all). 3 | -include_lib("avz/include/avz.hrl"). 4 | -include_lib("n2o/include/wf.hrl"). 5 | -include_lib("kvs/include/user.hrl"). 6 | -include_lib("nitro/include/nitro.hrl"). 7 | 8 | main() -> 9 | avz:callbacks(?METHODS), 10 | [ #dtl{file = "login", ext="dtl",bindings=[{title,<<"Login">>}, 11 | {header,index:header()}, 12 | {footer,index:footer()}, 13 | {sdk,avz:sdk(?METHODS)}, 14 | {buttons,avz:buttons(?METHODS)}]} ]. 15 | 16 | event({counter,C}) -> wf:update(onlinenumber,wf:to_list(C)); 17 | event(X) -> 18 | avz:event(X). 19 | api_event(X,Y,Z) -> avz:api_event(X,Y,Z). 20 | -------------------------------------------------------------------------------- /apps/web/priv/static/img/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /orderapps.erl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | 3 | % This script boots up the Reltool Server for apps ordering 4 | % It also could generate reltool.config 5 | 6 | -module(orderapps). 7 | -compile([export_all]). 8 | 9 | relconfig(Apps) -> 10 | LibDirs = [Dir || Dir <- ["apps", "deps"], case file:read_file_info(Dir) of {ok, _} -> true; _ -> false end], 11 | {sys, [{lib_dirs,LibDirs}, {rel,"node","1",Apps}, {profile, embedded}, 12 | {boot_rel,"node"}, {app,observer,[{incl_cond,exclude}]} ]}. 13 | 14 | main([]) -> io:format("usage: ./orderapps.erl apps~n", []); 15 | main(MainApps) -> 16 | Relconfig = relconfig([list_to_atom(A) || A <- MainApps]), 17 | {ok, Server} = reltool:start_server([{config, Relconfig}]), 18 | {ok, {release, _Node, _Erts, Apps}} = reltool_server:get_rel(Server, "node"), 19 | Ordered = [element(1, A) || A <- Apps], 20 | io:format("~w~n", [Ordered]). 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Skyline: N2O Sample Website 2 | =========================== 3 | 4 | Skyline was made with Nitrogen DSL and based on [N2O](https://github.com/5HT/n2o). 5 | It is a demo how to use N2O in real-life applications. 6 | Feel free to use it in your projects. It is provided as is in public domain. 7 | 8 | ![Login](http://synrc.com/images/N2O+Bootstrap.png) 9 | 10 | Prerequisites 11 | ============= 12 | 13 | * Erlang R16: sudo apt-get install erlang 14 | * Rebar: https://github.com/rebar/rebar 15 | * iNotify Tools (Linux only): sudo apt-get install inotify-tools 16 | 17 | Startup 18 | ------------- 19 | 20 | make 21 | make console 22 | 23 | Init Database 24 | ------------- 25 | 26 | In Erlang Shell perform 27 | 28 | > kvs:join(). 29 | 30 | And open in browser [http://localhost:8001](http://localhost:8001) 31 | 32 | Credits 33 | ------- 34 | 35 | * Maxim Sokhatsky 36 | * Andrew Zadorozhny 37 | 38 | OM A HUM 39 | -------------------------------------------------------------------------------- /apps/web/src/dashboard.erl: -------------------------------------------------------------------------------- 1 | -module(dashboard). 2 | -compile(export_all). 3 | -include_lib("n2o/include/wf.hrl"). 4 | -include_lib("nitro/include/nitro.hrl"). 5 | 6 | 7 | sidebar_menu(Page)-> 8 | Lis = [#li{class=if Page==I->active;true->[]end, body=#link{url="/"++atom_to_list(I), body=T}} 9 | || {I, T} <- [{products, <<"products">>}, {reviews, <<"reviews">>}, {account, <<"account">>}]], 10 | #panel{class=["docs-sidebar-menu", "dash-sidebar-menu"], body=#list{class=[nav, "nav-list", "docs-sidebar-nav", "dash-sidebar-nav", "affix-top"], 11 | data_fields=[{<<"data-spy">>, <<"affix">>}], 12 | body=Lis}}. 13 | 14 | section(Body, Icon) -> 15 | #section{class=["row-fluid", "dashboard-section"], body=[ 16 | #panel{class=[span1], body=#i{class=[Icon, "icon-2x"]}}, 17 | #panel{class=[span11, "dashboard-unit"], body=Body} 18 | ]}. 19 | 20 | broadcast_hello() -> 21 | spawn(fun()-> wf:wire(#alert{text="Hello, There!"}), wf:flush(room) end). 22 | -------------------------------------------------------------------------------- /apps/web/src/routes.erl: -------------------------------------------------------------------------------- 1 | -module (routes). 2 | -author('Maxim Sokhatsky'). 3 | -behaviour (route_handler). 4 | -include_lib("n2o/include/wf.hrl"). 5 | -export([init/2, finish/2]). 6 | 7 | finish(State, Ctx) -> {ok, State, Ctx}. 8 | init(State, Ctx) -> 9 | Path = wf:path(Ctx#cx.req), 10 | Module = route_prefix(Path), 11 | {ok, State, Ctx#cx{path=Path,module=Module}}. 12 | 13 | route_prefix(<<"/ws/",P/binary>>) -> route(P); 14 | route_prefix(<<"/",P/binary>>) -> route(P); 15 | route_prefix(P) -> route(P). 16 | 17 | route(<<>>) -> index; 18 | route(<<"index">>) -> index; 19 | route(<<"login">>) -> login; 20 | route(<<"feed">>) -> feed; 21 | route(<<"account">>) -> account; 22 | route(<<"products">>) -> products; 23 | route(<<"product">>) -> product; 24 | route(<<"reviews">>) -> reviews; 25 | route(<<"chat">>) -> chat; 26 | route(<<"favicon.ico">>) -> static_file; 27 | route(_) -> index. 28 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {sub_dirs,["deps","apps"]}. 2 | {lib_dirs,["apps","deps"]}. 3 | {deps_dir,["deps"]}. 4 | {deps, [ 5 | {"../apps/web/priv/static/bootstrap", ".*", {git, "git://github.com/twbs/bootstrap", {tag,"v2.3.2"}}, [raw]}, 6 | {"../apps/web/priv/static/less", ".*", {git, "git://github.com/less/less.js.git", {tag,"v1.5.0"}}, [raw]}, 7 | {"../apps/web/priv/static/font-awesome", ".*", {git, "git://github.com/FortAwesome/Font-Awesome", {tag,"v3.2.1"}}, [raw]}, 8 | {n2o, ".*", {git, "git://github.com/5HT/n2o.git", {tag,"3.4"}}}, 9 | {kvs, ".*", {git, "git://github.com/synrc/kvs.git", {tag,"3.4"}}}, 10 | {avz, ".*", {git, "git://github.com/synrc/avz.git", {tag,"3.4"}}}, 11 | {active, ".*", {git, "git://github.com/synrc/active", "master"}}, 12 | {recon, ".*", {git, "git@github.com:ferd/recon.git", "master"}} 13 | ]}. 14 | -------------------------------------------------------------------------------- /javascript.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | uglifyjs=`which uglifyjs` 4 | uglifyjs_args='-nc' 5 | 6 | check_uglifyjs() { 7 | if [ "$uglifyjs" == "" ] ; then 8 | echo "ERROR: uglifyjs not found. Please set uglifyjs in javascript.sh" 9 | exit 1; 10 | fi 11 | } 12 | 13 | create_script() { 14 | output="`pwd`/$2" 15 | echo "Generating `basename $output` in `dirname $output`" 16 | cp /dev/null $output 17 | 18 | cd $1 19 | 20 | # sort hack to keep order: 21 | for js in `ls -p -S *.js | egrep -v $3`; do 22 | echo "-> $js" 23 | $uglifyjs $uglifyjs_args $js >> $output 24 | done 25 | 26 | echo "done." 27 | cd - 28 | } 29 | 30 | check_uglifyjs 31 | 32 | create_script apps/web/priv/static/bootstrap/js apps/web/priv/static/js/bootstrap.min.js "/" 33 | create_script deps/n2o/priv apps/web/priv/static/js/all.min.js "/|all.js|zepto.js|minified.js" 34 | 35 | echo $? 36 | -------------------------------------------------------------------------------- /apps/web/priv/templates/prod.dtl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{title}} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {{body}} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /rels/web/files/erl: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## This script replaces the default "erl" in erts-VSN/bin. This is necessary 4 | ## as escript depends on erl and in turn, erl depends on having access to a 5 | ## bootscript (start.boot). Note that this script is ONLY invoked as a side-effect 6 | ## of running escript -- the embedded node bypasses erl and uses erlexec directly 7 | ## (as it should). 8 | ## 9 | ## Note that this script makes the assumption that there is a start_clean.boot 10 | ## file available in $ROOTDIR/release/VSN. 11 | 12 | # Determine the abspath of where this script is executing from. 13 | ERTS_BIN_DIR=$(cd ${0%/*} && pwd) 14 | 15 | # Now determine the root directory -- this script runs from erts-VSN/bin, 16 | # so we simply need to strip off two dirs from the end of the ERTS_BIN_DIR 17 | # path. 18 | ROOTDIR=${ERTS_BIN_DIR%/*/*} 19 | 20 | # Parse out release and erts info 21 | START_ERL=`cat $ROOTDIR/releases/start_erl.data` 22 | ERTS_VSN=${START_ERL% *} 23 | APP_VSN=${START_ERL#* } 24 | 25 | BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin 26 | EMU=beam 27 | PROGNAME=`echo $0 | sed 's/.*\\///'` 28 | CMD="$BINDIR/erlexec" 29 | export EMU 30 | export ROOTDIR 31 | export BINDIR 32 | export PROGNAME 33 | 34 | exec $CMD -boot $ROOTDIR/releases/$APP_VSN/start_clean ${1+"$@"} 35 | -------------------------------------------------------------------------------- /apps/web/priv/static/img/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 13 | 14 | -------------------------------------------------------------------------------- /apps/web/priv/templates/hd.dtl: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | -------------------------------------------------------------------------------- /rels/web/files/start_erl.cmd: -------------------------------------------------------------------------------- 1 | @setlocal 2 | 3 | @rem Parse arguments. erlsrv.exe prepends erl arguments prior to first ++. 4 | @rem Other args are position dependent. 5 | @set args="%*" 6 | @for /F "delims=++ tokens=1,2,3" %%I in (%args%) do @( 7 | @set erl_args=%%I 8 | @call :set_trim node_name %%J 9 | @rem Trim spaces from the left of %%K (node_root), which may have spaces inside 10 | @for /f "tokens=* delims= " %%a in ("%%K") do @set node_root=%%a 11 | ) 12 | 13 | @set releases_dir=%node_root%\releases 14 | 15 | @rem parse ERTS version and release version from start_erl.dat 16 | @for /F "usebackq tokens=1,2" %%I in ("%releases_dir%\start_erl.data") do @( 17 | @call :set_trim erts_version %%I 18 | @call :set_trim release_version %%J 19 | ) 20 | 21 | @set erl_exe="%node_root%\erts-%erts_version%\bin\erl.exe" 22 | @set boot_file="%releases_dir%\%release_version%\%node_name%" 23 | 24 | @if exist "%releases_dir%\%release_version%\sys.config" ( 25 | @set app_config="%releases_dir%\%release_version%\sys.config" 26 | ) else ( 27 | @set app_config="%node_root%\etc\app.config" 28 | ) 29 | 30 | @if exist "%releases_dir%\%release_version%\vm.args" ( 31 | @set vm_args="%releases_dir%\%release_version%\vm.args" 32 | ) else ( 33 | @set vm_args="%node_root%\etc\vm.args" 34 | ) 35 | 36 | @%erl_exe% %erl_args% -boot %boot_file% -config %app_config% -args_file %vm_args% 37 | 38 | :set_trim 39 | @set %1=%2 40 | @goto :EOF 41 | -------------------------------------------------------------------------------- /apps/web/src/web_sup.erl: -------------------------------------------------------------------------------- 1 | -module(web_sup). 2 | -behaviour(supervisor). 3 | -export([start_link/0, init/1]). 4 | -compile(export_all). 5 | -include_lib("n2o/include/wf.hrl"). 6 | -define(APP, web). 7 | 8 | start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). 9 | 10 | init([]) -> 11 | {ok, _} = cowboy:start_http(http, 100, [{port, 8001}], 12 | [{env, [{dispatch, dispatch_rules()}]}]), 13 | 14 | Pid = spawn(fun () -> wf:reg(lobby), chat_room([],0) end), 15 | wf:cache(mode,wf:config(n2o,mode,"prod")), 16 | {ok, {{one_for_one, 5, 10}, []}}. 17 | 18 | dispatch_rules() -> 19 | cowboy_router:compile( 20 | [{'_', [ 21 | {"/static/[...]", cowboy_static, 22 | {priv_dir, ?APP, <<"static">>,[{mimetypes,cow_mimetypes,all}]}}, 23 | {"/ws/[...]", bullet_handler, [{handler, n2o_bullet}]}, 24 | {'_', n2o_cowboy, []} 25 | ]}]). 26 | 27 | chat_room(List,Counter) -> 28 | receive 29 | {counter,Caller} -> Caller ! {counter,Counter}, chat_room(List,Counter); 30 | {inc} -> chat_room(List,Counter+1); 31 | {dec} -> chat_room(List,Counter-1); 32 | {add, Message} -> chat_room([Message|List],Counter); 33 | print -> io:format("~p",[List]), chat_room(List,Counter); 34 | {top, Number, Caller} -> Caller ! lists:sublist(List,Number), chat_room(List,Counter); 35 | {win, Page, Caller} -> Caller ! lists:sublist(List,Page*10,10), chat_room(List,Counter); 36 | _ -> chat_room(List,Counter) end. 37 | 38 | -------------------------------------------------------------------------------- /apps/web/priv/static/appstore/navs.less: -------------------------------------------------------------------------------- 1 | .sky-nav{ 2 | font-size: 24px; 3 | font-weight:bold; 4 | 5 | & li { 6 | &.active, &.active:hover, &.active:focus{ 7 | & a, a:focus { 8 | color: @linkColorHover; 9 | 10 | &:hover{ 11 | color:@linkColorHover; 12 | } 13 | background-color: transparent; 14 | border:3px solid white; 15 | border-top: 3px solid @linkColorHover; 16 | } 17 | } 18 | 19 | & a { 20 | color: #555555; 21 | text-transform: uppercase; 22 | border: 3px solid transparent; 23 | background-color: transparent; 24 | text-shadow: none; 25 | 26 | &:hover, &:focus{ 27 | color: @linkColorHover; 28 | background-color: transparent; 29 | border: 3px solid transparent; 30 | border-top: 3px solid @linkColorHover; 31 | } 32 | } 33 | } 34 | } 35 | 36 | /* support colors? 37 | .muted { color: @grayLight; } 38 | a.muted:hover, 39 | a.muted:focus { color: darken(@grayLight, 10%); } 40 | 41 | .text-warning { color: @warningText; } 42 | a.text-warning:hover, 43 | a.text-warning:focus { color: darken(@warningText, 10%); } 44 | 45 | .text-error { color: @errorText; } 46 | a.text-error:hover, 47 | a.text-error:focus { color: darken(@errorText, 10%); } 48 | 49 | .text-info { color: @infoText; } 50 | a.text-info:hover, 51 | a.text-info:focus { color: darken(@infoText, 10%); } 52 | 53 | .text-success { color: @successText; } 54 | a.text-success:hover, 55 | a.text-success:focus { color: darken(@successText, 10%); } 56 | */ -------------------------------------------------------------------------------- /apps/web/priv/templates/tl.dtl: -------------------------------------------------------------------------------- 1 | 2 | 33 | -------------------------------------------------------------------------------- /apps/web/src/element_carousel.erl: -------------------------------------------------------------------------------- 1 | -module(element_carousel). 2 | -compile(export_all). 3 | -include_lib("web/include/wf.hrl"). 4 | 5 | render_element(R = #carousel{})-> 6 | Id = if R#carousel.id == undefined -> wf:temp_id(); true -> R#carousel.id end, 7 | Interval = case R#carousel.interval of false -> <<"false">>; I -> list_to_binary(integer_to_list(I)) end, 8 | case length(R#carousel.items) of 9 | 0 -> []; 10 | ItemsCount -> {List, Items} = lists:unzip([ 11 | begin 12 | Index = list_to_binary(integer_to_list(I)), 13 | { #li{data_fields=[{<<"data-target">>, list_to_binary("#"++Id)}, {<<"data-slide-to">>, Index}]}, 14 | #panel{ class=if I==0 -> [item,active]; true -> [item] end, 15 | data_fields=[{<<"data-slide-number">>, Index}], body=E} } 16 | end || {I, E} <- lists:zip(lists:seq(0,ItemsCount-1), R#carousel.items)]), 17 | 18 | C = #panel{id = Id, show_if=ItemsCount > 0, class=[carousel, slide | R#carousel.class], style=[R#carousel.style], 19 | data_fields=[{<<"data-interval">>, Interval}, {<<"data-pause">>, R#carousel.pause}], body=[ 20 | #list{show_if=R#carousel.indicators == true, numbered=true, class=["carousel-indicators"], body=List}, 21 | #panel{class=["carousel-inner"], body=[Items]}, 22 | #link{class=["carousel-control", left], url="#"++Id, data_fields=[{<<"data-slide">>, <<"prev">>}], body="‹"}, 23 | #link{class=["carousel-control", right], url="#"++Id, data_fields=[{<<"data-slide">>, <<"next">>}], body="›"}, 24 | #panel{class=["carousel-caption"], body=R#carousel.caption} ]}, 25 | element_panel:render_element(C) 26 | end. 27 | -------------------------------------------------------------------------------- /apps/web/priv/templates/dev.dtl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{title}} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {{body}} 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /rels/web/files/install_upgrade.escript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %%! -noshell -noinput 3 | %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- 4 | %% ex: ft=erlang ts=4 sw=4 et 5 | 6 | -define(TIMEOUT, 60000). 7 | -define(INFO(Fmt,Args), io:format(Fmt,Args)). 8 | 9 | main([NodeName, Cookie, ReleasePackage]) -> 10 | TargetNode = start_distribution(NodeName, Cookie), 11 | {ok, Vsn} = rpc:call(TargetNode, release_handler, unpack_release, 12 | [ReleasePackage], ?TIMEOUT), 13 | ?INFO("Unpacked Release ~p~n", [Vsn]), 14 | {ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler, 15 | check_install_release, [Vsn], ?TIMEOUT), 16 | {ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler, 17 | install_release, [Vsn], ?TIMEOUT), 18 | ?INFO("Installed Release ~p~n", [Vsn]), 19 | ok = rpc:call(TargetNode, release_handler, make_permanent, [Vsn], ?TIMEOUT), 20 | ?INFO("Made Release ~p Permanent~n", [Vsn]); 21 | main(_) -> 22 | init:stop(1). 23 | 24 | start_distribution(NodeName, Cookie) -> 25 | MyNode = make_script_node(NodeName), 26 | {ok, _Pid} = net_kernel:start([MyNode, shortnames]), 27 | erlang:set_cookie(node(), list_to_atom(Cookie)), 28 | TargetNode = make_target_node(NodeName), 29 | case {net_kernel:hidden_connect_node(TargetNode), 30 | net_adm:ping(TargetNode)} of 31 | {true, pong} -> 32 | ok; 33 | {_, pang} -> 34 | io:format("Node ~p not responding to pings.\n", [TargetNode]), 35 | init:stop(1) 36 | end, 37 | TargetNode. 38 | 39 | make_target_node(Node) -> 40 | [_, Host] = string:tokens(atom_to_list(node()), "@"), 41 | list_to_atom(lists:concat([Node, "@", Host])). 42 | 43 | make_script_node(Node) -> 44 | list_to_atom(lists:concat([Node, "_upgrader_", os:getpid()])). 45 | -------------------------------------------------------------------------------- /apps/web/src/reviews.erl: -------------------------------------------------------------------------------- 1 | -module(reviews). 2 | -compile(export_all). 3 | -include_lib("web/include/wf.hrl"). 4 | -include_lib("kvs/include/user.hrl"). 5 | -include_lib("kvs/include/feed.hrl"). 6 | -include_lib("nitro/include/nitro.hrl"). 7 | 8 | main() -> [#dtl{file = wf:cache(mode), ext="dtl", bindings=[{title,<<"Reviews">>},{body,body()}]}]. 9 | 10 | body() -> index:header() ++ [ 11 | #section{id=content, body= 12 | #panel{class=[container], body= 13 | #panel{class=[row, dashboard], body=[ 14 | #panel{class=[span3], body=dashboard:sidebar_menu(reviews)}, 15 | #panel{class=[span9], body=dashboard:section(feed(wf:user()), "icon-list")}]}}} 16 | ] ++ index:footer(). 17 | 18 | feed(User) -> [ 19 | #h3{body= <<"your reviews">>}, 20 | #panel{class=["btn-toolbar"], body=[#link{class=[btn, "btn-large", "btn-success"], body= <<"Create New Box">>}]}, 21 | 22 | #table{class=[table, "table-hover", containers], 23 | header=[ #tr{cells=[ 24 | #th{body= <<"ID">>}, 25 | #th{body= <<"Host">>}, 26 | #th{body= <<"Pass">>}, 27 | #th{body= <<"Region">>}, 28 | #th{body= <<"SSH">>}, 29 | #th{body= <<"action">>}]} ], 30 | body=[[ 31 | #tr{class=[success], cells=[ 32 | #td{body= <<"1d3e102378f9">>}, 33 | #td{body= <<"sncn1">>}, 34 | #td{body= <<"pass">>}, 35 | #td{body= <<"do1.synrc.com">>}, 36 | #td{body= <<"49153">>}, 37 | #td{body=#link{class=[btn],body= <<"Stop">>}} ]}, 38 | #tr{class=[error], cells=[ 39 | #td{body= <<"0515e2b20bac">>}, 40 | #td{body= <<"sncn2">>}, 41 | #td{body= <<"pass">>}, 42 | #td{body= <<"do1.synrc.com">>}, 43 | #td{body= <<"49154">>}, 44 | #td{body=#link{class=[btn], body= <<"Start">>}} ]} ]]} 45 | ]. 46 | 47 | event(init) -> []; 48 | event({counter,C}) -> wf:update(onlinenumber,wf:to_list(C)). 49 | api_event(Name,Tag,Term) -> error_logger:info_msg("Name ~p, Tag ~p, Term ~p",[Name,Tag,Term]), ok. 50 | -------------------------------------------------------------------------------- /rels/web/reltool.config: -------------------------------------------------------------------------------- 1 | {sys, [ 2 | {lib_dirs, ["../../apps","../../deps"]}, 3 | {erts, [{mod_cond, derived}, {app_file, strip}]}, 4 | {app_file, strip}, 5 | {rel, "node", "1", 6 | [ 7 | kernel, 8 | stdlib, 9 | sasl, 10 | avz, 11 | erts, 12 | mnesia, 13 | compiler, 14 | % bitcask, 15 | % eleveldb, 16 | % riak_api, 17 | % riak_control, 18 | mimetypes, 19 | n2o, 20 | % folsom, 21 | gproc, 22 | ssl, 23 | kai, 24 | kvs, 25 | ranch, 26 | % active, 27 | % erlfsmon, 28 | % rebar, 29 | sync, 30 | erlydtl, 31 | cowboy, 32 | crypto, 33 | lager, 34 | oauth, 35 | xmerl, 36 | web 37 | ]}, 38 | {rel, "start_clean", "", 39 | [ 40 | kernel, 41 | stdlib 42 | ]}, 43 | {boot_rel, "node"}, 44 | {profile, embedded}, 45 | {incl_cond, derived}, 46 | {mod_cond, derived}, 47 | {excl_archive_filters, [".*"]}, %% Do not archive built libs 48 | {excl_sys_filters, ["^bin/.*", "^erts.*/bin/(dialyzer|typer)", 49 | "^erts.*/(doc|info|include|lib|man|src)"]}, 50 | {excl_app_filters, ["\.gitignore"]}, 51 | {app, appmon, [{mod_cond, app}, {incl_cond, exclude}]}, 52 | {app, web, [{mod_cond, app}, {incl_cond, include}]} 53 | ]}. 54 | 55 | {target_dir, "node"}. 56 | 57 | {overlay, [ 58 | {mkdir, "log/sasl"}, 59 | {copy, "files/erl", "\{\{erts_vsn\}\}/bin/erl"}, 60 | {copy, "files/nodetool", "\{\{erts_vsn\}\}/bin/nodetool"}, 61 | {copy, "files/node", "bin/node"}, 62 | {copy, "files/node.cmd", "bin/node.cmd"}, 63 | {copy, "files/start_erl.cmd", "bin/start_erl.cmd"}, 64 | {copy, "files/install_upgrade.escript", "bin/install_upgrade.escript"}, 65 | {copy, "files/sys.config", "releases/\{\{rel_vsn\}\}/sys.config"}, 66 | {copy, "files/vm.args", "releases/\{\{rel_vsn\}\}/vm.args"} 67 | ]}. 68 | -------------------------------------------------------------------------------- /apps/web/include/wf.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(N2O_BOOTSTRAP_HRL). 2 | -define(N2O_BOOTSTRAP_HRL, true). 3 | 4 | %%-include("../../../deps/n2o/include/wf.hrl"). 5 | -include("../../../deps/nitro/include/nitro.hrl"). 6 | 7 | % emulate msg ! socket through wire 8 | -define(WS_SEND(Id,Ev,Detail), wf:wire(wf:f("document.getElementById('~s').dispatchEvent(" 9 | "new CustomEvent('~s', {'detail': ~s}));", [Id,wf:to_list(Ev),wf:json([Detail])]))). 10 | 11 | % REST macros 12 | -define(rest(), is_rest() -> true). 13 | -define(unmap(Record), unmap(P,R) -> wf_utils:hunmap(P,R,record_info(fields, Record),size(R)-1)). 14 | -define(map(Record), map(O) -> 15 | Y = [ try N=lists:nth(1,B), if is_number(N) -> wf:to_binary(B); true -> B end catch _:_ -> B end 16 | || B <- tl(tuple_to_list(O)) ], 17 | lists:zip(record_info(fields, Record), Y)). 18 | 19 | 20 | % Twitter Bootstrap Elements 21 | -record(carousel, {?ELEMENT_BASE(element_carousel), interval=5000, pause= <<"hover">>, start=0, indicators=true, items=[], caption=[]}). 22 | -record(accordion, {?ELEMENT_BASE(element_accordion), items=[], nav_stacked=false}). 23 | -record(slider, {?ELEMENT_BASE(element_slider), min, max, step, orientation, value, selection, tooltip, handle, formater}). 24 | 25 | % Synrc Elements 26 | %%-record(rtable, {?ELEMENT_BASE(element_rtable), rows=[], postback}). 27 | -record(rtable, {?ELEMENT_BASE(element_rtable), rows=[]}). 28 | -record(upload_state, {cid, root=code:priv_dir(n2o), dir="", name, 29 | type, room=upload, data= <<>>, preview=false, size=[{200,200}], index=0, block_size=1048576}). 30 | %%-record(upload, {?CTRL_BASE(element_upload), name, value, state=#upload_state{}, root, dir, delegate_query, delegate_api, post_write, img_tool, post_target, size, preview}). 31 | %%-record(textboxlist, {?ELEMENT_BASE(element_textboxlist), placeholder="", postback, unique=true, values=[], autocomplete=true, queryRemote=true, onlyFromValues=true, minLenght=1}). 32 | -record(textboxlist, {?ELEMENT_BASE(element_textboxlist), placeholder="", unique=true, values=[], autocomplete=true, queryRemote=true, onlyFromValues=true, minLenght=1}). 33 | %%-record(htmlbox, {?CTRL_BASE(element_htmlbox), html, state=#upload_state{}, root, dir, delegate_query, delegate_api, post_write, img_tool, post_target, size}). 34 | -record(htmlbox, {?CTRL_BASE(element_htmlbox), html, state=#upload_state{}, root, delegate_query, delegate_api, post_write, img_tool, post_target, size}). 35 | -endif. 36 | -------------------------------------------------------------------------------- /apps/web/priv/static/appstore/variables.less: -------------------------------------------------------------------------------- 1 | // Bootstrap Core variables 2 | @import "../bootstrap/less/variables.less"; 3 | 4 | 5 | @azure: #6bc7e1; 6 | @darkAzure: #41b7d8; 7 | 8 | // Links 9 | // ------------------------- 10 | @linkColor: @azure; 11 | @linkColorHover: @darkAzure; 12 | 13 | // Typography 14 | // ------------------------- 15 | @import url(http://fonts.googleapis.com/css?family=Open+Sans:400italic,400,800); 16 | 17 | @font-face { 18 | font-family: 'Nokia Pure Text'; 19 | src: url("http://synrc.com/fonts/nokiapuretextlight-webfont.eot?v=3"); 20 | src: url("http://synrc.com/fonts/nokiapuretextlight-webfont.eot?v=3") format("eot"), 21 | url("http://synrc.com/fonts/nokiapuretextlight-webfont.woff?v=3") format("truetype"), 22 | url("http://synrc.com/fonts/nokiapuretextlight-webfont.ttf?v=3") format("truetype"); 23 | font-weight: 200; 24 | font-style: normal; 25 | } 26 | 27 | @nokiaFontFamily: "Nokia Pure Text", Arial, serif; 28 | @openSansFontFamily: "Open Sans", Arial, serif; 29 | 30 | @baseFontSize: 20px; 31 | @baseFontFamily: @nokiaFontFamily; 32 | @baseLineHeight: 24px; 33 | @altFontFamily: @openSansFontFamily; 34 | 35 | @headingsFontFamily: @openSansFontFamily; // empty to use BS default, @baseFontFamily 36 | @headingsFontWeight: 800; // instead of browser default, bold 37 | @headingsColor: inherit; // empty to use BS default, @textColor 38 | 39 | // Component sizing 40 | // ------------------------- 41 | @fontSizeLarge: @baseFontSize * 1.25; 42 | @fontSizeSmall: @baseFontSize * 0.85; 43 | @fontSizeMini: @baseFontSize * 0.75; 44 | 45 | @baseBorderRadius: 0; 46 | @borderRadiusLarge: 0; 47 | @borderRadiusSmall: 0; 48 | 49 | // Navbar 50 | // ------------------------- 51 | @navbarCollapseWidth: 979px;//? 52 | @navbarCollapseDesktopWidth: @navbarCollapseWidth + 1; 53 | 54 | @navbarHeight: 40px;// ? 55 | @navbarLinkColorHover: @darkAzure; 56 | 57 | // Products 58 | // ------------------------- 59 | @productHeight: 160px; 60 | @prodListBackground: @grayLighter; 61 | @prodBackground: @white; 62 | @reviewBackground: @white; 63 | 64 | // Buttons 65 | // ------------------------- 66 | @btnGooglePlusBackground: #cc3732; 67 | @btnGooglePlusBackgroundHighlight: #e74b37; 68 | -------------------------------------------------------------------------------- /otp.mk: -------------------------------------------------------------------------------- 1 | VM := rels/web/files/vm.args 2 | SYS := rels/web/files/sys.config 3 | PLT_NAME := ~/.n2o_dialyzer.plt 4 | ERL_ARGS := -args_file $(VM) -config $(SYS) 5 | RUN_DIR ?= rels/web/devbox 6 | LOG_DIR ?= rels/web/devbox/logs 7 | empty := 8 | ROOTS := apps deps 9 | space := $(empty) $(empty) 10 | comma := $(empty),$(empty) 11 | VSN := $(shell git rev-parse HEAD | head -c 6) 12 | DATE := $(shell date "+%Y%m%d-%H%M%S") 13 | ERL_LIBS := $(subst $(space),:,$(ROOTS)) 14 | relx := "{release,{$(RELEASE),\"$(VER)\"},[$(RELEASE)]}.\\n{include_erts,true}.\ 15 | \\n{extended_start_script,true}.\\n{generate_start_script,true}.\\n{sys_config,\"$(SYS)\"}.\ 16 | \\n{vm_args,\"$(VM)\"}.\\n{overlay,[{mkdir,\"log/sasl\"}]}." 17 | 18 | test: eunit ct 19 | compile: get-deps static-link 20 | clean: 21 | rm -f .applist 22 | rebar $@ 23 | delete-deps get-deps compile update-deps: 24 | rebar $@ 25 | .applist: 26 | $(eval APPS := $(subst deps/,,$(subst apps/,,$(shell find apps deps -maxdepth 1 -mindepth 1 -type d)))) 27 | ./orderapps.erl $(APPS) > $@ 28 | $(RUN_DIR) $(LOG_DIR): 29 | mkdir -p $(RUN_DIR) & mkdir -p $(LOG_DIR) 30 | console: .applist 31 | ERL_LIBS=$(ERL_LIBS) erl $(ERL_ARGS) -eval '[application:start(A) || A <- $(shell cat .applist)]' 32 | start: $(RUN_DIR) $(LOG_DIR) .applist 33 | RUN_ERL_LOG_GENERATIONS=1000 RUN_ERL_LOG_MAXSIZE=20000000 \ 34 | ERL_LIBS=$(ERL_LIBS) run_erl -daemon $(RUN_DIR)/ $(LOG_DIR)/ "exec $(MAKE) console" 35 | attach: 36 | to_erl $(RUN_DIR)/ 37 | release: 38 | echo $(relx) > relx.config && relx 39 | stop: 40 | @kill -9 $(shell ps ax -o pid= -o command=|grep $(RELEASE)|grep $(COOKIE)|awk '{print $$1}') 41 | $(PLT_NAME): 42 | $(eval APPS := $(subst deps/,,$(subst apps/,,$(shell find apps deps -maxdepth 1 -mindepth 1 -type d)))) 43 | ERL_LIBS=$(ERL_LIBS) dialyzer --build_plt --output_plt $(PLT_NAME) --apps $(APPS) || true 44 | dialyze: $(PLT_NAME) compile 45 | $(eval APPS := $(shell find apps deps -maxdepth 1 -mindepth 1 -type d)) 46 | @$(foreach var,$(APPS),(echo "Process $(var)"; dialyzer -q $(var)/ebin --plt $(PLT_NAME) --no_native -Werror_handling -Wunderspecs -Wrace_conditions -Wno_undefined_callbacks);) 47 | tar: release 48 | tar zcvf $(RELEASE)-$(VSN)-$(DATE).tar.gz _rel/lib/*/ebin _rel/lib/*/priv _rel/bin _rel/releases 49 | eunit: 50 | rebar eunit skip_deps=true 51 | ct: 52 | rebar ct skip_deps=true verbose=1 53 | 54 | .PHONY: delete-deps get-deps compile clean console start attach release update-deps dialyze ct eunit tar 55 | -------------------------------------------------------------------------------- /apps/web/priv/static/img/google.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 26 | 27 | -------------------------------------------------------------------------------- /apps/web/src/index.erl: -------------------------------------------------------------------------------- 1 | -module(index). 2 | -compile(export_all). 3 | -include_lib("n2o/include/wf.hrl"). 4 | -include_lib("kvs/include/user.hrl"). 5 | -include_lib("nitro/include/nitro.hrl"). 6 | 7 | 8 | log_modules() -> [index,login,chat]. 9 | 10 | main() -> [ #dtl{file = wf:cache(mode), ext="dtl", bindings=[{title,<<"Index">>},{body,body()}]} ]. 11 | 12 | body() -> 13 | index:header() 14 | ++ [#panel{style="font-size:38pt;height:200px;text-align: center;margin-top:200px;", 15 | body="Hello, N2O!"}] 16 | ++ index:footer(). 17 | 18 | account() -> 19 | #li{body=[ 20 | case wf:user() of 21 | undefined -> #link{id=login1, body= <<"Login">>, postback=to_login, delegate=login}; 22 | User -> #link{class=["dropdown-toggle", "avatar"], url="/account", body=[ 23 | case User#user.images of 24 | undefined -> ""; 25 | Img -> #image{class=["img-circle", "img-polaroid"], 26 | image=iolist_to_binary([Img,"?sz=50&width=50&height=50&s=50"]), 27 | width= <<"50px">>, height= <<"50px">>} end, 28 | case User#user.display_name of undefined -> []; N -> N end]} end, 29 | #button{id="style-switcher", class=[btn, "btn-inverse", "dropdown-toggle", "account-link"], 30 | data_fields=[{<<"data-toggle">>, <<"dropdown">>}], body=#i{class=["icon-cog"]}}, 31 | #list{class=["dropdown-menu"], body=[ 32 | #li{body=#link{body=[#i{class=["icon-cog"]}, <<" Preferences">>]}}, 33 | #li{body=#link{postback=chat,body=[#i{class=["icon-cog"]}, <<" Notifications">>]}}, 34 | case wf:user() of 35 | undefined -> #li{body=#link{id=loginbtn, postback=to_login, delegate=login, 36 | body=[#i{class=["icon-off"]}, <<" Login">> ]}}; 37 | _A -> #li{body=#link{id=logoutbtn, postback=logout, delegate=login, 38 | body=[#i{class=["icon-off"]}, <<" Logout">> ] }} end ]} ]}. 39 | 40 | 41 | header() -> Online = ets:lookup(globals,onlineusers), 42 | C = case Online of 43 | [{_,I}] -> I; 44 | _ -> 0 45 | end, 46 | [ #dtl{file = "hd", ext="dtl", bindings=[{account,account()},{online,wf:to_list(C)}]} ]. 47 | footer() -> [ #dtl{file = "tl", ext="dtl", bindings=[]} ]. 48 | 49 | api_event(Name,Tag,Term) -> error_logger:info_msg("Index Name ~p, Tag ~p, Term ~p",[Name,Tag,Term]), event(change_me). 50 | event({counter,C}) -> wf:update(onlinenumber,wf:to_list(C)); 51 | event(Event) -> 52 | wf:info(?MODULE,"Context: ~p",[wf_context:context()]), 53 | error_logger:info_msg("Event: ~p", [Event]). 54 | -------------------------------------------------------------------------------- /rels/web/files/node.cmd: -------------------------------------------------------------------------------- 1 | @setlocal 2 | 3 | @set node_name=node 4 | 5 | @rem Get the absolute path to the parent directory, 6 | @rem which is assumed to be the node root. 7 | @for /F "delims=" %%I in ("%~dp0..") do @set node_root=%%~fI 8 | 9 | @set releases_dir=%node_root%\releases 10 | 11 | @rem Parse ERTS version and release version from start_erl.data 12 | @for /F "usebackq tokens=1,2" %%I in ("%releases_dir%\start_erl.data") do @( 13 | @call :set_trim erts_version %%I 14 | @call :set_trim release_version %%J 15 | ) 16 | 17 | @set vm_args=%releases_dir%\%release_version%\vm.args 18 | @set sys_config=%releases_dir%\%release_version%\sys.config 19 | @set node_boot_script=%releases_dir%\%release_version%\%node_name% 20 | @set clean_boot_script=%releases_dir%\%release_version%\start_clean 21 | 22 | @rem extract erlang cookie from vm.args 23 | @for /f "usebackq tokens=1-2" %%I in (`findstr /b \-setcookie "%vm_args%"`) do @set erlang_cookie=%%J 24 | 25 | @set erts_bin=%node_root%\erts-%erts_version%\bin 26 | 27 | @set service_name=%node_name%_%release_version% 28 | 29 | @set erlsrv="%erts_bin%\erlsrv.exe" 30 | @set epmd="%erts_bin%\epmd.exe" 31 | @set escript="%erts_bin%\escript.exe" 32 | @set werl="%erts_bin%\werl.exe" 33 | 34 | @if "%1"=="usage" @goto usage 35 | @if "%1"=="install" @goto install 36 | @if "%1"=="uninstall" @goto uninstall 37 | @if "%1"=="start" @goto start 38 | @if "%1"=="stop" @goto stop 39 | @if "%1"=="restart" @call :stop && @goto start 40 | @if "%1"=="console" @goto console 41 | @if "%1"=="query" @goto query 42 | @if "%1"=="attach" @goto attach 43 | @if "%1"=="upgrade" @goto upgrade 44 | @echo Unknown command: "%1" 45 | 46 | :usage 47 | @echo Usage: %~n0 [install^|uninstall^|start^|stop^|restart^|console^|query^|attach^|upgrade] 48 | @goto :EOF 49 | 50 | :install 51 | @set description=Erlang node %node_name% in %node_root% 52 | @set start_erl=%node_root%\bin\start_erl.cmd 53 | @set args= ++ %node_name% ++ %node_root% 54 | @%erlsrv% add %service_name% -c "%description%" -sname %node_name% -w "%node_root%" -m "%start_erl%" -args "%args%" -stopaction "init:stop()." 55 | @goto :EOF 56 | 57 | :uninstall 58 | @%erlsrv% remove %service_name% 59 | @%epmd% -kill 60 | @goto :EOF 61 | 62 | :start 63 | @%erlsrv% start %service_name% 64 | @goto :EOF 65 | 66 | :stop 67 | @%erlsrv% stop %service_name% 68 | @goto :EOF 69 | 70 | :console 71 | @start "%node_name% console" %werl% -boot "%node_boot_script%" -config "%sys_config%" -args_file "%vm_args%" -sname %node_name% 72 | @goto :EOF 73 | 74 | :query 75 | @%erlsrv% list %service_name% 76 | @exit %ERRORLEVEL% 77 | @goto :EOF 78 | 79 | :attach 80 | @for /f "usebackq" %%I in (`hostname`) do @set hostname=%%I 81 | start "%node_name% attach" %werl% -boot "%clean_boot_script%" -remsh %node_name%@%hostname% -sname console -setcookie %erlang_cookie% 82 | @goto :EOF 83 | 84 | :upgrade 85 | @if "%2"=="" ( 86 | @echo Missing upgrade package argument 87 | @echo Usage: %~n0 upgrade {package base name} 88 | @echo NOTE {package base name} MUST NOT include the .tar.gz suffix 89 | @goto :EOF 90 | ) 91 | @%escript% %node_root%\bin\install_upgrade.escript %node_name% %erlang_cookie% %2 92 | @goto :EOF 93 | 94 | :set_trim 95 | @set %1=%2 96 | @goto :EOF 97 | -------------------------------------------------------------------------------- /apps/web/priv/static/appstore/account.less: -------------------------------------------------------------------------------- 1 | .dash-sidebar-nav{ 2 | font-size: 26px; 3 | list-style-type: none; 4 | margin: 24px 20px 0 0; 5 | padding: 0; 6 | width: 210px; 7 | 8 | @media (min-width: 1200px){width: 250px;} 9 | & li { 10 | & a{ 11 | color: #555555; 12 | margin: 0 0 10px; 13 | text-align: right; 14 | text-transform: uppercase; 15 | padding-right: 20px; 16 | background-color: transparent; 17 | border-right: 3px solid #555555; 18 | text-shadow: none; 19 | } 20 | } 21 | } 22 | 23 | .dashboard{ 24 | margin-top:40px; 25 | @media (max-width: 767px){margin-top:0;} 26 | 27 | & .dashboard-section{ 28 | margin:60px 0 0 0; 29 | &:first-child{ 30 | margin-top:0; 31 | border-bottom: 1px solid #e9e9e9; 32 | } 33 | } 34 | 35 | & .dashboard-unit{ 36 | padding: 0; 37 | z-index: 1; 38 | overflow: hidden; 39 | padding-bottom:30px; 40 | 41 | & select{width:100%;} 42 | & h3{margin-bottom:60px;} 43 | 44 | &:hover{& .dashboard-img{ 45 | border-color: rgba(153, 153, 153, 0.3); 46 | & img {.opacity(80);} 47 | }} 48 | 49 | & .dashboard-img-wrapper{ 50 | text-align:center; 51 | @media (max-width: 979px) and (min-width: 768px){width:100%;} 52 | @media (min-width: 1200px) {text-align:left;} 53 | } 54 | & .dashboard-img { 55 | display:inline-block; 56 | margin:0 0 30px; 57 | max-width: 180px; 58 | max-height: 180px; 59 | .box-shadow(0 0 10px rgba(51, 51, 51, 0.9) inset); 60 | border: 10px solid #e9e9e9; 61 | .transition(all 0.2s); 62 | .border-radius(500px); 63 | 64 | z-index: -1; 65 | & img{ 66 | .transition(all 0.2s); 67 | .border-radius(inherit); 68 | .opacity(40); 69 | } 70 | &.img-polaroid{padding:0;} 71 | } 72 | 73 | & .profile-info-wrapper{ 74 | min-width:370px; 75 | text-align:center; 76 | @media (max-width:410px){width:100%;min-width:100%;} 77 | @media (max-width: 979px) and (min-width: 768px){width:100%;} 78 | @media (min-width: 1200px) {text-align:left; margin-left:0;} 79 | } 80 | & .profile-info{ 81 | min-width:370px; 82 | @media (max-width:410px){width:100%; min-width:100%;} 83 | 84 | font-size: 30px; 85 | line-height: 1.5em; 86 | font-weight: 300; 87 | display:inline-block; 88 | text-align:left; 89 | 90 | & label { 91 | font-size: 30px; 92 | line-height: 1.5em; 93 | font-weight: 300; 94 | margin-right:10px; 95 | margin-bottom:0; 96 | 97 | @media (max-width:410px){width:100%;} 98 | } 99 | } 100 | 101 | & .payments{ 102 | font-size:20px; 103 | margin-bottom:60px; 104 | max-width: 500px; 105 | @media (max-width:768px){margin: 0 auto;} 106 | } 107 | 108 | & .containers{ 109 | margin-bottom:60px; 110 | max-width: 600px; 111 | font-size:18pt; 112 | } 113 | 114 | & .btn-charge{margin-top:10px;} 115 | 116 | & .ballance-label{ 117 | margin-bottom:20px; 118 | font-size: 20px; 119 | 120 | & b{ 121 | font-size:56px; 122 | line-height:70px; 123 | margin:0 auto; 124 | display:block; 125 | &.positive{color:green;} 126 | &.negative{color:red;} 127 | } 128 | & span { } 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /apps/web/src/chat.erl: -------------------------------------------------------------------------------- 1 | -module(chat). 2 | -compile(export_all). 3 | -include_lib("n2o/include/wf.hrl"). 4 | -include_lib("kvs/include/user.hrl"). 5 | -include_lib("nitro/include/nitro.hrl"). 6 | 7 | 8 | main() -> 9 | [ #dtl{file = wf:cache(mode), ext="dtl", bindings=[{title,<<"Login">>},{body,body()}]} ]. 10 | 11 | message(undefined,What) -> message("Anonymous",What); 12 | message(Who,What) -> 13 | error_logger:info_msg("~p",[What]), 14 | #panel{class=["media"],body=[ 15 | #link{class=["pull-left"], body=[ 16 | #image{class=["media-object"],image="static/img/infinity.png",width= <<"63">>} ]}, 17 | #panel{class=["media-body"],body=[ 18 | #h4{body= unicode:characters_to_binary(Who)}, 19 | #span{body=wf:js_escape(What)} ]} ]}. 20 | 21 | body() -> 22 | {ok,Pid} = wf:async("looper",fun() -> chat_loop() end), 23 | index:header() ++ [ 24 | #panel{class=["container-fluid", chat], body=[ 25 | #panel{class=["row-fluid"], body=[ 26 | #h1{body= <<"N2O based WebSocket Chat">>,class=[offset3, span8]} 27 | ]}, 28 | #panel{class=["row-fluid"], body=[ 29 | #panel{class=[span3], body=[ 30 | #h4{body= "Online Users:" }, 31 | #list{class=[unstyled, "chat-rooms"], body=[ 32 | #li{body=#link{body= io_lib:format("~p",[Z])}} || {X,Y,Z} <- qlc:e(gproc:table()), X=={p,l,broadcast} 33 | ]} ]}, 34 | #panel{class=[span8], body=[ 35 | #panel{id=history, class=[history], body=[ 36 | case wf:user() of undefined -> message("System","You are not logged in. Anonymous mode!"); 37 | User -> message("System", "Hello, "++ 38 | case User#user.display_name of <> -> binary_to_list(N); 39 | undefined -> "Anonymous"; 40 | L -> L end 41 | ++ "! Here you can chat, please go on!") end ]}, 42 | #textarea{id=message,style="display: inline-block; width: 200px; margin-top: 20px; margin-right: 20px;"}, 43 | #button{id=send,body="Send",class=["btn","btn-primary","btn-large","btn-inverse"],postback={chat,Pid},source=[message]} 44 | ]} 45 | ]} 46 | ]} 47 | 48 | ] ++ index:footer(). 49 | 50 | 51 | event({inc,Pid}) -> 52 | {Headers,Req} = wf:headers(?REQ), 53 | Host = lists:keyfind(<<"host">>,1,Headers), 54 | wf:info(?MODULE,"Headers: ~p",[Headers]), 55 | wf:info(?MODULE,"Host: ~p",[Host]), 56 | wf:info("Inc"), 57 | Pid ! inc; 58 | 59 | event(init) -> 60 | Self = self(), 61 | wf:reg(room), 62 | wf:send(lobby,{top,5,Self}), 63 | Terms = wf:render(receive Top -> [ message(U,M) || {U,M} <- Top] end), 64 | wf:insert_top(<<"history">>, #panel{body=[Terms]}), 65 | wf:wire("$('#history').scrollTop = $('#history').scrollHeight;"); 66 | 67 | event({chat,Pid}) -> 68 | Username = case wf:user() of undefined -> "anonymous"; A -> A#user.id end, 69 | Message = wf:q(message), 70 | Terms = [ message("Systen","Message added"), #button{postback=hello} ], 71 | wf:wire("$('#message').focus(); $('#message').select(); "), 72 | Pid ! {message, Username, Message}. 73 | 74 | chat_loop() -> 75 | receive 76 | {message, Username, Message} -> 77 | error_logger:info_msg("Comet received : ~p",[{Username,Message}]), 78 | Terms = message(Username,Message), 79 | wf:insert_top(<<"history">>, Terms), 80 | wf:send(lobby,{add,{Username,Message}}), 81 | wf:flush(room); 82 | Unknown -> error_logger:info_msg("Unknown Looper Message ~p in Pid: ~p",[Unknown,self()]) 83 | end, chat_loop(). 84 | 85 | -------------------------------------------------------------------------------- /apps/web/src/account.erl: -------------------------------------------------------------------------------- 1 | -module(account). 2 | -compile(export_all). 3 | -include_lib("n2o/include/wf.hrl"). 4 | -include_lib("kvs/include/user.hrl"). 5 | -include_lib("nitro/include/nitro.hrl"). 6 | 7 | 8 | main() -> 9 | case wf:user() of undefined -> wf:redirect("/login"); _ -> [#dtl{file = wf:cache(mode), ext="dtl",bindings=[{title,<<"Account">>},{body,body()}]}] end. 10 | 11 | body() -> index:header() ++ [ 12 | #section{id=content, body= 13 | #panel{class=[container], body= 14 | #panel{class=[row, dashboard], body=[ 15 | #panel{class=[span3], body=dashboard:sidebar_menu(account)}, 16 | #panel{class=[span9], body=[ 17 | dashboard:section(profile_info(wf:user()), "icon-user"), 18 | dashboard:section(ballance(wf:user()), "icon-ok-sign"), 19 | dashboard:section(payments(wf:user()), "icon-list") ]} ]} } } 20 | ] ++ index:footer(). 21 | 22 | profile_info(U) -> 23 | {{Y, M, D}, _} = calendar:now_to_datetime(U#user.register_date), 24 | RegDate = io_lib:format("~p ~s ~p", [D, element(M, {"Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec"}), Y]), 25 | Mailto = if U#user.email==undefined -> []; true-> iolist_to_binary(["mailto:", U#user.email]) end, 26 | [ 27 | #h3{class=["text-left"], body= <<"Profile">>}, 28 | #panel{class=["row-fluid"], body=[ 29 | #panel{class=[span4, "dashboard-img-wrapper"], body= 30 | #panel{class=["dashboard-img"], body= 31 | #image{class=[], alt="", 32 | image = case U#user.images of undefined -> undefined; 33 | Avatar -> re:replace(Avatar, <<"_normal">>, <<"">>, [{return, list}]) ++ 34 | "?sz=180&width=180&height=180&s=180" end, 35 | width= <<"180px">>, height= <<"180px">> }} }, 36 | #panel{class=[span8, "profile-info-wrapper"], body= 37 | #panel{class=["form-inline", "profile-info"], body=[ 38 | #panel{body=[#label{body= <<"Name:">>}, #b{body= U#user.display_name}]}, 39 | #panel{show_if=U#user.email=/=undefined, body=[#label{body= <<"Mail:">>}, #link{url= Mailto, body=#strong{body= U#user.email}}]}, 40 | #panel{body=[#label{body= <<"Member since ">>}, #strong{body= RegDate}]}, 41 | #b{class=["text-success"], body= <<"Active">>} ]}}]}]. 42 | 43 | ballance(User) -> [ 44 | #h3{body= <<"Balance">>}, 45 | #panel{class=["row-fluid"], body=[ 46 | #panel{class=[span4, "text-center", "ballance-label"], body=[ 47 | #b{class=[positive], body= <<"+30.0">>}, 48 | #span{body= <<"Credit">>}]}, 49 | #panel{class=[span3, "text-center", "ballance-label"], body=[ 50 | #b{class=[negative], body= <<"-0.82">>}, 51 | #span{body= <<"Debit">>}]}, 52 | #panel{class=[span4, "text-center"], body=[#link{class=[btn, "btn-large", "btn-success", "btn-charge"], body= <<"Charge">>}]}, 53 | #panel{class=[span1]}]}]. 54 | 55 | payments(User) -> [ 56 | #h3{body= <<"Payments">>}, 57 | #table{class=[table, "table-hover", payments], body=[[ 58 | #tr{cells= [ 59 | #td{body= <<"27 Jun 2013">>}, 60 | #td{body= <<"Charge">>}, 61 | #td{body= <<"-$0.82">>}, 62 | #td{body=#link{body= <<"sncn1">>}} ]}, 63 | #tr{cells= [ 64 | #td{body= <<"26 Jun 2013">>}, 65 | #td{body= <<"Payment">>}, 66 | #td{body= <<"$30">>}, 67 | #td{body= <<"">>} ]} ]]} ]. 68 | 69 | api_event(Name,Tag,Term) -> error_logger:info_msg("dashboard Name ~p, Tag ~p, Term ~p",[Name,Tag,Term]). 70 | event(init) -> []; 71 | event({counter,C}) -> wf:update(onlinenumber,wf:to_list(C)); 72 | event(U) -> wf:info("Unknown Event: ~p",[U]). 73 | -------------------------------------------------------------------------------- /apps/web/priv/static/appstore/common.less: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top:0px; 3 | @media(min-width: 980px){padding-top:80px;} 4 | } 5 | @media(min-width: 980px){ .sky-navbar{ &.navbar .nav > li > a {padding:29px 10px 31px; &.avatar{padding: 10px 10px 10px;} } } } 6 | @media(max-width: 979px){ .sky-navbar { &.navbar .nav > li > a { &.avatar{padding-top:5px;} } } } 7 | .sky-navbar{ 8 | & .brand { 9 | font-weight: 800; 10 | font-size: 30px; 11 | line-height: 30px; 12 | padding: 23px 20px 27px; 13 | margin-top: 0; 14 | margin-bottom: 0; 15 | color: #555555; 16 | text-shadow: 0 1px 0 white; 17 | 18 | & a{color: #555555;} 19 | } 20 | 21 | &.navbar-inverse{ 22 | & .brand{& a { color: inherit;}} 23 | & .nav > li > a {padding: 23px 20px 27px;} 24 | } 25 | 26 | & .navbar-inner { 27 | background: @white; 28 | background: rgba(255, 255, 255, 0.95); 29 | //.box-shadow(none); 30 | border-bottom: 1px solid #e9e9e9; 31 | } 32 | 33 | & .btn-navbar {margin-top:15px;} 34 | 35 | & .account-link { 36 | position: absolute; 37 | top: 50%; 38 | right: -35px; 39 | margin: -17px 0 0; 40 | z-index: 10000; 41 | font-size: 13px; 42 | .border-radius(50%); 43 | width: 2.5em; 44 | height: 2.5em; 45 | padding: 0; 46 | } 47 | 48 | & .avatar{ 49 | max-height:79px; 50 | & img { margin:0 5px;} 51 | } 52 | } 53 | 54 | .n2o-modal-body { 55 | position: relative; 56 | padding: 15px; 57 | overflow-y: auto; 58 | } 59 | 60 | .tag(@size, @color){ 61 | @fontSize: @baseFontSize * @size; 62 | font-size: @fontSize; 63 | line-height: @fontSize + 4; 64 | color: @color; 65 | 66 | a { 67 | color:@color; 68 | 69 | &:hover{ 70 | color: @linkColorHover; 71 | } 72 | } 73 | } 74 | 75 | .tag-mini {.tag(1, @grayLight);} 76 | .tag-small{.tag(1.2, @grayLight);} 77 | .tag-large{.tag(2.3, @grayLight);} 78 | .tag-huge {.tag(3.7, @grayLight);} 79 | 80 | .label, 81 | .badge { 82 | &-transparent, &-transperent[href]{ 83 | background-color: transparent; 84 | color: currentColor; 85 | top:0 !important; 86 | text-shadow:none; 87 | } 88 | } 89 | 90 | #mainfooter{ 91 | background: #333333; 92 | color: #e9e9e9; 93 | text-shadow: 1px 1px 1px #222222; 94 | .box-shadow(0 3px 3px rgba(0,0,0,.25)); 95 | text-shadow:1px 1px 1px #222222; 96 | padding: 0px; 97 | } 98 | 99 | .footer-banner{ 100 | background-color: @darkAzure; 101 | color: @white; 102 | text-shadow:0 0 0; 103 | & a{color:@white;&:hover{color:@white;}} 104 | } 105 | 106 | .sky-footer{ 107 | padding: 0; 108 | margin: 0; 109 | position: relative; 110 | 111 | & .row-fluid [class*="span"] { 112 | padding:30px; 113 | } 114 | 115 | & small { 116 | color: #e9e9e9; 117 | font-style: italic; 118 | text-indent:0; 119 | display:block; 120 | } 121 | & .icons { 122 | text-indent: -1em; 123 | margin-left: 2em; 124 | list-style-type: none; 125 | } 126 | 127 | & h3 {text-transform: uppercase} 128 | } 129 | 130 | .chat { 131 | min-height:600px; 132 | } 133 | 134 | .chat-rooms { 135 | background-color: #f5f5f5; 136 | & li { 137 | border-top: 1px dashed #454; 138 | padding:10px 20px 10px 20px; 139 | font-weight: bold; 140 | & a { color: #34495e;} 141 | } 142 | } 143 | 144 | .history{ 145 | background-color: #f5f5f5; 146 | 147 | & .media { 148 | border-top: 1px dashed #454; 149 | margin-top: 0px; 150 | padding:10px; 151 | 152 | & .media-object {background-color: darkblue;} 153 | } 154 | 155 | } 156 | 157 | #n2ostatus{ 158 | word-break: break-word; 159 | } -------------------------------------------------------------------------------- /apps/web/src/products.erl: -------------------------------------------------------------------------------- 1 | -module(products). 2 | -compile(export_all). 3 | -include_lib("web/include/wf.hrl"). 4 | -include_lib("kvs/include/user.hrl"). 5 | -include_lib("kvs/include/product.hrl"). 6 | -include("records.hrl"). 7 | 8 | -define(PAGE_SIZE, case wf:session(page_size) of list -> 6; _ -> 8 end). 9 | 10 | main()-> 11 | wf:session(products, [product:product(I, 2) || I <- lists:seq(1,20)] ), 12 | wf:session(page_size, list), 13 | #dtl{file=wf:cache(mode), ext="dtl",bindings=[{title,<<"products">>},{body, body()}]}. 14 | 15 | body()-> 16 | % wf:wire("$('#listsw').on('click', function(){ $('#products').removeClass('items-grid').addClass('items-list').children('li').addClass('span3').removeClass('span4'); });"), 17 | % wf:wire("$('#gridsw').on('click', function(){ $('#products').removeClass('items-list').addClass('items-grid').children('li').addClass('span4').removeClass('span3'); });"), 18 | index:header() ++ [ 19 | #panel{id=content, body=[ 20 | #section{class=[section, alt], body=#panel{class=[container], body= 21 | #carousel{class=["product-carousel"], items=[#product_figure{product=product:product(I,1)} || I <- lists:seq(1,2)] }}}, 22 | #section{class=[section], body=[ 23 | #panel{class=[container], body=[ 24 | #panel{class=["page-header", "thumbnail-filters"], body=[#h1{body=[ 25 | <<"Categories">>, 26 | #small{body=[[<<" / ">>, #link{data_fields=[{<<"data-filter">>, C}], body=C}] || {C, _D} <- categories() ]} 27 | ]} ]}, 28 | #list{id=products, class=[thumbnails, bordered, "thumbnail-list", products, "items-list"], body=list_products(1)}, 29 | #panel{class=["pagination pagination-centered"],body=[ #list{id=pagination, body=pagination(1)} ]} 30 | ]} 31 | ]}, 32 | #section{class=[section, alt], body=#panel{class=[container], body=[ 33 | #panel{class=["hero-unit", "text-center"], body=[ 34 | #h1{body= <<"Got a question?">>}, 35 | #p{body= <<"want to work with us to move your bussines to the next level? Well, dont be afraid">>}, 36 | #link{class=[btn, "btn-large", "btn-info"], body= <<"contact us">>} 37 | ]} 38 | ]}} 39 | ]}] ++ index:footer(). 40 | 41 | categories() -> 42 | [{<<"Action">>, <<"abababab">>}, {<<"Shooting">>, <<"shooting">>}, {<<"Sports">>, <<"bla-bla">>}, {<<"Role-Playing">>, <<"abababab">>}, {<<"Strategy">>, <<"bla-bla">>}]. 43 | 44 | list_products(Page) -> [#li{class=[span4], body=#product_figure{product=P}} || P <- lists:sublist(wf:session(products), (Page-1) * ?PAGE_SIZE + 1, ?PAGE_SIZE)]. 45 | 46 | pagination(Page)-> 47 | PageCount = (length(wf:session(products)) -1 ) div ?PAGE_SIZE + 1, 48 | [#li{class=[if Page==1-> "disabled"; true->[] end, "previous"],body=#link{class=["fui-arrow-left"], body= <<"‹">>, postback={page, 1}, url="javascript:void(0);" }}, 49 | [#li{class=if I==Page -> active;true->[] end,body=#link{id="pglink"++integer_to_list(I),body=integer_to_list(I), postback={page, I}, url="javascript:void(0);" }} 50 | || I <- lists:seq(1, PageCount)], 51 | #li{class=[if PageCount==Page -> "disabled";true->[] end,"next"], body=#link{class=["fui-arrow-right"], body= <<"›">>, postback={page, PageCount},url="javascript:void(0);" }} ]. 52 | 53 | event(init) -> []; 54 | event({counter,C}) -> wf:update(onlinenumber,wf:to_list(C)); 55 | event({page, Page})-> 56 | wf:update(pagination, pagination(Page)), 57 | wf:update(products, list_products(Page)); 58 | event({product, Id})-> wf:redirect("/product?id=" ++ Id); 59 | event(to_list)-> 60 | wf:session(page_size, list), 61 | wf:update(products, list_products(1)), 62 | wf:update(pagination, pagination(1)); 63 | event(to_grid)-> 64 | wf:session(page_size, grid), 65 | wf:update(products, list_products(1)), 66 | wf:update(pagination, pagination(1)); 67 | 68 | event(Event) -> error_logger:info_msg("Page event: ~p", [Event]), []. 69 | 70 | api_event(Name,Tag,Term) -> error_logger:info_msg("Name ~p, Tag ~p, Term ~p",[Name,Tag,Term]). 71 | -------------------------------------------------------------------------------- /apps/web/priv/templates/login.dtl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{title}} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 | 59 |
Not a member? Sign Up
60 |
61 |
62 |
63 | 64 |
65 | 66 | 67 | {{sdk}} 68 | 69 | 70 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /rels/web/files/nodetool: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- 2 | %% ex: ft=erlang ts=4 sw=4 et 3 | %% ------------------------------------------------------------------- 4 | %% 5 | %% nodetool: Helper Script for interacting with live nodes 6 | %% 7 | %% ------------------------------------------------------------------- 8 | 9 | main(Args) -> 10 | ok = start_epmd(), 11 | %% Extract the args 12 | {RestArgs, TargetNode} = process_args(Args, [], undefined), 13 | 14 | %% See if the node is currently running -- if it's not, we'll bail 15 | case {net_kernel:hidden_connect_node(TargetNode), net_adm:ping(TargetNode)} of 16 | {true, pong} -> 17 | ok; 18 | {_, pang} -> 19 | io:format("Node ~p not responding to pings.\n", [TargetNode]), 20 | halt(1) 21 | end, 22 | 23 | case RestArgs of 24 | ["ping"] -> 25 | %% If we got this far, the node already responsed to a ping, so just dump 26 | %% a "pong" 27 | io:format("pong\n"); 28 | ["stop"] -> 29 | io:format("~p\n", [rpc:call(TargetNode, init, stop, [], 60000)]); 30 | ["restart"] -> 31 | io:format("~p\n", [rpc:call(TargetNode, init, restart, [], 60000)]); 32 | ["reboot"] -> 33 | io:format("~p\n", [rpc:call(TargetNode, init, reboot, [], 60000)]); 34 | ["rpc", Module, Function | RpcArgs] -> 35 | case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), 36 | [RpcArgs], 60000) of 37 | ok -> 38 | ok; 39 | {badrpc, Reason} -> 40 | io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), 41 | halt(1); 42 | _ -> 43 | halt(1) 44 | end; 45 | ["rpcterms", Module, Function, ArgsAsString] -> 46 | case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), 47 | consult(ArgsAsString), 60000) of 48 | {badrpc, Reason} -> 49 | io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), 50 | halt(1); 51 | Other -> 52 | io:format("~p\n", [Other]) 53 | end; 54 | Other -> 55 | io:format("Other: ~p\n", [Other]), 56 | io:format("Usage: nodetool {ping|stop|restart|reboot}\n") 57 | end, 58 | net_kernel:stop(). 59 | 60 | process_args([], Acc, TargetNode) -> 61 | {lists:reverse(Acc), TargetNode}; 62 | process_args(["-setcookie", Cookie | Rest], Acc, TargetNode) -> 63 | erlang:set_cookie(node(), list_to_atom(Cookie)), 64 | process_args(Rest, Acc, TargetNode); 65 | process_args(["-name", TargetName | Rest], Acc, _) -> 66 | ThisNode = append_node_suffix(TargetName, "_maint_"), 67 | {ok, _} = net_kernel:start([ThisNode, longnames]), 68 | process_args(Rest, Acc, nodename(TargetName)); 69 | process_args(["-sname", TargetName | Rest], Acc, _) -> 70 | ThisNode = append_node_suffix(TargetName, "_maint_"), 71 | {ok, _} = net_kernel:start([ThisNode, shortnames]), 72 | process_args(Rest, Acc, nodename(TargetName)); 73 | process_args([Arg | Rest], Acc, Opts) -> 74 | process_args(Rest, [Arg | Acc], Opts). 75 | 76 | 77 | start_epmd() -> 78 | [] = os:cmd(epmd_path() ++ " -daemon"), 79 | ok. 80 | 81 | epmd_path() -> 82 | ErtsBinDir = filename:dirname(escript:script_name()), 83 | Name = "epmd", 84 | case os:find_executable(Name, ErtsBinDir) of 85 | false -> 86 | case os:find_executable(Name) of 87 | false -> 88 | io:format("Could not find epmd.~n"), 89 | halt(1); 90 | GlobalEpmd -> 91 | GlobalEpmd 92 | end; 93 | Epmd -> 94 | Epmd 95 | end. 96 | 97 | 98 | nodename(Name) -> 99 | case string:tokens(Name, "@") of 100 | [_Node, _Host] -> 101 | list_to_atom(Name); 102 | [Node] -> 103 | [_, Host] = string:tokens(atom_to_list(node()), "@"), 104 | list_to_atom(lists:concat([Node, "@", Host])) 105 | end. 106 | 107 | append_node_suffix(Name, Suffix) -> 108 | case string:tokens(Name, "@") of 109 | [Node, Host] -> 110 | list_to_atom(lists:concat([Node, Suffix, os:getpid(), "@", Host])); 111 | [Node] -> 112 | list_to_atom(lists:concat([Node, Suffix, os:getpid()])) 113 | end. 114 | 115 | 116 | %% 117 | %% Given a string or binary, parse it into a list of terms, ala file:consult/0 118 | %% 119 | consult(Str) when is_list(Str) -> 120 | consult([], Str, []); 121 | consult(Bin) when is_binary(Bin)-> 122 | consult([], binary_to_list(Bin), []). 123 | 124 | consult(Cont, Str, Acc) -> 125 | case erl_scan:tokens(Cont, Str, 0) of 126 | {done, Result, Remaining} -> 127 | case Result of 128 | {ok, Tokens, _} -> 129 | {ok, Term} = erl_parse:parse_term(Tokens), 130 | consult([], Remaining, [Term | Acc]); 131 | {eof, _Other} -> 132 | lists:reverse(Acc); 133 | {error, Info, _} -> 134 | {error, Info} 135 | end; 136 | {more, Cont1} -> 137 | consult(Cont1, eof, Acc) 138 | end. 139 | -------------------------------------------------------------------------------- /apps/web/src/feed.erl: -------------------------------------------------------------------------------- 1 | -module(feed). 2 | -compile(export_all). 3 | -include_lib("n2o/include/wf.hrl"). 4 | -include_lib("kvs/include/product.hrl"). 5 | -include_lib("kvs/include/comment.hrl"). 6 | -include_lib("kvs/include/feed.hrl"). 7 | -include_lib("nitro/include/nitro.hrl"). 8 | 9 | -include("records.hrl"). 10 | 11 | -define(PAGE_SIZE, 4). 12 | 13 | main() -> #dtl{file=wf:cache(mode), ext="dtl",bindings=[{title,<<"feed">>},{body, body()}]}. 14 | 15 | body() -> 16 | % Qid = wf:qs(<<"id">>), 17 | index:header()++[ 18 | #section{class=[section, alt], body=#panel{class=[container], body=[ 19 | #panel{class=["hero-unit"], body=[ 20 | #h1{body= <<"Product feed">>}, 21 | #p{body= <<"Thats a single review page">>} 22 | ]} 23 | ]}}, 24 | 25 | #section{class=[section], body=#panel{class=[container], body=#panel{class=["row-fluid"], body=[ 26 | #panel{class=[span9], body=[ 27 | product:blog_post_expanded(), 28 | #panel{class=[comments], body=[ 29 | #h3{body= <<"comments">>}, 30 | product:comment([product:comment(), product:comment([product:comment()])]), 31 | product:comment() 32 | ]}, 33 | #panel{class=["comments-form"], body=[ 34 | #h3{class=["comments-form"], body= <<"Add your comment">>}, 35 | #panel{class=["form-horizontal"], body=[ 36 | #fieldset{body=[ 37 | #panel{class=["control-group"], body=[ 38 | #label{class=["control-label"], for="name", body= <<"Name">>}, 39 | #panel{class=["controls"], body=[ 40 | #textbox{id=name, class=["input-xxlarge"]} 41 | ]} 42 | ]}, 43 | #panel{class=["control-group"], body=[ 44 | #label{class=["control-label"], for="email", body= <<"Email">>}, 45 | #panel{class=["controls"], body=[ 46 | #textbox{id=email, class=["input-xxlarge"]} 47 | ]} 48 | ]}, 49 | #panel{class=["control-group"], body=[ 50 | #label{class=["control-label"], for="message", body= <<"Your message">>}, 51 | #panel{class=["controls"], body=[ 52 | #textarea{id=message, class=["input-xxlarge"]} 53 | ]} 54 | ]}, 55 | #panel{class=["control-group"], body=[ 56 | #panel{class=["controls"], body=[ 57 | #button{class=[btn, "btn-info", "btn-large"], body= <<"send">>} 58 | ]} 59 | ]} 60 | ]} 61 | ]} 62 | ]} 63 | ]}, 64 | #aside{class=[span3, sidebar], body=[ 65 | #panel{class=["sidebar-widget"], body=[ 66 | #h2{class=["sidebar-header"], body= <<"Search">>}, 67 | #panel{class=["input-append"], body=[ 68 | #textbox{id="search-button"}, 69 | #button{class=[btn], body= <<"Go!">>} 70 | ]} 71 | ]}, 72 | #panel{class=["sidebar-widget"], body=[ 73 | #h2{class=["sidebar-header"], body= <<"About SmartBiz">>}, 74 | #p{body= <<"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.">>} 75 | ]}, 76 | #panel{class=["sidebar-widget"], body=[ 77 | #h2{class=["sidebar-header"], body= <<"Recent posts">>}, 78 | #list{class=[unstyled], body=[ 79 | #li{body=[#h4{body=#link{body = <<"Lorem ipsum dolor sit">>}}, 80 | #p{body=#small{body= <<"November 12, 2012">>}}]}, 81 | #li{body=[#h4{body=#link{body = <<"Ed do eiusmod tempor">>}}, 82 | #p{body=#small{body= <<"August 12, 2012">>}}]}, 83 | #li{body=[#h4{body=#link{body = <<"Incididunt ut labore">>}}, 84 | #p{body=#small{body= <<"June 12, 2012">>}}]}, 85 | #li{body=[#h4{body=#link{body = <<"Quis nostrud exercitation">>}}, 86 | #p{body=#small{body= <<"June 12, 2012">>}}]}, 87 | #li{body=[#h4{body=#link{body = <<"Quis nostrud exercitation">>}}, 88 | #p{body=#small{body= <<"June 12, 2012">>}}]} 89 | ]} 90 | ]}, 91 | #panel{class=["sidebar-widget"], body=[ 92 | #h2{class=["sidebar-header"], body= <<"Popular posts">>}, 93 | #list{class=[unstyled], body=[ 94 | #li{body=[#h4{body=#link{body = <<"Lorem ipsum dolor sit">>}}, 95 | #p{body=#small{body= <<"November 12, 2012">>}}]}, 96 | #li{body=[#h4{body=#link{body = <<"Ed do eiusmod tempor">>}}, 97 | #p{body=#small{body= <<"August 12, 2012">>}}]}, 98 | #li{body=[#h4{body=#link{body = <<"Quis nostrud exercitation">>}}, 99 | #p{body=#small{body= <<"June 12, 2012">>}}]} 100 | ]} 101 | ]} 102 | ]} 103 | ]}}} 104 | 105 | ]++index:footer(). 106 | 107 | title()-> <<"Review title">>. 108 | 109 | description(Id, Description) -> [ 110 | #panel{id="description"++Id, body=[ 111 | #panel{class=[collapse, in], body= <<"Description head">>}, 112 | #panel{id=Id, class=collapse, body= Description} 113 | ]}, 114 | #button{class=[btn, "btn-link"], data_fields=[ 115 | {<<"data-toggle">>, <<"collapse">>}, 116 | {<<"data-target">>, list_to_binary("#"++Id) }, 117 | {<<"data-parent">>, list_to_binary("#description"++Id)}], body= <<"Read...">>}]. 118 | 119 | list_medias(C)-> 120 | {Cid, Fid} = C#comment.id, 121 | [#feed_media{media=M, target=wf:temp_id(), fid = Fid, cid = Cid} || M <- C#comment.media]. 122 | 123 | image_media(_Media)-> element_image:render_element(#image{image= "/static/img/item-bg.png"}). 124 | 125 | event(init) -> []; 126 | event({counter,C}) -> wf:update(onlinenumber,wf:to_list(C)); 127 | event(Event) -> error_logger:info_msg("Page event: ~p", [Event]), []. 128 | 129 | api_event(Name,Tag,Term) -> error_logger:info_msg("Name ~p, Tag ~p, Term ~p",[Name,Tag,Term]), event(change_me). 130 | 131 | control_event(Id, Tag) -> 132 | error_logger:info_msg("Tinymce editor control event ~p: ~p", [Id, Tag]), 133 | Ed = wf:q(mcecontent), 134 | error_logger:info_msg("Data: ~p", [Ed]), 135 | ok. -------------------------------------------------------------------------------- /apps/web/priv/static/appstore/products.less: -------------------------------------------------------------------------------- 1 | .products {} 2 | 3 | .product-carousel{ 4 | & .carousel-inner{ max-height:500px;} 5 | & .carousel-caption{background:none;} 6 | } 7 | 8 | 9 | .product { 10 | text-decoration:none; 11 | margin: 0 0 24px; 12 | position: relative; 13 | background: #333333; 14 | 15 | 16 | & .product-price{ 17 | background: @white; 18 | color: @black; 19 | 20 | &-price{ 21 | font-size: 30px; 22 | margin: -10px -20px 0 -20px; 23 | padding: 8px 0 10px; 24 | background-color: #eeeeee; 25 | .box-shadow(0px 0px 10px rgba(153, 153, 153, 0.2) inset); 26 | color: #41b7d8; 27 | text-shadow: 1px 1px 0 white; 28 | 29 | & span {font-weight: normal;} 30 | } 31 | 32 | &-list{ 33 | list-style-type: none; 34 | margin: 0 0 20px; 35 | border-bottom: 1px solid white; 36 | .box-shadow(0px 1px 0px #e9e9e9); 37 | 38 | & li { 39 | border-top: 1px solid white; 40 | .box-shadow(0px -1px 0px #e9e9e9 ); 41 | padding: 8px; 42 | 43 | &:first-child{ 44 | border-color: transparent; 45 | .box-shadow(none); 46 | } 47 | } 48 | } 49 | 50 | &-featured { 51 | background: #41b7d8; 52 | border-color: #238caa; 53 | color: white; 54 | text-shadow: 0px 1px 0px #279ebf; 55 | 56 | & .product-price-price{ text-shadow: 1px 1px 0 white; color: #41b7d8;} 57 | & h3 { 58 | border-bottom: 0 none; 59 | & span{&:after{ 60 | display: inline-block; 61 | font-family: "FontAwesome"; 62 | content: "\f069"; 63 | padding-left: 10px; 64 | }} 65 | } 66 | & .product-price-list{ 67 | border-color: #279ebf; 68 | .box-shadow( 0px 1px 0px #6bc7e1); 69 | } 70 | & li { 71 | border-color: #6bc7e1; 72 | .box-shadow( 0px -1px 0px #279ebf); 73 | &:first-child{ 74 | border-color: transparent; 75 | .box-shadow(none); 76 | } 77 | } 78 | 79 | &.featured-orange{ 80 | background: #f89406; 81 | border-color: #c67605; 82 | color: white; 83 | text-shadow: 0px 1px 0px #c67605; 84 | 85 | & .product-price-price { 86 | text-shadow: 1px 1px 0 white; 87 | color: #f89406; 88 | } 89 | 90 | & h3 {border-bottom: 0 none;} 91 | 92 | & .product-price-list { 93 | border-color: #c67605; 94 | .box-shadow(0px 1px 0px #faa937); 95 | } 96 | 97 | & li { 98 | border-color: #faa937; 99 | .box-shadow(0px -1px 0px #c67605); 100 | &:first-child{ 101 | border-color: transparent; 102 | .box-shadow(none); 103 | } 104 | } 105 | } 106 | } 107 | } 108 | 109 | //product inside the featured carousel 110 | .product-carousel .carousel-inner & { 111 | 112 | & img {max-height:500px; .transition(all .2s); &:hover{opacity:.4;}} 113 | 114 | & .product-caption { 115 | position: absolute; 116 | top: 50%; 117 | margin-top:-50px; 118 | left: 0; 119 | margin-left:0; 120 | .transition(all .2s); 121 | 122 | & .product-title { 123 | position: absolute; 124 | top: 0; 125 | margin-top:0px; 126 | .transition(all .2s); 127 | max-width: 600px; 128 | max-height: 200px; 129 | 130 | & p { 131 | max-height: 150px; 132 | overflow:hidden; 133 | } 134 | & span { 135 | display: inline-block; 136 | background: @white; 137 | background: rgba(255, 255, 255, 0.8); 138 | color: @black; 139 | padding: 5px 10px; 140 | text-shadow: 1px 1px 1px white; 141 | margin-top: 1px; 142 | } 143 | } 144 | 145 | & .product-price { 146 | @media (min-width: 768px){ 147 | position: absolute; 148 | top:0; 149 | margin-top: -115px; 150 | left:70%; 151 | } 152 | @media (max-width: 767px) { display:none;} 153 | @media (min-width: 768px) and (max-width: 979px) { 154 | & h3 span:after{ padding-left:0;} 155 | } 156 | } 157 | } 158 | } 159 | 160 | // product inside grid view 161 | .items-grid & { 162 | & img {} 163 | & .product-price {display:none;} 164 | & .product-caption{ 165 | position:absolute; 166 | top:0; left:0; 167 | margin-top:0; 168 | } 169 | & .product-title { 170 | position: absolute; 171 | top: 50%; 172 | margin-top:40px; 173 | .transition(all .2s); 174 | 175 | & .badges {display:none;} 176 | } 177 | } 178 | 179 | // product inside list view 180 | .items-list & { 181 | // width:100%; 182 | // overflow: hidden; 183 | 184 | &:before{ 185 | // display: inline-block;vertical-align: middle; 186 | // content: ""; 187 | // padding-top: 75% 188 | } 189 | 190 | & img { 191 | // width:100%; 192 | // display: inline-block;vertical-align: middle; 193 | // padding-left: 100%; 194 | // margin: -999em -100%; 195 | } 196 | 197 | & .product-caption{ position:absolute; top:0; left:0;} 198 | & .product-title{ 199 | position:absolute; 200 | top:50%; 201 | margin-top:80px; 202 | overflow:hidden; 203 | max-height:100px; 204 | line-height:41px; 205 | & .badges{display:none;} 206 | } 207 | & .product-price{display:none;} 208 | } 209 | 210 | } 211 | 212 | .product { 213 | .product-view & { 214 | & .product-controls, & .product-name, & .product-price, & .product-description { 215 | width:100%; 216 | margin-left:0; 217 | } 218 | 219 | & .product-image { 220 | width:300px; 221 | img {max-width:290px;max-height:290px;} 222 | } 223 | } 224 | } 225 | 226 | .product-review1 { 227 | background: @reviewBackground; 228 | border: 1px solid #ddd; 229 | .border-radius(@baseBorderRadius); 230 | .box-shadow(0 1px 3px rgba(0,0,0,.055)); 231 | padding: 5px; 232 | margin-left:0 !important; 233 | 234 | & .review-date {display: block;} 235 | 236 | & .review-author { 237 | text-align:center; 238 | 239 | & .avatar { 240 | max-width:100px; 241 | max-height:100px; 242 | } 243 | } 244 | 245 | } 246 | 247 | .brief{ 248 | padding: 15px 0; 249 | } 250 | 251 | -------------------------------------------------------------------------------- /rels/web/files/node: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # -*- tab-width:4;indent-tabs-mode:nil -*- 3 | # ex: ts=4 sw=4 et 4 | 5 | RUNNER_SCRIPT_DIR=$(cd ${0%/*} && pwd) 6 | 7 | CALLER_DIR=$PWD 8 | 9 | RUNNER_BASE_DIR=${RUNNER_SCRIPT_DIR%/*} 10 | RUNNER_ETC_DIR=$RUNNER_BASE_DIR/etc 11 | # Note the trailing slash on $PIPE_DIR/ 12 | PIPE_DIR=/tmp/$RUNNER_BASE_DIR/ 13 | RUNNER_USER= 14 | 15 | # Make sure this script is running as the appropriate user 16 | if [ ! -z "$RUNNER_USER" ] && [ `whoami` != "$RUNNER_USER" ]; then 17 | exec sudo -u $RUNNER_USER -i $0 $@ 18 | fi 19 | 20 | # Identify the script name 21 | SCRIPT=`basename $0` 22 | 23 | # Parse out release and erts info 24 | START_ERL=`cat $RUNNER_BASE_DIR/releases/start_erl.data` 25 | ERTS_VSN=${START_ERL% *} 26 | APP_VSN=${START_ERL#* } 27 | 28 | # Use $CWD/vm.args if exists, otherwise releases/APP_VSN/vm.args, or else etc/vm.args 29 | if [ -e "$CALLER_DIR/vm.args" ]; then 30 | VMARGS_PATH=$CALLER_DIR/vm.args 31 | USE_DIR=$CALLER_DIR 32 | else 33 | USE_DIR=$RUNNER_BASE_DIR 34 | if [ -e "$RUNNER_BASE_DIR/releases/$APP_VSN/vm.args" ]; then 35 | VMARGS_PATH="$RUNNER_BASE_DIR/releases/$APP_VSN/vm.args" 36 | else 37 | VMARGS_PATH="$RUNNER_ETC_DIR/vm.args" 38 | fi 39 | fi 40 | 41 | RUNNER_LOG_DIR=$USE_DIR/log 42 | # Make sure log directory exists 43 | mkdir -p $RUNNER_LOG_DIR 44 | 45 | # Use releases/VSN/sys.config if it exists otherwise use etc/app.config 46 | if [ -e "$USE_DIR/sys.config" ]; then 47 | CONFIG_PATH="$USE_DIR/sys.config" 48 | else 49 | if [ -e "$RUNNER_BASE_DIR/releases/$APP_VSN/sys.config" ]; then 50 | CONFIG_PATH="$RUNNER_BASE_DIR/releases/$APP_VSN/sys.config" 51 | else 52 | CONFIG_PATH="$RUNNER_ETC_DIR/app.config" 53 | fi 54 | fi 55 | 56 | # Extract the target node name from node.args 57 | NAME_ARG=`egrep '^-s?name' $VMARGS_PATH` 58 | if [ -z "$NAME_ARG" ]; then 59 | echo "vm.args needs to have either -name or -sname parameter." 60 | exit 1 61 | fi 62 | 63 | # Extract the name type and name from the NAME_ARG for REMSH 64 | REMSH_TYPE=`echo $NAME_ARG | awk '{print $1}'` 65 | REMSH_NAME=`echo $NAME_ARG | awk '{print $2}'` 66 | 67 | # Note the `date +%s`, used to allow multiple remsh to the same node transparently 68 | REMSH_NAME_ARG="$REMSH_TYPE remsh`date +%s`@`echo $REMSH_NAME | awk -F@ '{print $2}'`" 69 | REMSH_REMSH_ARG="-remsh $REMSH_NAME" 70 | 71 | # Extract the target cookie 72 | COOKIE_ARG=`grep '^-setcookie' $VMARGS_PATH` 73 | if [ -z "$COOKIE_ARG" ]; then 74 | echo "vm.args needs to have a -setcookie parameter." 75 | exit 1 76 | fi 77 | 78 | # Make sure CWD is set to the right dir 79 | cd $USE_DIR 80 | 81 | # Make sure log directory exists 82 | mkdir -p $USE_DIR/log 83 | 84 | 85 | # Add ERTS bin dir to our path 86 | ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin 87 | 88 | # Setup command to control the node 89 | NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG" 90 | 91 | # Setup remote shell command to control node 92 | REMSH="$ERTS_PATH/erl $REMSH_NAME_ARG $REMSH_REMSH_ARG $COOKIE_ARG" 93 | 94 | # Check the first argument for instructions 95 | case "$1" in 96 | start|start_boot) 97 | # Make sure there is not already a node running 98 | RES=`$NODETOOL ping` 99 | if [ "$RES" = "pong" ]; then 100 | echo "Node is already running!" 101 | exit 1 102 | fi 103 | case "$1" in 104 | start) 105 | shift 106 | START_OPTION="console" 107 | HEART_OPTION="start" 108 | ;; 109 | start_boot) 110 | shift 111 | START_OPTION="console_boot" 112 | HEART_OPTION="start_boot" 113 | ;; 114 | esac 115 | RUN_PARAM=$(printf "\'%s\' " "$@") 116 | HEART_COMMAND="$RUNNER_BASE_DIR/bin/$SCRIPT $HEART_OPTION $RUN_PARAM" 117 | export HEART_COMMAND 118 | mkdir -p $PIPE_DIR 119 | $ERTS_PATH/run_erl -daemon $PIPE_DIR $RUNNER_LOG_DIR "exec $RUNNER_BASE_DIR/bin/$SCRIPT $START_OPTION $RUN_PARAM" 2>&1 120 | ;; 121 | 122 | stop) 123 | # Wait for the node to completely stop... 124 | case `uname -s` in 125 | Linux|Darwin|FreeBSD|DragonFly|NetBSD|OpenBSD) 126 | # PID COMMAND 127 | PID=`ps ax -o pid= -o command=|\ 128 | grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $1}'` 129 | ;; 130 | SunOS) 131 | # PID COMMAND 132 | PID=`ps -ef -o pid= -o args=|\ 133 | grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $1}'` 134 | ;; 135 | CYGWIN*) 136 | # UID PID PPID TTY STIME COMMAND 137 | PID=`ps -efW|grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $2}'` 138 | ;; 139 | esac 140 | $NODETOOL stop 141 | ES=$? 142 | if [ "$ES" -ne 0 ]; then 143 | exit $ES 144 | fi 145 | while `kill -0 $PID 2>/dev/null`; 146 | do 147 | sleep 1 148 | done 149 | ;; 150 | 151 | restart) 152 | ## Restart the VM without exiting the process 153 | $NODETOOL restart 154 | ES=$? 155 | if [ "$ES" -ne 0 ]; then 156 | exit $ES 157 | fi 158 | ;; 159 | 160 | reboot) 161 | ## Restart the VM completely (uses heart to restart it) 162 | $NODETOOL reboot 163 | ES=$? 164 | if [ "$ES" -ne 0 ]; then 165 | exit $ES 166 | fi 167 | ;; 168 | 169 | ping) 170 | ## See if the VM is alive 171 | $NODETOOL ping 172 | ES=$? 173 | if [ "$ES" -ne 0 ]; then 174 | exit $ES 175 | fi 176 | ;; 177 | 178 | attach) 179 | # Make sure a node IS running 180 | RES=`$NODETOOL ping` 181 | ES=$? 182 | if [ "$ES" -ne 0 ]; then 183 | echo "Node is not running!" 184 | exit $ES 185 | fi 186 | 187 | shift 188 | exec $ERTS_PATH/to_erl $PIPE_DIR 189 | ;; 190 | 191 | remote_console) 192 | # Make sure a node IS running 193 | RES=`$NODETOOL ping` 194 | ES=$? 195 | if [ "$ES" -ne 0 ]; then 196 | echo "Node is not running!" 197 | exit $ES 198 | fi 199 | 200 | shift 201 | exec $REMSH 202 | ;; 203 | 204 | upgrade) 205 | if [ -z "$2" ]; then 206 | echo "Missing upgrade package argument" 207 | echo "Usage: $SCRIPT upgrade {package base name}" 208 | echo "NOTE {package base name} MUST NOT include the .tar.gz suffix" 209 | exit 1 210 | fi 211 | 212 | # Make sure a node IS running 213 | RES=`$NODETOOL ping` 214 | ES=$? 215 | if [ "$ES" -ne 0 ]; then 216 | echo "Node is not running!" 217 | exit $ES 218 | fi 219 | 220 | node_name=`echo $NAME_ARG | awk '{print $2}'` 221 | erlang_cookie=`echo $COOKIE_ARG | awk '{print $2}'` 222 | 223 | $ERTS_PATH/escript $RUNNER_BASE_DIR/bin/install_upgrade.escript $node_name $erlang_cookie $2 224 | ;; 225 | 226 | console|console_clean|console_boot) 227 | # .boot file typically just $SCRIPT (ie, the app name) 228 | # however, for debugging, sometimes start_clean.boot is useful. 229 | # For e.g. 'setup', one may even want to name another boot script. 230 | case "$1" in 231 | console) BOOTFILE=$SCRIPT ;; 232 | console_clean) BOOTFILE=start_clean ;; 233 | console_boot) 234 | shift 235 | BOOTFILE="$1" 236 | shift 237 | ;; 238 | esac 239 | # Setup beam-required vars 240 | ROOTDIR=$RUNNER_BASE_DIR 241 | BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin 242 | EMU=beam 243 | PROGNAME=`echo $0 | sed 's/.*\\///'` 244 | CMD="$BINDIR/erlexec -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$BOOTFILE -mode embedded -config $CONFIG_PATH -args_file $VMARGS_PATH" 245 | export EMU 246 | export ROOTDIR 247 | export BINDIR 248 | export PROGNAME 249 | 250 | # Dump environment info for logging purposes 251 | echo "Exec: $CMD" -- ${1+"$@"} 252 | echo "Root: $ROOTDIR" 253 | 254 | # Log the startup 255 | logger -t "$SCRIPT[$$]" "Starting up" 256 | 257 | # Start the VM 258 | exec $CMD -- ${1+"$@"} 259 | ;; 260 | 261 | foreground) 262 | # start up the release in the foreground for use by runit 263 | # or other supervision services 264 | 265 | BOOTFILE=$SCRIPT 266 | FOREGROUNDOPTIONS="-noinput +Bd" 267 | 268 | # Setup beam-required vars 269 | ROOTDIR=$RUNNER_BASE_DIR 270 | BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin 271 | EMU=beam 272 | PROGNAME=`echo $0 | sed 's/.*\///'` 273 | CMD="$BINDIR/erlexec $FOREGROUNDOPTIONS -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$BOOTFILE -config $CONFIG_PATH -args_file $VMARGS_PATH" 274 | export EMU 275 | export ROOTDIR 276 | export BINDIR 277 | export PROGNAME 278 | 279 | # Dump environment info for logging purposes 280 | echo "Exec: $CMD" -- ${1+"$@"} 281 | echo "Root: $ROOTDIR" 282 | 283 | # Start the VM 284 | exec $CMD -- ${1+"$@"} 285 | ;; 286 | *) 287 | echo "Usage: $SCRIPT {start|start_boot |foreground|stop|restart|reboot|ping|console|console_clean|console_boot |attach|remote_console|upgrade}" 288 | exit 1 289 | ;; 290 | esac 291 | 292 | exit 0 293 | -------------------------------------------------------------------------------- /apps/web/priv/static/js/all.min.js: -------------------------------------------------------------------------------- 1 | function atom(o){return{type:"Atom",value:o,toString:function(){return this.value}}}function bin(o){return{type:"Binary",value:o,toString:function(){return"<<'"+this.value+"'>>"}}}function tuple(){return{type:"Tuple",value:arguments,toString:function(){var s="";for(var i=0;i0){throw"BERT: Range: "+OriginalInt}return s}function ltoi(S,Length){var isNegative,i,n,Num=0;isNegative=S.charCodeAt(0)>128;for(i=0;i=0;i--){n=S.charCodeAt(i);if(Num===0){Num=n}else{Num=Num*256+n}}if(isNegative){return Num*-1}return Num}function encode(o){return BERT+en_inner(o)}function en_inner(Obj){if(Obj===undefined)return NIL;var func="en_"+typeof Obj;return eval(func)(Obj)}function en_string(Obj){return STR+itol(Obj.length,2)+Obj}function en_boolean(Obj){if(Obj)return en_inner(atom("true"));else return en_inner(atom("false"))}function en_number(Obj){var s,isi=Obj%1===0;if(!isi){return en_float(Obj)}if(isi&&Obj>=0&&Obj<256){return SINT+itol(Obj,1)}return INT+itol(Obj,4)}function en_float(Obj){var s=Obj.toExponential();while(s.length<31){s+=ZERO}return FLOAT+s}function en_object(Obj){if(Obj.type==="Atom")return en_atom(Obj);if(Obj.type==="Binary")return en_bin(Obj);if(Obj.type==="Tuple")return en_tuple(Obj);if(Obj.constructor.toString().indexOf("Array")!==-1)return en_array(Obj);return en_associative_array(Obj)}function en_atom(Obj){return ATOM+itol(Obj.value.length,2)+Obj.value}function en_bin(Obj){return BINARY+itol(Obj.value.length,4)+Obj.value}function en_tuple(Obj){var i,s="";if(Obj.value.length<256){s+=TUPLE+itol(Obj.value.length,1)}else{s+=LTUPLE+itol(Obj.value.length,4)}for(i=0;idelayMax){delay=delayMax}isClosed=true;setTimeout(function(){init()},delay)}};transport.onerror=transport.onclose;transport.onmessage=function(e){stream.onmessage(e)}}init();this.onopen=function(){};this.oninit=function(){};this.onmessage=function(){};this.ondisconnect=function(){};this.onclose=function(){};this.onheartbeat=function(){return this.send("PING")};this.setURL=function(newURL){url=newURL};this.send=function(data){if(transport)return transport.send(data);else return false};this.close=function(){readyState=CLOSING;if(transport)transport.close()}};return stream}var msg=0;var ws;var utf8={};function addStatus(text){var date=new Date;if(document.getElementById("n2ostatus")){document.getElementById("n2ostatus").innerHTML=document.getElementById("n2ostatus").innerHTML+"E> "+text+"
"}}utf8.toByteArray=function(str){var byteArray=[];if(str!==undefined&&str!==null)for(var i=0;i0){var dataView=new DataView(reader.result);var s=dataView.getInt8(0).toString();for(var i=1;i 1) { 68 | text_height = template.size / (ctx.measureText(text).width / width); 69 | } 70 | //Resetting font size if necessary 71 | ctx.font = "bold " + (text_height * ratio) + "px "+font; 72 | ctx.fillText(text, (width / 2), (height / 2), width); 73 | return canvas.toDataURL("image/png"); 74 | } 75 | 76 | function render(mode, el, holder, src) { 77 | var dimensions = holder.dimensions, 78 | theme = holder.theme, 79 | text = holder.text ? decodeURIComponent(holder.text) : holder.text; 80 | var dimensions_caption = dimensions.width + "x" + dimensions.height; 81 | theme = (text ? extend(theme, { text: text }) : theme); 82 | theme = (holder.font ? extend(theme, {font: holder.font}) : theme); 83 | 84 | var ratio = 1; 85 | if(window.devicePixelRatio && window.devicePixelRatio > 1){ 86 | ratio = window.devicePixelRatio; 87 | } 88 | 89 | if (mode == "image") { 90 | el.setAttribute("data-src", src); 91 | el.setAttribute("alt", text ? text : theme.text ? theme.text + " [" + dimensions_caption + "]" : dimensions_caption); 92 | 93 | if(fallback || !holder.auto){ 94 | el.style.width = dimensions.width + "px"; 95 | el.style.height = dimensions.height + "px"; 96 | } 97 | 98 | if (fallback) { 99 | el.style.backgroundColor = theme.background; 100 | 101 | } 102 | else{ 103 | el.setAttribute("src", draw(ctx, dimensions, theme, ratio)); 104 | } 105 | } else { 106 | if (!fallback) { 107 | el.style.backgroundImage = "url(" + draw(ctx, dimensions, theme, ratio) + ")"; 108 | el.style.backgroundSize = dimensions.width+"px "+dimensions.height+"px"; 109 | } 110 | } 111 | }; 112 | 113 | function fluid(el, holder, src) { 114 | var dimensions = holder.dimensions, 115 | theme = holder.theme, 116 | text = holder.text; 117 | var dimensions_caption = dimensions.width + "x" + dimensions.height; 118 | theme = (text ? extend(theme, { 119 | text: text 120 | }) : theme); 121 | 122 | var fluid = document.createElement("div"); 123 | 124 | fluid.style.backgroundColor = theme.background; 125 | fluid.style.color = theme.foreground; 126 | fluid.className = el.className + " holderjs-fluid"; 127 | fluid.style.width = holder.dimensions.width + (holder.dimensions.width.indexOf("%")>0?"":"px"); 128 | fluid.style.height = holder.dimensions.height + (holder.dimensions.height.indexOf("%")>0?"":"px"); 129 | fluid.id = el.id; 130 | 131 | el.style.width=0; 132 | el.style.height=0; 133 | 134 | if (theme.text) { 135 | fluid.appendChild(document.createTextNode(theme.text)) 136 | } else { 137 | fluid.appendChild(document.createTextNode(dimensions_caption)) 138 | fluid_images.push(fluid); 139 | setTimeout(fluid_update, 0); 140 | } 141 | 142 | el.parentNode.insertBefore(fluid, el.nextSibling) 143 | 144 | if(window.jQuery){ 145 | jQuery(function($){ 146 | $(el).on("load", function(){ 147 | el.style.width = fluid.style.width; 148 | el.style.height = fluid.style.height; 149 | $(el).show(); 150 | $(fluid).remove(); 151 | }); 152 | }) 153 | } 154 | } 155 | 156 | function fluid_update() { 157 | for (i in fluid_images) { 158 | if(!fluid_images.hasOwnProperty(i)) continue; 159 | var el = fluid_images[i], 160 | label = el.firstChild; 161 | 162 | el.style.lineHeight = el.offsetHeight+"px"; 163 | label.data = el.offsetWidth + "x" + el.offsetHeight; 164 | } 165 | } 166 | 167 | function parse_flags(flags, options) { 168 | 169 | var ret = { 170 | theme: settings.themes.gray 171 | }, render = false; 172 | 173 | for (sl = flags.length, j = 0; j < sl; j++) { 174 | var flag = flags[j]; 175 | if (app.flags.dimensions.match(flag)) { 176 | render = true; 177 | ret.dimensions = app.flags.dimensions.output(flag); 178 | } else if (app.flags.fluid.match(flag)) { 179 | render = true; 180 | ret.dimensions = app.flags.fluid.output(flag); 181 | ret.fluid = true; 182 | } else if (app.flags.colors.match(flag)) { 183 | ret.theme = app.flags.colors.output(flag); 184 | } else if (options.themes[flag]) { 185 | //If a theme is specified, it will override custom colors 186 | ret.theme = options.themes[flag]; 187 | } else if (app.flags.text.match(flag)) { 188 | ret.text = app.flags.text.output(flag); 189 | } else if(app.flags.font.match(flag)){ 190 | ret.font = app.flags.font.output(flag); 191 | } 192 | else if(app.flags.auto.match(flag)){ 193 | ret.auto = true; 194 | } 195 | } 196 | 197 | return render ? ret : false; 198 | 199 | }; 200 | 201 | if (!canvas.getContext) { 202 | fallback = true; 203 | } else { 204 | if (canvas.toDataURL("image/png") 205 | .indexOf("data:image/png") < 0) { 206 | //Android doesn't support data URI 207 | fallback = true; 208 | } else { 209 | var ctx = canvas.getContext("2d"); 210 | } 211 | } 212 | 213 | var fluid_images = []; 214 | 215 | var settings = { 216 | domain: "holder.js", 217 | images: "img", 218 | bgnodes: ".holderjs", 219 | themes: { 220 | "gray": { 221 | background: "#eee", 222 | foreground: "#aaa", 223 | size: 12 224 | }, 225 | "social": { 226 | background: "#3a5a97", 227 | foreground: "#fff", 228 | size: 12 229 | }, 230 | "industrial": { 231 | background: "#434A52", 232 | foreground: "#C2F200", 233 | size: 12 234 | } 235 | }, 236 | stylesheet: ".holderjs-fluid {font-size:16px;font-weight:bold;text-align:center;font-family:sans-serif;margin:0}" 237 | }; 238 | 239 | 240 | app.flags = { 241 | dimensions: { 242 | regex: /^(\d+)x(\d+)$/, 243 | output: function (val) { 244 | var exec = this.regex.exec(val); 245 | return { 246 | width: +exec[1], 247 | height: +exec[2] 248 | } 249 | } 250 | }, 251 | fluid: { 252 | regex: /^([0-9%]+)x([0-9%]+)$/, 253 | output: function (val) { 254 | var exec = this.regex.exec(val); 255 | return { 256 | width: exec[1], 257 | height: exec[2] 258 | } 259 | } 260 | }, 261 | colors: { 262 | regex: /#([0-9a-f]{3,})\:#([0-9a-f]{3,})/i, 263 | output: function (val) { 264 | var exec = this.regex.exec(val); 265 | return { 266 | size: settings.themes.gray.size, 267 | foreground: "#" + exec[2], 268 | background: "#" + exec[1] 269 | } 270 | } 271 | }, 272 | text: { 273 | regex: /text\:(.*)/, 274 | output: function (val) { 275 | return this.regex.exec(val)[1]; 276 | } 277 | }, 278 | font: { 279 | regex: /font\:(.*)/, 280 | output: function(val){ 281 | return this.regex.exec(val)[1]; 282 | } 283 | }, 284 | auto: { 285 | regex: /^auto$/ 286 | } 287 | } 288 | 289 | for (var flag in app.flags) { 290 | if(!app.flags.hasOwnProperty(flag)) continue; 291 | app.flags[flag].match = function (val) { 292 | return val.match(this.regex) 293 | } 294 | } 295 | 296 | app.add_theme = function (name, theme) { 297 | name != null && theme != null && (settings.themes[name] = theme); 298 | return app; 299 | }; 300 | 301 | app.add_image = function (src, el) { 302 | var node = selector(el); 303 | if (node.length) { 304 | for (var i = 0, l = node.length; i < l; i++) { 305 | var img = document.createElement("img") 306 | img.setAttribute("data-src", src); 307 | node[i].appendChild(img); 308 | } 309 | } 310 | return app; 311 | }; 312 | 313 | app.run = function (o) { 314 | var options = extend(settings, o), images = []; 315 | 316 | if(options.images instanceof window.NodeList){ 317 | imageNodes = options.images; 318 | } 319 | else if(options.images instanceof window.Node){ 320 | imageNodes = [options.images]; 321 | } 322 | else{ 323 | imageNodes = selector(options.images); 324 | } 325 | 326 | if(options.elements instanceof window.NodeList){ 327 | bgnodes = options.bgnodes; 328 | } 329 | else if(options.bgnodes instanceof window.Node){ 330 | bgnodes = [options.bgnodes]; 331 | } 332 | else{ 333 | bgnodes = selector(options.bgnodes); 334 | } 335 | 336 | preempted = true; 337 | 338 | for (i = 0, l = imageNodes.length; i < l; i++) images.push(imageNodes[i]); 339 | 340 | var holdercss = document.getElementById("holderjs-style"); 341 | 342 | if(!holdercss){ 343 | holdercss = document.createElement("style"); 344 | holdercss.setAttribute("id", "holderjs-style"); 345 | holdercss.type = "text/css"; 346 | document.getElementsByTagName("head")[0].appendChild(holdercss); 347 | } 348 | 349 | if(holdercss.styleSheet){ 350 | holdercss.styleSheet += options.stylesheet; 351 | } 352 | else{ 353 | holdercss.textContent+= options.stylesheet; 354 | } 355 | 356 | var cssregex = new RegExp(options.domain + "\/(.*?)\"?\\)"); 357 | 358 | for (var l = bgnodes.length, i = 0; i < l; i++) { 359 | var src = window.getComputedStyle(bgnodes[i], null) 360 | .getPropertyValue("background-image"); 361 | var flags = src.match(cssregex); 362 | if (flags) { 363 | var holder = parse_flags(flags[1].split("/"), options); 364 | if (holder) { 365 | render("background", bgnodes[i], holder, src); 366 | } 367 | } 368 | } 369 | 370 | for (var l = images.length, i = 0; i < l; i++) { 371 | var src = images[i].getAttribute("src") || images[i].getAttribute("data-src"); 372 | if (src != null && src.indexOf(options.domain) >= 0) { 373 | var holder = parse_flags(src.substr(src.lastIndexOf(options.domain) + options.domain.length + 1) 374 | .split("/"), options); 375 | if (holder) { 376 | if (holder.fluid) { 377 | fluid(images[i], holder, src); 378 | } else { 379 | render("image", images[i], holder, src); 380 | } 381 | } 382 | } 383 | } 384 | return app; 385 | }; 386 | 387 | contentLoaded(win, function () { 388 | if (window.addEventListener) { 389 | window.addEventListener("resize", fluid_update, false); 390 | window.addEventListener("orientationchange", fluid_update, false); 391 | } else { 392 | window.attachEvent("onresize", fluid_update) 393 | } 394 | preempted || app.run(); 395 | }); 396 | 397 | if ( typeof define === "function" && define.amd ) { 398 | define( "Holder", [], function () { return app; } ); 399 | } 400 | 401 | })(Holder, window); 402 | -------------------------------------------------------------------------------- /apps/web/src/product.erl: -------------------------------------------------------------------------------- 1 | -module(product). 2 | -compile(export_all). 3 | -include_lib("n2o/include/wf.hrl"). 4 | -include_lib("kvs/include/user.hrl"). 5 | -include_lib("kvs/include/product.hrl"). 6 | -include_lib("kvs/include/comment.hrl"). 7 | -include_lib("kvs/include/entry.hrl"). 8 | -include_lib("kvs/include/feed.hrl"). 9 | -include_lib("nitro/include/nitro.hrl"). 10 | 11 | -include("records.hrl"). 12 | 13 | -define(PAGE_SIZE, 4). 14 | 15 | main() -> 16 | #dtl{file=wf:cache(mode), ext="dtl",bindings=[{title,<<"product">>},{body, body()}]}. 17 | 18 | body() -> 19 | % Qid = wf:qs(<<"id">>), 20 | % Product = case [P || #product{id=Id} = P <- wf:session(products), Id == list_to_integer(binary_to_list(Qid))] of [] -> []; L -> lists:nth(1,L) end, 21 | P = product(1,2), 22 | 23 | index:header()++[ 24 | #section{class=[section, alt], body=#panel{class=[container], body=#panel{class=["row-fluid"], body=[ 25 | #panel{class=[span6], body=[ 26 | #panel{class=["hero-unit"], body=[ 27 | #h1{body=P#product.title}, 28 | #p{body=P#product.brief}, 29 | #button{class=[btn, "btn-large", "btn-info"], body= <<"buy it">>, postback={product, integer_to_list(P#product.id)}} 30 | ]} 31 | ]}, 32 | #panel{class=[span6], body=#image{image=P#product.cover}} 33 | ]}}}, 34 | 35 | #section{class=[section], body=#panel{class=[container], body=#panel{class=["row-fluid"], body=[ 36 | #panel{class=[span9], body=[ 37 | [blog_post() || _I <- lists:seq(1,3)], 38 | #list{class=[pager], body=[ 39 | #li{class=[previous], body=#link{body=[#i{class=["icon-chevron-left"]}, <<" Older">> ]}}, 40 | #li{class=[next], body=#link{body=[#i{class=["icon-chevron-right"]}, <<" Newer">> ]}} 41 | ]} 42 | ]}, 43 | #aside{class=[span3, sidebar], body=[ 44 | #panel{class=["sidebar-widget"], body=[ 45 | #h2{class=["sidebar-header"], body= <<"Search">>}, 46 | #panel{class=["input-append"], body=[ 47 | #textbox{id="search-button"}, 48 | #button{class=[btn], body= <<"Go!">>} 49 | ]} 50 | ]}, 51 | #panel{class=["sidebar-widget"], body=[ 52 | #h2{class=["sidebar-header"], body= <<"About SmartBiz">>}, 53 | #p{body= <<"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.">>} 54 | ]}, 55 | #panel{class=["sidebar-widget"], body=[ 56 | #h2{class=["sidebar-header"], body= <<"Recent posts">>}, 57 | #list{class=[unstyled], body=[ 58 | #li{body=[#h4{body=#link{body = <<"Lorem ipsum dolor sit">>}}, 59 | #p{body=#small{body= <<"November 12, 2012">>}}]}, 60 | #li{body=[#h4{body=#link{body = <<"Ed do eiusmod tempor">>}}, 61 | #p{body=#small{body= <<"August 12, 2012">>}}]}, 62 | #li{body=[#h4{body=#link{body = <<"Incididunt ut labore">>}}, 63 | #p{body=#small{body= <<"June 12, 2012">>}}]}, 64 | #li{body=[#h4{body=#link{body = <<"Quis nostrud exercitation">>}}, 65 | #p{body=#small{body= <<"June 12, 2012">>}}]}, 66 | #li{body=[#h4{body=#link{body = <<"Quis nostrud exercitation">>}}, 67 | #p{body=#small{body= <<"June 12, 2012">>}}]} 68 | ]} 69 | ]}, 70 | #panel{class=["sidebar-widget"], body=[ 71 | #h2{class=["sidebar-header"], body= <<"Popular posts">>}, 72 | #list{class=[unstyled], body=[ 73 | #li{body=[#h4{body=#link{body = <<"Lorem ipsum dolor sit">>}}, 74 | #p{body=#small{body= <<"November 12, 2012">>}}]}, 75 | #li{body=[#h4{body=#link{body = <<"Ed do eiusmod tempor">>}}, 76 | #p{body=#small{body= <<"August 12, 2012">>}}]}, 77 | #li{body=[#h4{body=#link{body = <<"Quis nostrud exercitation">>}}, 78 | #p{body=#small{body= <<"June 12, 2012">>}}]} 79 | ]} 80 | ]} 81 | ]} 82 | 83 | ]}}} 84 | 85 | ]++index:footer(). 86 | 87 | blog_post()-> % feed entry 88 | #article{class=["blog-post"], body=[ 89 | #header{class=["blog-header"], body=[ 90 | #h2{body=[<<"Lorem ipsum dolor">>, #small{body=[<<" by">>, #link{body= <<" John Doe">>}, <<" 12 Sep 2012.">>]}]} 91 | ]}, 92 | #figure{class=["thumbnail-figure"], body=[ 93 | #link{url= <<"/feed?id=1">>, body=[#image{image= <<"/static/img/crysis3-bg1.png">>} ]}, 94 | #figcaption{class=["thumbnail-title"], body=[ 95 | #h3{body=#span{body= <<"Lorem ipsum dolor sit amet">>}} 96 | ]} 97 | ]}, 98 | #p{body= <<"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.">>}, 99 | #footer{class=["blog-footer", "row-fluid"], body=[ 100 | #panel{class=[span4, "blog-categories"], body=[#i{class=["icon-pushpin"]}, #link{body= <<" consectetur">>}]}, 101 | #panel{class=[span4, "blog-tags"], body=[#i{class=["icon-tags"]}, #link{body= <<" fugiat, nulla, pariatur">>}]}, 102 | #panel{class=[span4, "blog-more"], body=[#i{class=["icon-link"]}, #link{body= <<" read more">>, url="/feed"}]} 103 | ]} 104 | ]}. 105 | 106 | blog_post_expanded()-> 107 | #article{class=["blog-post"], body=[ 108 | #header{class=["blog-header"], body=[ 109 | #h2{body=[<<"Lorem ipsum dolor">>, #small{body=[<<" by">>, #link{body= <<" John Doe">>}, <<" 12 Sep 2012.">>]}]} 110 | ]}, 111 | #figure{class=["thumbnail-figure"], body=[ 112 | #link{url= <<"/feed?id=1">>, body=[#image{image= <<"/static/img/crysis3-bg1.png">>} ]}, 113 | #figcaption{class=["thumbnail-title"], body=[ 114 | #h3{body=#span{body= <<"Lorem ipsum dolor sit amet">>}} 115 | ]} 116 | ]}, 117 | #p{body= <<"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.">>}, 118 | #p{body= <<"Nullam eget suscipit turpis. Suspendisse nec magna et velit elementum vulputate. Suspendisse ac nibh lectus, at sollicitudin turpis. Aenean ut tortor a felis consectetur pulvinar. Phasellus mattis viverra luctus. Pellentesque tempor bibendum arcu non vestibulum. In bibendum mattis nibh, nec laoreet enim pretium a. Donec augue sem, convallis euismod pellentesque non, facilisis non nulla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Duis blandit cursus mi, malesuada euismod enim accumsan nec. Pellentesque nisl enim, elementum non molestie eu, lobortis sed odio. Mauris vehicula commodo neque, nec viverra libero accumsan in.">>}, 119 | #panel{class=["row-fluid"], body=[ 120 | #panel{class=[span6], body=#p{body= <<"Vestibulum et dapibus orci. Vivamus non elit sed quam egestas egestas. Donec interdum ultrices ante ac pharetra. Praesent euismod augue erat, vel ornare sem. Morbi ante nisl, fringilla at commodo vitae, cursus aliquet lacus. Maecenas nec orci at est venenatis congue vitae eget justo. Aenean dui odio, eleifend sed viverra et, eleifend vitae tortor. Duis ac diam vitae risus aliquam tempor. Phasellus volutpat, metus porttitor ornare gravida, erat lacus bibendum lectus, et dictum tortor ligula a est. Proin id purus eget mi vestibulum accumsan. Nam scelerisque malesuada tellus vel laoreet. Vestibulum eleifend, risus ut faucibus iaculis">>}}, 121 | #panel{class=[span6], body=[#image{image= <<"/static/img/crysis3-bg2.png">>}]} 122 | ]}, 123 | #p{body= <<"Quisque diam libero, aliquam eget blandit et, vulputate vel felis. Quisque ut purus at justo mattis volutpat. Curabitur nibh neque, sodales feugiat suscipit vel, vulputate nec quam. Sed quam nulla, sollicitudin non pulvinar in, viverra ac nunc. Maecenas a neque quis mauris vehicula viverra eu eget elit. Suspendisse potenti. Donec tincidunt sollicitudin elementum. Nunc volutpat purus ac lectus tincidunt et bibendum quam sollicitudin. Pellentesque rutrum ultricies porttitor. Suspendisse pellentesque rutrum mollis. Integer varius nulla quis metus varius imperdiet. ">>}, 124 | #footer{class=["blog-footer", "row-fluid"], body=[ 125 | #panel{class=[span4, "blog-categories"], body=[#i{class=["icon-pushpin"]}, #link{body= <<" consectetur">>}]}, 126 | #panel{class=[span4, "blog-tags"], body=[#i{class=["icon-tags"]}, #link{body= <<" fugiat, nulla, pariatur">>}]}, 127 | #panel{class=[span4, "blog-more"], body=[#i{class=["icon-link"]}, #link{body= <<" read more">>}]} 128 | ]} 129 | ]}. 130 | 131 | comment() -> comment([]). 132 | comment(InnerComment)-> 133 | #panel{class=[media, "media-comment"], body=[ 134 | #link{class=["pull-left"], body=[ 135 | #image{class=["media-objects","img-circle"], data_fields=[{<<"data-src">>, <<"holder.js/64x64">>}]} 136 | ]}, 137 | #panel{class=["media-body"], body=[ 138 | #p{class=["media-heading"], body=[ 139 | <<"John Doe, 12 Sep 2012.">>, 140 | #link{class=["comment-reply","pull-right"], body= <<"reply">>} 141 | ]}, 142 | #p{body= <<"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.">>}, 143 | InnerComment 144 | ]} 145 | ]}. 146 | 147 | feed() -> [#feed_entry{entry=E} || E <- entries(), _I <- lists:seq(1, 5)]. 148 | 149 | render_element(#product_figure{product=P})-> 150 | {PriceHead, PriceClass, BtnClass} = case P#product.price of % case for featured product 151 | true -> {<<"Featured">>, ["pricing-table-featured", "product-price-featured"], []}; 152 | false -> {<<"Featured">>, ["pricing-table-featured", "product-price-featured", "featured-orange"], []}; 153 | _ -> {<<"Standard">>, [], ["btn-info"]} 154 | end, 155 | 156 | L = #link{url= list_to_binary("/product?id="++ integer_to_list(P#product.id)), body=#figure{class=[product, "thumbnail-figure"], body=[ 157 | #image{image=P#product.cover}, 158 | #figcaption{class=["row-fluid", "product-caption"], body=[ 159 | #panel{class=["product-title", "thumbnail-title" ], body=[ 160 | #h3{body=#span{body=P#product.title}}, 161 | #p{body=#span{body=P#product.brief}}, 162 | #span{class=[badges],body=[ 163 | #i{class=["icon-user"]}, #span{class=["badge badge-info"], body= <<"1024">>}, 164 | #i{class=["icon-comment"]}, #span{class=["badge badge-info"], body= <<"10">>} 165 | ]} ]}, 166 | #panel{class=["well","pricing-table", "product-price", span3, "text-center"]++PriceClass, body=[ 167 | #h3{body=#span{body=PriceHead}}, 168 | #h2{class=["pricing-table-price", "product-price-price"], body=[#span{body= <<"$">>}, list_to_binary(io_lib:format("~.2f", [P#product.price]))]}, 169 | 170 | #list{class=["pricing-table-list", "product-price-list"], body=[ 171 | #li{body= <<"Lorem ipsum">>}, 172 | #li{body= <<"dolor sit amet">>} 173 | ]}, 174 | #button{class=[btn, "btn-large"]++BtnClass, body= <<"buy it">>, postback={product, integer_to_list(P#product.id)}} 175 | ]} 176 | ]} ]} }, 177 | element_link:render_element(L); 178 | 179 | render_element(#feed_entry{entry=E, id=Id}) -> 180 | {{Y, Mm, D}, {_H, _M, _S}} = E#entry.created, 181 | 182 | Li = #li{class=[thumbnail, span12, clearfix, "product-review"], body=[ 183 | #panel{class=[span3, "review-author"], body=[ 184 | #link{class=[avatar], body=#image{class=["img-circle"], data_fields=[{<<"data-src">>, <<"holder.js/100x100">>}]}}, 185 | #p{body=[ 186 | #link{url="#", body=[ #i{class=["icon-user"]}, #span{body =E#entry.from}]}, 187 | #span{class=["review-date"],body=[#i{class=["icon-calendar"]}, io_lib:format("~p/~p/~p",[Mm, D, Y]) ]}, 188 | #link{url="#",body=[ #i{class=["icon-thumbs-up"]}, #span{class=["badge badge-info"], body= <<"3">>} ]}, 189 | #link{url="#",body=[ #i{class=["icon-comment"]}, #span{class=["badge badge-info"], body= <<"10">>} ]}, 190 | #link{url="#",body=[ #i{class=["icon-share"]}, #span{class=["badge badge-info"], body= <<"5">>} ]} 191 | ]} 192 | ]}, 193 | 194 | #panel{class=[span3], body=[#feed_media{media=M} || M <- E#entry.media]}, 195 | 196 | #panel{class=[span6], body=[ 197 | #h4{body = title()}, 198 | description(wf:temp_id(), E#entry.description) 199 | ]}, 200 | 201 | #list{class=[comments],body=list_comments(Id)} 202 | ]}, 203 | element_li:render_element(Li); 204 | render_element(_R = #feed_comment{comment=C}) -> 205 | #li{body=[ 206 | #panel{body=#link{body= <<"comment">>}}, 207 | #p{body=C#comment.content}, 208 | #panel{class=[media], body=list_medias(C)} 209 | ]}; 210 | render_element(FeedMedia = #feed_media{media = Media}) -> 211 | case Media#media.type of 212 | image -> image_media(FeedMedia); 213 | _ -> image_media(FeedMedia) 214 | end. 215 | 216 | list_comments(_Eid)-> 217 | Comments =[], 218 | [#feed_comment{comment=C}|| C <- Comments]. 219 | 220 | title()-> <<"Review title">>. 221 | 222 | description(Id, Description) -> [ 223 | #panel{id="description"++Id, body=[ 224 | #panel{class=[collapse, in], body= <<"Description head">>}, 225 | #panel{id=Id, class=collapse, body= Description} 226 | ]}, 227 | #button{class=[btn, "btn-link"], data_fields=[ 228 | {<<"data-toggle">>, <<"collapse">>}, 229 | {<<"data-target">>, list_to_binary("#"++Id) }, 230 | {<<"data-parent">>, list_to_binary("#description"++Id)}], body= <<"Read...">>}]. 231 | 232 | list_medias(C)-> 233 | {Cid, Fid} = C#comment.id, 234 | [#feed_media{media=M, target=wf:temp_id(), fid = Fid, cid = Cid} || M <- C#comment.media]. 235 | 236 | image_media(_Media)-> element_image:render_element(#image{image= "/static/img/item-bg.png"}). 237 | 238 | event(init) -> []; 239 | event({counter,C}) -> wf:update(onlinenumber,wf:to_list(C)); 240 | event(Event) -> error_logger:info_msg("Page event: ~p", [Event]), []. 241 | 242 | api_event(Name,Tag,Term) -> error_logger:info_msg("Name ~p, Tag ~p, Term ~p",[Name,Tag,Term]), event(change_me). 243 | 244 | control_event(Id, Tag) -> 245 | error_logger:info_msg("Tinymce editor control event ~p: ~p", [Id, Tag]), 246 | Ed = wf:q(mcecontent), 247 | error_logger:info_msg("Data: ~p", [Ed]), 248 | ok. 249 | 250 | entries()-> [ 251 | #entry{ 252 | id={1,1}, 253 | description= <<"The award-winning developer Crytek is back with Crysis 3, the first blockbuster shooter of 2013!">>, 254 | from= <<"Pupkin">>, 255 | created = calendar:local_time(), 256 | type= {product, normal}, 257 | media= [ 258 | #media{ 259 | id=1, 260 | type = image, 261 | title= <<"Media1">>, 262 | html= <<"Media1">>, 263 | url = <<"/static/img/p1.jpg">>, 264 | thumbnail_url = <<"/static/img/p1.jpg">> }] }]. 265 | 266 | product() -> product(1,1). 267 | product(Id, Pic)-> 268 | #product{ 269 | id=Id, 270 | title = <<"Crysis 3">>, 271 | categories= [1], 272 | brief= <<"

Crytek is back with Crysis 3

The award-winning developer Crytek is back with Crysis 3, the first blockbuster shooter of 2013!Return to the fight as Prophet, the Nanosuit soldier on a quest to rediscover his humanity. Adapt on the fly with the stealth and armor abilities of your unique Nanosuit as you battle through the seven wonders of New York’s Liberty Dome. Unleash the firepower of your all-new, Predator bow and alien weaponry to hunt both human and alien enemies. Crysis 3 is the ultimate sandbox shooter, realized in the stunning visuals only Crytek and the latest version of CryENGINE can deliver. Available now on Xbox 360, PlayStation 3, and PC">>, 273 | cover = "/static/img/crysis3-bg" ++ integer_to_list(Pic)++".png", 274 | publish_start_date = calendar:local_time(), 275 | publish_end_date = calendar:local_time(), 276 | price=22.95}. 277 | -------------------------------------------------------------------------------- /apps/web/priv/static/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | !function($){"use strict";var Tooltip=function(element,options){this.init("tooltip",element,options)};Tooltip.prototype={constructor:Tooltip,init:function(type,element,options){var eventIn,eventOut,triggers,trigger,i;this.type=type;this.$element=$(element);this.options=this.getOptions(options);this.enabled=true;triggers=this.options.trigger.split(" ");for(i=triggers.length;i--;){trigger=triggers[i];if(trigger=="click"){this.$element.on("click."+this.type,this.options.selector,$.proxy(this.toggle,this))}else if(trigger!="manual"){eventIn=trigger=="hover"?"mouseenter":"focus";eventOut=trigger=="hover"?"mouseleave":"blur";this.$element.on(eventIn+"."+this.type,this.options.selector,$.proxy(this.enter,this));this.$element.on(eventOut+"."+this.type,this.options.selector,$.proxy(this.leave,this))}}this.options.selector?this._options=$.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(options){options=$.extend({},$.fn[this.type].defaults,this.$element.data(),options);if(options.delay&&typeof options.delay=="number"){options.delay={show:options.delay,hide:options.delay}}return options},enter:function(e){var defaults=$.fn[this.type].defaults,options={},self;this._options&&$.each(this._options,function(key,value){if(defaults[key]!=value)options[key]=value},this);self=$(e.currentTarget)[this.type](options).data(this.type);if(!self.options.delay||!self.options.delay.show)return self.show();clearTimeout(this.timeout);self.hoverState="in";this.timeout=setTimeout(function(){if(self.hoverState=="in")self.show()},self.options.delay.show)},leave:function(e){var self=$(e.currentTarget)[this.type](this._options).data(this.type);if(this.timeout)clearTimeout(this.timeout);if(!self.options.delay||!self.options.delay.hide)return self.hide();self.hoverState="out";this.timeout=setTimeout(function(){if(self.hoverState=="out")self.hide()},self.options.delay.hide)},show:function(){var $tip,pos,actualWidth,actualHeight,placement,tp,e=$.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(e);if(e.isDefaultPrevented())return;$tip=this.tip();this.setContent();if(this.options.animation){$tip.addClass("fade")}placement=typeof this.options.placement=="function"?this.options.placement.call(this,$tip[0],this.$element[0]):this.options.placement;$tip.detach().css({top:0,left:0,display:"block"});this.options.container?$tip.appendTo(this.options.container):$tip.insertAfter(this.$element);pos=this.getPosition();actualWidth=$tip[0].offsetWidth;actualHeight=$tip[0].offsetHeight;switch(placement){case"bottom":tp={top:pos.top+pos.height,left:pos.left+pos.width/2-actualWidth/2};break;case"top":tp={top:pos.top-actualHeight,left:pos.left+pos.width/2-actualWidth/2};break;case"left":tp={top:pos.top+pos.height/2-actualHeight/2,left:pos.left-actualWidth};break;case"right":tp={top:pos.top+pos.height/2-actualHeight/2,left:pos.left+pos.width};break}this.applyPlacement(tp,placement);this.$element.trigger("shown")}},applyPlacement:function(offset,placement){var $tip=this.tip(),width=$tip[0].offsetWidth,height=$tip[0].offsetHeight,actualWidth,actualHeight,delta,replace;$tip.offset(offset).addClass(placement).addClass("in");actualWidth=$tip[0].offsetWidth;actualHeight=$tip[0].offsetHeight;if(placement=="top"&&actualHeight!=height){offset.top=offset.top+height-actualHeight;replace=true}if(placement=="bottom"||placement=="top"){delta=0;if(offset.left<0){delta=offset.left*-2;offset.left=0;$tip.offset(offset);actualWidth=$tip[0].offsetWidth;actualHeight=$tip[0].offsetHeight}this.replaceArrow(delta-width+actualWidth,actualWidth,"left")}else{this.replaceArrow(actualHeight-height,actualHeight,"top")}if(replace)$tip.offset(offset)},replaceArrow:function(delta,dimension,position){this.arrow().css(position,delta?50*(1-delta/dimension)+"%":"")},setContent:function(){var $tip=this.tip(),title=this.getTitle();$tip.find(".tooltip-inner")[this.options.html?"html":"text"](title);$tip.removeClass("fade in top bottom left right")},hide:function(){var that=this,$tip=this.tip(),e=$.Event("hide");this.$element.trigger(e);if(e.isDefaultPrevented())return;$tip.removeClass("in");function removeWithAnimation(){var timeout=setTimeout(function(){$tip.off($.support.transition.end).detach()},500);$tip.one($.support.transition.end,function(){clearTimeout(timeout);$tip.detach()})}$.support.transition&&this.$tip.hasClass("fade")?removeWithAnimation():$tip.detach();this.$element.trigger("hidden");return this},fixTitle:function(){var $e=this.$element;if($e.attr("title")||typeof $e.attr("data-original-title")!="string"){$e.attr("data-original-title",$e.attr("title")||"").attr("title","")}},hasContent:function(){return this.getTitle()},getPosition:function(){var el=this.$element[0];return $.extend({},typeof el.getBoundingClientRect=="function"?el.getBoundingClientRect():{width:el.offsetWidth,height:el.offsetHeight},this.$element.offset())},getTitle:function(){var title,$e=this.$element,o=this.options;title=$e.attr("data-original-title")||(typeof o.title=="function"?o.title.call($e[0]):o.title);return title},tip:function(){return this.$tip=this.$tip||$(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},validate:function(){if(!this.$element[0].parentNode){this.hide();this.$element=null;this.options=null}},enable:function(){this.enabled=true},disable:function(){this.enabled=false},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(e){var self=e?$(e.currentTarget)[this.type](this._options).data(this.type):this;self.tip().hasClass("in")?self.hide():self.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var old=$.fn.tooltip;$.fn.tooltip=function(option){return this.each(function(){var $this=$(this),data=$this.data("tooltip"),options=typeof option=="object"&&option;if(!data)$this.data("tooltip",data=new Tooltip(this,options));if(typeof option=="string")data[option]()})};$.fn.tooltip.Constructor=Tooltip;$.fn.tooltip.defaults={animation:true,placement:"top",selector:false,template:'
',trigger:"hover focus",title:"",delay:0,html:false,container:false};$.fn.tooltip.noConflict=function(){$.fn.tooltip=old;return this}}(window.jQuery);!function($){"use strict";var Typeahead=function(element,options){this.$element=$(element);this.options=$.extend({},$.fn.typeahead.defaults,options);this.matcher=this.options.matcher||this.matcher;this.sorter=this.options.sorter||this.sorter;this.highlighter=this.options.highlighter||this.highlighter;this.updater=this.options.updater||this.updater;this.source=this.options.source;this.$menu=$(this.options.menu);this.shown=false;this.listen()};Typeahead.prototype={constructor:Typeahead,select:function(){var val=this.$menu.find(".active").attr("data-value");this.$element.val(this.updater(val)).change();return this.hide()},updater:function(item){return item},show:function(){var pos=$.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});this.$menu.insertAfter(this.$element).css({top:pos.top+pos.height,left:pos.left}).show();this.shown=true;return this},hide:function(){this.$menu.hide();this.shown=false;return this},lookup:function(event){var items;this.query=this.$element.val();if(!this.query||this.query.length"+match+""})},render:function(items){var that=this;items=$(items).map(function(i,item){i=$(that.options.item).attr("data-value",item);i.find("a").html(that.highlighter(item));return i[0]});items.first().addClass("active");this.$menu.html(items);return this},next:function(event){var active=this.$menu.find(".active").removeClass("active"),next=active.next();if(!next.length){next=$(this.$menu.find("li")[0])}next.addClass("active")},prev:function(event){var active=this.$menu.find(".active").removeClass("active"),prev=active.prev();if(!prev.length){prev=this.$menu.find("li").last()}prev.addClass("active")},listen:function(){this.$element.on("focus",$.proxy(this.focus,this)).on("blur",$.proxy(this.blur,this)).on("keypress",$.proxy(this.keypress,this)).on("keyup",$.proxy(this.keyup,this));if(this.eventSupported("keydown")){this.$element.on("keydown",$.proxy(this.keydown,this))}this.$menu.on("click",$.proxy(this.click,this)).on("mouseenter","li",$.proxy(this.mouseenter,this)).on("mouseleave","li",$.proxy(this.mouseleave,this))},eventSupported:function(eventName){var isSupported=eventName in this.$element;if(!isSupported){this.$element.setAttribute(eventName,"return;");isSupported=typeof this.$element[eventName]==="function"}return isSupported},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault();this.prev();break;case 40:e.preventDefault();this.next();break}e.stopPropagation()},keydown:function(e){this.suppressKeyPressRepeat=~$.inArray(e.keyCode,[40,38,9,13,27]);this.move(e)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation();e.preventDefault()},focus:function(e){this.focused=true},blur:function(e){this.focused=false;if(!this.mousedover&&this.shown)this.hide()},click:function(e){e.stopPropagation();e.preventDefault();this.select();this.$element.focus()},mouseenter:function(e){this.mousedover=true;this.$menu.find(".active").removeClass("active");$(e.currentTarget).addClass("active")},mouseleave:function(e){this.mousedover=false;if(!this.focused&&this.shown)this.hide()}};var old=$.fn.typeahead;$.fn.typeahead=function(option){return this.each(function(){var $this=$(this),data=$this.data("typeahead"),options=typeof option=="object"&&option;if(!data)$this.data("typeahead",data=new Typeahead(this,options));if(typeof option=="string")data[option]()})};$.fn.typeahead.defaults={source:[],items:8,menu:'',item:'
  • ',minLength:1};$.fn.typeahead.Constructor=Typeahead;$.fn.typeahead.noConflict=function(){$.fn.typeahead=old;return this};$(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(e){var $this=$(this);if($this.data("typeahead"))return;$this.typeahead($this.data())})}(window.jQuery);!function($){"use strict";var Modal=function(element,options){this.options=options;this.$element=$(element).delegate('[data-dismiss="modal"]',"click.dismiss.modal",$.proxy(this.hide,this));this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};Modal.prototype={constructor:Modal,toggle:function(){return this[!this.isShown?"show":"hide"]()},show:function(){var that=this,e=$.Event("show");this.$element.trigger(e);if(this.isShown||e.isDefaultPrevented())return;this.isShown=true;this.escape();this.backdrop(function(){var transition=$.support.transition&&that.$element.hasClass("fade");if(!that.$element.parent().length){that.$element.appendTo(document.body)}that.$element.show();if(transition){that.$element[0].offsetWidth}that.$element.addClass("in").attr("aria-hidden",false);that.enforceFocus();transition?that.$element.one($.support.transition.end,function(){that.$element.focus().trigger("shown")}):that.$element.focus().trigger("shown")})},hide:function(e){e&&e.preventDefault();var that=this;e=$.Event("hide");this.$element.trigger(e);if(!this.isShown||e.isDefaultPrevented())return;this.isShown=false;this.escape();$(document).off("focusin.modal");this.$element.removeClass("in").attr("aria-hidden",true);$.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal()},enforceFocus:function(){var that=this;$(document).on("focusin.modal",function(e){if(that.$element[0]!==e.target&&!that.$element.has(e.target).length){that.$element.focus()}})},escape:function(){var that=this;if(this.isShown&&this.options.keyboard){this.$element.on("keyup.dismiss.modal",function(e){e.which==27&&that.hide()})}else if(!this.isShown){this.$element.off("keyup.dismiss.modal")}},hideWithTransition:function(){var that=this,timeout=setTimeout(function(){that.$element.off($.support.transition.end);that.hideModal()},500);this.$element.one($.support.transition.end,function(){clearTimeout(timeout);that.hideModal()})},hideModal:function(){var that=this;this.$element.hide();this.backdrop(function(){that.removeBackdrop();that.$element.trigger("hidden")})},removeBackdrop:function(){this.$backdrop&&this.$backdrop.remove();this.$backdrop=null},backdrop:function(callback){var that=this,animate=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var doAnimate=$.support.transition&&animate;this.$backdrop=$('