├── apps ├── cb_admin │ ├── log │ │ └── .gitignore │ ├── ebin │ │ └── .gitignore │ ├── rebar │ ├── priv │ │ ├── lang │ │ │ └── .gitignore │ │ ├── static │ │ │ ├── favicon.ico │ │ │ ├── chicago-boss.png │ │ │ ├── news.js │ │ │ ├── lang.js │ │ │ └── stylesheets │ │ │ │ ├── application.css │ │ │ │ └── application_orig.css │ │ ├── cb_admin.routes │ │ ├── init │ │ │ └── cb_admin_01_news.erl │ │ └── rebar │ │ │ └── boss_plugin.erl │ ├── src │ │ ├── test │ │ │ ├── eunit │ │ │ │ └── .gitignore │ │ │ └── functional │ │ │ │ └── cb_admin_test.erl │ │ ├── view │ │ │ ├── lang │ │ │ │ ├── shared │ │ │ │ │ ├── _lang_right_submenu.html │ │ │ │ │ └── _lang_left_submenu.html │ │ │ │ ├── delete.html │ │ │ │ ├── big_red_button.html │ │ │ │ ├── create.html │ │ │ │ └── show.html │ │ │ ├── admin │ │ │ │ ├── access_denied.html │ │ │ │ ├── layouts │ │ │ │ │ ├── shared │ │ │ │ │ │ ├── _alerts.html │ │ │ │ │ │ ├── _main_menu.html │ │ │ │ │ │ └── _sidebar.html │ │ │ │ │ └── admin.html │ │ │ │ ├── splash.html │ │ │ │ └── index.html │ │ │ ├── model │ │ │ │ ├── delete.html │ │ │ │ ├── upload.html │ │ │ │ ├── create.html │ │ │ │ ├── edit.html │ │ │ │ ├── show.html │ │ │ │ └── model.html │ │ │ ├── upgrade │ │ │ │ └── upgrade.html │ │ │ └── routes │ │ │ │ └── index.html │ │ ├── mail │ │ │ ├── cb_admin_incoming_mail_controller.erl │ │ │ └── cb_admin_outgoing_mail_controller.erl │ │ ├── cb_admin.app.src │ │ ├── controller │ │ │ ├── cb_admin_routes_controller.erl │ │ │ ├── cb_admin_upgrade_controller.erl │ │ │ ├── cb_admin_admin_controller.erl │ │ │ ├── cb_admin_lang_controller.erl │ │ │ └── cb_admin_model_controller.erl │ │ └── lib │ │ │ ├── cb_admin_lib.erl │ │ │ └── cb_admin_model_lib.erl │ ├── .gitignore │ ├── rebar.cmd │ ├── start-server.bat │ ├── init-dev.sh │ ├── rebar.config │ ├── boss.config │ ├── README.md │ ├── LICENSE │ └── init.sh └── draw │ ├── rebar │ ├── priv │ ├── static │ │ ├── favicon.ico │ │ ├── chicago-boss.png │ │ ├── style.css │ │ └── drawings.js │ ├── draw.routes │ ├── init │ │ └── draw_01_news.erl │ └── rebar │ │ └── boss_plugin.erl │ ├── rebar.cmd │ ├── src │ ├── controller │ │ └── draw_draw_controller.erl │ ├── mail │ │ ├── draw_incoming_mail_controller.erl │ │ └── draw_outgoing_mail_controller.erl │ ├── draw.app.src │ ├── view │ │ ├── lib │ │ │ ├── filter_modules │ │ │ │ └── draw_custom_filters.erl │ │ │ ├── tag_modules │ │ │ │ └── draw_custom_tags.erl │ │ │ └── README │ │ └── draw │ │ │ ├── index.html │ │ │ └── test.html │ └── websocket │ │ ├── draw_websocket_test_websocket.erl │ │ └── draw_draw_protocol_websocket.erl │ ├── start-server.bat │ ├── init-dev.sh │ ├── rebar.config │ ├── Makefile │ ├── README.md │ ├── init.sh │ └── boss.config ├── rebar ├── rebar.cmd ├── .gitignore ├── rel_vsn.sh ├── fix_vsn.sh ├── rebar.config ├── rel ├── vars.config ├── files │ ├── vm.args │ ├── erl │ ├── start_erl.cmd │ ├── install_upgrade.escript │ ├── draw.cmd │ ├── sys.config │ ├── nodetool │ └── draw └── reltool.config ├── Makefile └── README.md /apps/cb_admin/log/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/cb_admin/ebin/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/cb_admin/rebar: -------------------------------------------------------------------------------- 1 | ../../rebar -------------------------------------------------------------------------------- /apps/cb_admin/priv/lang/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/cb_admin/src/test/eunit/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihawk/draw/HEAD/rebar -------------------------------------------------------------------------------- /apps/draw/rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihawk/draw/HEAD/apps/draw/rebar -------------------------------------------------------------------------------- /apps/cb_admin/.gitignore: -------------------------------------------------------------------------------- 1 | ebin/*.beam 2 | ebin/*.app 3 | log/*.log 4 | erl_crash.dump 5 | -------------------------------------------------------------------------------- /rebar.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | set rebarscript=%~f0 4 | escript.exe "%rebarscript:.cmd=%" %* 5 | -------------------------------------------------------------------------------- /apps/draw/priv/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihawk/draw/HEAD/apps/draw/priv/static/favicon.ico -------------------------------------------------------------------------------- /apps/draw/rebar.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | set rebarscript=%~f0 4 | escript.exe "%rebarscript:.cmd=%" %* 5 | -------------------------------------------------------------------------------- /apps/cb_admin/rebar.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | set rebarscript=%~f0 4 | escript.exe "%rebarscript:.cmd=%" %* 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ebin/*.beam 2 | ebin/boss.app 3 | log/* 4 | deps 5 | erl_crash.dump 6 | *~ 7 | #*# 8 | .*# 9 | *# 10 | -------------------------------------------------------------------------------- /apps/cb_admin/priv/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihawk/draw/HEAD/apps/cb_admin/priv/static/favicon.ico -------------------------------------------------------------------------------- /apps/draw/priv/static/chicago-boss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihawk/draw/HEAD/apps/draw/priv/static/chicago-boss.png -------------------------------------------------------------------------------- /apps/cb_admin/priv/static/chicago-boss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihawk/draw/HEAD/apps/cb_admin/priv/static/chicago-boss.png -------------------------------------------------------------------------------- /rel_vsn.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cat rel/reltool.config | grep "{rel" | grep draw | awk -F',' '{print $3}' | sed 's/ //g' | sed 's/"//g' 4 | -------------------------------------------------------------------------------- /apps/draw/src/controller/draw_draw_controller.erl: -------------------------------------------------------------------------------- 1 | -module(draw_draw_controller, [Req]). 2 | -compile(export_all). 3 | 4 | index('GET', []) -> 5 | ok. 6 | 7 | -------------------------------------------------------------------------------- /apps/draw/start-server.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | FOR /F "tokens=*" %%i in ('"rebar.cmd boss c=start_dev_cmd ^| findstr werl"') do set myvar=%%i 3 | START "Erlang Window" %myvar% 4 | -------------------------------------------------------------------------------- /apps/cb_admin/start-server.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | FOR /F "tokens=*" %%i in ('"rebar.cmd boss c=start_dev_cmd ^| findstr werl"') do set myvar=%%i 3 | START "Erlang Window" %myvar% 4 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/lang/shared/_lang_right_submenu.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/draw/init-dev.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # 3 | # Chicago Boss Dev Init System 4 | # easy start dev server (most common task) 5 | 6 | cd `dirname $0` 7 | 8 | ./init.sh start-dev 9 | -------------------------------------------------------------------------------- /apps/cb_admin/init-dev.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # 3 | # Chicago Boss Dev Init System 4 | # easy start dev server (most common task) 5 | 6 | cd `dirname $0` 7 | 8 | ./init.sh start-dev 9 | -------------------------------------------------------------------------------- /apps/cb_admin/priv/cb_admin.routes: -------------------------------------------------------------------------------- 1 | %% The controller/action pair to be used on the home page. 2 | %% Format: [{controller, "Controller"}, {action, "Action"}] 3 | 4 | {"/", [{controller, "admin"}, {action, "index"}]}. 5 | -------------------------------------------------------------------------------- /apps/draw/src/mail/draw_incoming_mail_controller.erl: -------------------------------------------------------------------------------- 1 | -module(draw_incoming_mail_controller). 2 | -compile(export_all). 3 | 4 | authorize_(User, DomainName, IPAddress) -> 5 | true. 6 | 7 | % post(FromAddress, Message) -> 8 | % ok. 9 | -------------------------------------------------------------------------------- /fix_vsn.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | APP_DIR=../apps 4 | 5 | for i in $(ls ${APP_DIR}) 6 | do 7 | F=${APP_DIR}/$i/ebin/$i.app 8 | cat ${F} | sed 's/\/bin\/sh: 1: \(.*\): not found/\1/' > ${F}.tmp 9 | mv ${F}.tmp ${F} 10 | done 11 | -------------------------------------------------------------------------------- /apps/cb_admin/src/mail/cb_admin_incoming_mail_controller.erl: -------------------------------------------------------------------------------- 1 | -module(cb_admin_incoming_mail_controller). 2 | -compile(export_all). 3 | 4 | authorize_(User, DomainName, IPAddress) -> 5 | true. 6 | 7 | % post(FromAddress, Message) -> 8 | % ok. 9 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/admin/access_denied.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Access denied 4 | 5 | 6 |

Scram!

7 |

(Access denied)

8 | 9 | 10 | -------------------------------------------------------------------------------- /apps/cb_admin/src/cb_admin.app.src: -------------------------------------------------------------------------------- 1 | {application, cb_admin, [ 2 | {description, "Chicago Boss Admin Interface"}, 3 | {vsn, "0.7.1"}, 4 | {modules, []}, 5 | {registered, []}, 6 | {applications, [kernel, stdlib]}, 7 | {env, []} 8 | ]}. 9 | -------------------------------------------------------------------------------- /apps/draw/src/draw.app.src: -------------------------------------------------------------------------------- 1 | {application, draw, [ 2 | {description, "My Awesome Web Framework"}, 3 | {vsn, "0.0.2"}, 4 | {modules, []}, 5 | {registered, []}, 6 | {applications, [kernel, stdlib, crypto, boss]}, 7 | {env, []} 8 | ]}. 9 | -------------------------------------------------------------------------------- /apps/draw/src/view/lib/filter_modules/draw_custom_filters.erl: -------------------------------------------------------------------------------- 1 | -module(draw_custom_filters). 2 | -compile(export_all). 3 | 4 | % put custom filters in here, e.g. 5 | % 6 | % my_reverse(Value) -> 7 | % lists:reverse(binary_to_list(Value)). 8 | % 9 | % "foo"|my_reverse => "foo" 10 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/admin/layouts/shared/_alerts.html: -------------------------------------------------------------------------------- 1 | {% if boss_flash %} 2 | {% for flash in boss_flash %} 3 |
4 | {{ flash.title }} 5 | {% if flash.message %} 6 |
{{ flash.message }} 7 | {% endif %} 8 |
9 | {% endfor %} 10 | {% endif %} 11 | -------------------------------------------------------------------------------- /apps/draw/rebar.config: -------------------------------------------------------------------------------- 1 | {deps, [ 2 | {boss, ".*", {git, "git://github.com/ChicagoBoss/ChicagoBoss.git", {tag, "v0.8.12"}}} 3 | ]}. 4 | {deps_dir, ["../../deps"]}. 5 | {plugin_dir, ["priv/rebar"]}. 6 | {plugins, [boss_plugin]}. 7 | {eunit_compile_opts, [{src_dirs, ["src/test"]}]}. 8 | {lib_dirs, ["../../deps/elixir/lib"]}. 9 | -------------------------------------------------------------------------------- /apps/cb_admin/rebar.config: -------------------------------------------------------------------------------- 1 | {deps, [ 2 | {boss, ".*", {git, "git://github.com/ChicagoBoss/ChicagoBoss.git", {tag, "v0.8.12"}}} 3 | ]}. 4 | {deps_dir, ["../../deps"]}. 5 | {plugin_dir, ["priv/rebar"]}. 6 | {plugins, [boss_plugin]}. 7 | {eunit_compile_opts, [{src_dirs, ["src/test"]}]}. 8 | {lib_dirs, ["../../deps/elixir/lib"]}. 9 | -------------------------------------------------------------------------------- /apps/draw/Makefile: -------------------------------------------------------------------------------- 1 | REBAR = $(shell pwd)/rebar 2 | 3 | .PHONY: 4 | 5 | all: compile 6 | 7 | compile: 8 | $(REBAR) boss c=compile 9 | 10 | clean: 11 | $(REBAR) clean 12 | 13 | start: 14 | ./init-dev.sh 15 | 16 | 17 | draw: all 18 | 19 | 20 | 21 | ### 22 | ### Docs 23 | ### 24 | docs: 25 | @$(REBAR) skip_deps=true doc 26 | 27 | -------------------------------------------------------------------------------- /apps/draw/src/view/lib/tag_modules/draw_custom_tags.erl: -------------------------------------------------------------------------------- 1 | -module(draw_custom_tags). 2 | -compile(export_all). 3 | 4 | % put custom tags in here, e.g. 5 | % 6 | % reverse(Variables, Options) -> 7 | % lists:reverse(binary_to_list(proplists:get_value(string, Variables))). 8 | % 9 | % {% reverse string="hello" %} => "olleh" 10 | % 11 | % Variables are the passed-in vars in your template 12 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {sub_dirs, [ 2 | %"apps/cb_admin", 3 | %"apps/draw", 4 | "rel" 5 | ]}. 6 | 7 | {erl_opts, [ 8 | debug_info 9 | %,fail_on_warning 10 | ]}. 11 | 12 | {deps, [ 13 | {boss, ".*", {git, "git://github.com/ChicagoBoss/ChicagoBoss.git", {tag, "v0.8.12"}}} 14 | %, PUT HERE YOUR DEPS 15 | ]}. 16 | 17 | {require_otp_vsn, "R15B03|R16B"}. 18 | 19 | 20 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/model/delete.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/layouts/admin.html" %} 2 | {% block title %}Delete {{ record.id }}?{% endblock %} 3 | {% block body %} 4 |
5 |

Really delete {{ record.id }}?

6 |

This action cannot be undone

7 |
8 | 9 |
10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /apps/draw/src/mail/draw_outgoing_mail_controller.erl: -------------------------------------------------------------------------------- 1 | -module(draw_outgoing_mail_controller). 2 | -compile(export_all). 3 | 4 | %% See http://www.chicagoboss.org/api-mail-controller.html for what should go in here 5 | 6 | test_message(FromAddress, ToAddress, Subject) -> 7 | Headers = [ 8 | {"Subject", Subject}, 9 | {"To", ToAddress}, 10 | {"From", FromAddress} 11 | ], 12 | {ok, FromAddress, ToAddress, Headers, [{address, ToAddress}]}. 13 | -------------------------------------------------------------------------------- /apps/cb_admin/src/mail/cb_admin_outgoing_mail_controller.erl: -------------------------------------------------------------------------------- 1 | -module(cb_admin_outgoing_mail_controller). 2 | -compile(export_all). 3 | 4 | %% See http://www.chicagoboss.org/api-mail-controller.html for what should go in here 5 | 6 | test_message(FromAddress, ToAddress, Subject) -> 7 | Headers = [ 8 | {"Subject", Subject}, 9 | {"To", ToAddress}, 10 | {"From", FromAddress} 11 | ], 12 | {ok, FromAddress, ToAddress, Headers, [{address, ToAddress}]}. 13 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/lang/delete.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/layouts/admin.html" %} 2 | {% block title %}Delete language file for {{ this_lang }}?{% endblock %} 3 | {% block body %} 4 |
5 |

Really delete the language file for {{ this_lang }}?

6 |

This action cannot be undone

7 |
8 | 9 |
10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /apps/cb_admin/boss.config: -------------------------------------------------------------------------------- 1 | [{boss, [ 2 | {path, "../../deps/boss"}, 3 | {vm_cookie, "abc123"}, 4 | {applications, [cb_admin]}, 5 | {db_host, "localhost"}, 6 | {db_port, 1978}, 7 | {db_adapter, mock}, 8 | {log_dir, "log"}, 9 | {server, misultin}, 10 | {port, 8001}, 11 | {session_adapter, mock}, 12 | {session_key, "_boss_session"}, 13 | {session_exp_time, 525600} 14 | ]}, 15 | {cb_admin, [ 16 | {path, "../cb_admin"}, 17 | {allow_ip_blocks, ["127.0.0.1"]}, 18 | {base_url, "/admin"} 19 | ]}]. 20 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/lang/shared/_lang_left_submenu.html: -------------------------------------------------------------------------------- 1 | {% if this_application %} 2 | 7 | {% endif %} 8 | -------------------------------------------------------------------------------- /rel/vars.config: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- 2 | %% ex: ft=erlang ts=4 sw=4 et 3 | 4 | %% Platform-specific installation paths 5 | {platform_bin_dir, "./bin"}. 6 | {platform_data_dir, "./data"}. 7 | {platform_etc_dir, "./etc"}. 8 | {platform_lib_dir, "./lib"}. 9 | {platform_log_dir, "./log"}. 10 | 11 | {sasl_error_log, "{{platform_log_dir}}/sasl-error.log"}. 12 | {sasl_log_dir, "{{platform_log_dir}}/sasl"}. 13 | 14 | {node, "draw@127.0.0.1"}. 15 | {cookie, "draw_cookie"}. 16 | {crash_dump, "{{platform_log_dir}}/erl_crash.dump"}. 17 | 18 | -------------------------------------------------------------------------------- /apps/cb_admin/src/controller/cb_admin_routes_controller.erl: -------------------------------------------------------------------------------- 1 | -module(cb_admin_routes_controller, [Req, SessionID]). 2 | -compile(export_all). 3 | -default_action(index). 4 | 5 | before_(_) -> 6 | cb_admin_lib:require_ip_address(Req). 7 | 8 | index('GET', [], Authorization) -> 9 | {ok, [ {routes_section, true}, {all_routes, boss_web:get_all_routes()} ]}. 10 | 11 | reload('GET', [], Authorization) -> 12 | boss_web:reload_routes(), 13 | lists:map(fun(Node) -> 14 | rpc:call(Node, boss_web, reload_routes, []) 15 | end, erlang:nodes()), 16 | boss_flash:add(SessionID, notice, "Routes reloaded"), 17 | {redirect, [{action, "index"}]}. 18 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/admin/layouts/shared/_main_menu.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/model/upload.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/layouts/admin.html" %} 2 | {% block title %}Bulk upload ({{ type }}){% endblock %} 3 | {% block body %} 4 |

Bulk upload ({{ type }})

5 |

Back to the {{ type }} list

6 |

Choose a CSV file with these columns:

7 | 12 |
13 |     14 | 15 |
16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /rel/files/vm.args: -------------------------------------------------------------------------------- 1 | ## Name of the node 2 | -name {{node}} 3 | 4 | ## Cookie for distributed erlang 5 | -setcookie {{cookie}} 6 | 7 | ## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive 8 | ## (Disabled by default..use with caution!) 9 | ##-heart 10 | 11 | ## Enable kernel poll and a few async threads 12 | +K true 13 | +A 64 14 | 15 | ## Treat error_logger warnings as warnings 16 | +W w 17 | 18 | ## MAX processes 19 | +P 134217727 20 | ## +P 268435456 21 | 22 | ## Increase number of concurrent ports/sockets 23 | -env ERL_MAX_PORTS 4096 24 | 25 | ## Tweak GC to run more often 26 | -env ERL_FULLSWEEP_AFTER 0 27 | 28 | ## Set the location of crash dumps 29 | -env ERL_CRASH_DUMP {{crash_dump}} 30 | 31 | ## Raise the ETS table limit 32 | -env ERL_MAX_ETS_TABLES 8192 33 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/upgrade/upgrade.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/layouts/admin.html" %} 2 | {% block title %}Chicago Boss Hot Code Upgrade{% endblock %} 3 | 4 | {% block body %} 5 |

Chicago Boss Hot Code Upgrade

6 |
7 |
8 |
9 |

10 | 11 | 12 |

13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 |

21 | 22 | 23 |

24 |
25 |
26 |
27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /apps/draw/src/view/lib/README: -------------------------------------------------------------------------------- 1 | This directory contains: 2 | 3 | * tag_html/ - template files which are compiled to tags. If you have a file 4 | called "foo.html" and then call {% foo bar=1 %} from another template, the 5 | contents of "foo.html" will be evaluated with the "bar" variable set to 1. 6 | 7 | * tag_modules/ - Erlang modules that export functions to implement tags. If 8 | a module in this directory exports foo/1, then {% foo bar=1 %} will call 9 | 10 | Module:foo([{bar, 1}]) 11 | 12 | * filter_modules/ - Erlang modules that export functions to implement filters. 13 | If a module in this directory exports foo/1, then {% "Example"|foo %} will call 14 | 15 | Module:foo(<<"Example">>) 16 | 17 | If module in this directory exports foo/2, then {% "Example"|foo:42 %} will call 18 | 19 | Module:foo(<<"Example">>, 42) 20 | 21 | You can specify external tag and filter modules in the configuration via the 22 | template_tag_modules and template_filter_modules options. 23 | -------------------------------------------------------------------------------- /apps/draw/README.md: -------------------------------------------------------------------------------- 1 | Draw is a demo of the websocket protocol in ChicagoBoss 2 | ======================================================= 3 | 4 | Background 5 | ---------- 6 | 7 | `open whiteboard` is a drawing websocket javascript application, 8 | you can find more information on the given link. 9 | 10 | https://developer.mozilla.org/fr/demosdetail/open-whiteboard 11 | 12 | 13 | Quickstart 14 | ---------- 15 | 16 | Get ChicagoBoss 17 | 18 | git clone http://github.com/mihawk/ChicagoBoss.git 19 | cd ChicagoBoss 20 | make 21 | cd .. 22 | 23 | 24 | Get draw 25 | 26 | git clone http://github.com/mihawk/draw.git 27 | cd draw 28 | make 29 | make start 30 | 31 | Open `http://localhost:8001/draw` in your browser, 32 | open a second web browser on the same url, or a tab 33 | in the first browser then start to draw. :) 34 | 35 | Digging in 36 | ---------- 37 | 38 | Behind the scenes, you should look at: 39 | 40 | boss.config 41 | priv/src/websocket/draw_protocol.erl 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /apps/cb_admin/src/controller/cb_admin_upgrade_controller.erl: -------------------------------------------------------------------------------- 1 | -module(cb_admin_upgrade_controller, [Req, SessionID]). 2 | -compile(export_all). 3 | -default_action(upgrade). 4 | 5 | upgrade('GET', [], Auth) -> 6 | {ok, [ {upgrade_section, true} ]}; 7 | upgrade('POST', [], Auth) -> 8 | error_logger:info_msg("Reloading modules...~n"), 9 | boss_load:reload_all(), 10 | error_logger:info_msg("Reloading routes...~n"), 11 | boss_web:reload_routes(), 12 | error_logger:info_msg("Reloading translation...~n"), 13 | boss_web:reload_all_translations(), 14 | lists:map(fun(Node) -> 15 | error_logger:info_msg("Reloading on ~p~n", [Node]), 16 | rpc:call(Node, boss_load, reload_all, []), 17 | rpc:call(Node, boss_web, reload_routes, []), 18 | rpc:call(Node, boss_web, reload_all_translations, []) 19 | end, erlang:nodes()), 20 | {redirect, [{action, "upgrade"}]}. 21 | 22 | reread_init_scripts('POST', [], Auth) -> 23 | boss_web:reload_init_scripts(), 24 | {redirect, [{action, "upgrade"}]}. 25 | 26 | -------------------------------------------------------------------------------- /apps/draw/priv/draw.routes: -------------------------------------------------------------------------------- 1 | % Routes file. 2 | 3 | % Formats: 4 | % {"/some/route", [{controller, "Controller"}, {action, "Action"}]}. 5 | % {"/some/route", [{controller, "Controller"}, {action, "Action"}, {id, "42"}]}. 6 | % {"/(some|any)/route/(\\d+)", [{controller, '$1'}, {action, "Action"}, {id, '$2'}]}. 7 | % {"/some/route/(?\\d+)", [{controller, "Controller"}, {action, "Action"}, {id, '$route_id'}]}. 8 | % {"/some/route", [{application, some_app}, {controller, "Controller"}, {action, "Action"}, {id, "42"}]}. 9 | % 10 | % {404, [{controller, "Controller"}, {action, "Action"}]}. 11 | % {404, [{controller, "Controller"}, {action, "Action"}, {id, "42"}]}. 12 | % {404, [{application, some_app}, {controller, "Controller"}, {action, "Action"}, {id, "42"}]}. 13 | % 14 | % Note that routing across applications results in a 302 redirect. 15 | 16 | % Front page 17 | % {"/", [{controller, "world"}, {action, "hello"}]}. 18 | 19 | % 404 File Not Found handler 20 | % {404, [{controller, "world"}, {action, "lost"}]}. 21 | 22 | % 500 Internal Error handler (only invoked in production) 23 | % {500, [{controller, "world"}, {action, "calamity"}]}. 24 | -------------------------------------------------------------------------------- /apps/cb_admin/README.md: -------------------------------------------------------------------------------- 1 | cb_admin 2 | ================= 3 | 4 | This is the admin interface for Chicago Boss, which can be configured as a standalone application with CB 0.6 or later. 5 | 6 | Installation as a standalone server 7 | ----------------------------------- 8 | 9 | Check your boss.config and make sure that the path's are correct, defaults to ../ChicagoBoss (framework) and ../cb_admin (the checkout dir). 10 | 11 | Then in *nix: 12 | 13 | ./rebar compile 14 | ./init.sh start 15 | 16 | In Windows: 17 | 18 | rebar compile 19 | start-server.bat 20 | 21 | Then visit http://localhost:8001/admin 22 | 23 | Installation with an existing CB server 24 | --------------------------------------- 25 | 26 | First "./rebar compile" in *nix or "rebar compile" in Windows, and add the app config section to the boss.config of your existing CB server. 27 | 28 | Add something like this to your boss.config: 29 | 30 | [{boss, [ 31 | {applications, [cb_admin, ...]}, 32 | ... 33 | ]}, 34 | {cb_admin, [ 35 | {path, "../cb_admin"}, 36 | {allow_ip_blocks, ["127.0.0.1"]}, 37 | {base_url, "/admin"} 38 | ]}]. 39 | 40 | -------------------------------------------------------------------------------- /apps/cb_admin/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2012 Evan Miller (except where otherwise noted) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/admin/splash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |

“Boss says Hello”

8 |
9 | 10 |

If you can read this and still possess all ten fingers, Chicago Boss was set up successfully.

11 | 19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR := ./rebar 2 | FIX := ./fix_vsn.sh 3 | DIALYZER := dialyzer 4 | DIALYZER_APPS := kernel stdlib sasl inets crypto public_key ssl 5 | ERL_LIBS=deps 6 | APP := draw 7 | APPTGZ := draw-`sh ./rel_vsn.sh`.tar.gz 8 | 9 | .PHONY: deps test rel 10 | 11 | all: app 12 | 13 | cb_admin: 14 | @cd apps/cb_admin && env ERL_LIBS=$(ERL_LIBS) $(REBAR) boss c=compile 15 | 16 | draw: 17 | @cd apps/draw && env ERL_LIBS=$(ERL_LIBS) $(REBAR) boss c=compile 18 | 19 | app: deps cb_admin draw 20 | 21 | deps: $(REBAR) 22 | @$(REBAR) get-deps compile 23 | 24 | clean: $(REBAR) 25 | @$(REBAR) clean 26 | 27 | rel: app 28 | @cd rel && ../$(FIX) && ../$(REBAR) generate 29 | 30 | dist: rel 31 | @mkdir -p dist && tar -zcf dist/$(APPTGZ) -C rel $(APP) 32 | 33 | test: $(REBAR) app 34 | @$(REBAR) eunit skip_deps=true suites=$(SUITE) 35 | 36 | ct: $(REBAR) 37 | @$(REBAR) ct skip_deps=true 38 | 39 | build-plt: 40 | @$(DIALYZER) --build_plt --output_plt .draw_dialyzer.plt --apps $(DIALYZER_APPS) 41 | 42 | dialyze: 43 | @$(DIALYZER) --src src --plt .draw_dialyzer.plt -Werror_handling \ 44 | -Wrace_conditions -Wunmatched_returns # -Wunderspecs 45 | 46 | docs: $(REBAR) 47 | ./deps/edown/edown_make -config priv/edown.config -pa deps/edown/ebin 48 | 49 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/admin/index.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/layouts/admin.html" %} 2 | {% block title %}General Info{% endblock %} 3 | 4 | {% block right_submenu %} 5 | 8 | {% endblock %} 9 | 10 | {% block body %} 11 |

Welcome to the Admin Interface

12 | 13 |

System Info

14 | 20 | 21 |

Applications loaded

22 | 27 | 28 |

Application configuration Info

29 | 34 | 35 |

See the loaded routes here 36 | 37 |

Connected nodes

38 | 45 | 46 | {% endblock %} 47 | 48 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/routes/index.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/layouts/admin.html" %} 2 | {% block title %}Loaded Routes{% endblock %} 3 | 4 | {% block left_submenu %} 5 | 8 | {% endblock %} 9 | 10 | {% block right_submenu %} 11 | 14 | {% endblock %} 15 | 16 | {% block body %} 17 | 18 | {% for appname, routes in all_routes %} 19 |

Loaded Routes: file {{ appname }}.routes

20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {% for route in routes %} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {% endfor %} 41 | 42 | 43 |
Url/SpecialApplicationControllerActionParams
{{ route.url }}{{ route.application }}{{ route.controller }}{{ route.action }}{{ route.params }}
44 | {% endfor %} 45 | {% endblock %} 46 | -------------------------------------------------------------------------------- /rel/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/cb_admin/src/view/model/create.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/layouts/admin.html" %} 2 | {% block title %}New {{ type }}{% endblock %} 3 | {% block body %} 4 |

Create a new {{ type }}

5 |

Back to the {{ type }} list

6 | {% if errors %} 7 |
    8 | {% for error in errors %} 9 |
  1. {{ error }}
  2. 10 | {% endfor %} 11 |
12 | {% endif %} 13 |
14 | 15 | {% for key, val in record.attributes %} 16 | {% ifnotequal key "id" %} 17 | {% if "_time" in key %} 18 | 22 | {% endif %} 23 | {% endifnotequal %} 24 | {% endfor %} 25 |
{{ key }} 19 |   20 | {% else %} 21 |
26 |

27 |
28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /apps/cb_admin/src/controller/cb_admin_admin_controller.erl: -------------------------------------------------------------------------------- 1 | -module(cb_admin_admin_controller, [Req, SessionID]). 2 | -compile(export_all). 3 | -default_action(index). 4 | 5 | before_("splash") -> 6 | ok; 7 | before_("access_denied") -> 8 | ok; 9 | before_(_) -> 10 | cb_admin_lib:require_ip_address(Req). 11 | 12 | index('GET', [], Authorization) -> 13 | [{loaded, ModulesLoaded}, _, _, _, _, _] = application:info(), 14 | ConfigValues = [ [{Key, Value}] || {Key, Value} <- application:get_all_env()], 15 | SystemValues = [ {otp_release, erlang:system_info(system_version)}, 16 | {processors, erlang:system_info(logical_processors_online)}, 17 | {uptime, cb_admin_lib:uptime()}, 18 | {node, erlang:node()} 19 | ], 20 | {ok, [ {index_section, true}, {modules_loaded, ModulesLoaded}, 21 | {config_env, ConfigValues}, {system_env, SystemValues}, 22 | {nodes, erlang:nodes()}] }. 23 | 24 | news_api('POST', ["created", Id], Auth) -> 25 | ok = boss_news:created(Id, Req:post_params("new")), 26 | {output, "ok"}; 27 | news_api('POST', ["updated", Id], Auth) -> 28 | ok = boss_news:updated(Id, Req:post_params("old"), Req:post_params("new")), 29 | {output, "ok"}; 30 | news_api('POST', ["deleted", Id], Auth) -> 31 | ok = boss_news:deleted(Id, Req:post_params("old")), 32 | {output, "ok"}. 33 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/model/edit.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/layouts/admin.html" %} 2 | {% block title %}Edit {{ record.id }} (Chicago Boss Admin Interface){% endblock %} 3 | {% block body %} 4 |

Editing {{ record.id }}

5 |

Back to the {{ record.id }} detail page

6 | {% if errors %} 7 |
    8 | {% for error in errors %} 9 |
  1. {{ error }}
  2. 10 | {% endfor %} 11 |
12 | {% endif %} 13 |
14 | 15 | {% for key, val in record.attributes %} 16 | {% ifnotequal key "id" %} 17 | {% if "_time" in key %} 18 | 22 | {% endif %} 23 | {% endifnotequal %} 24 | {% endfor %} 25 |
{{ key }} 19 |   20 | {% else %} 21 |
26 |

27 |
28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /rel/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/cb_admin/priv/static/news.js: -------------------------------------------------------------------------------- 1 | var watch_id, base_url; 2 | function listen_for_events(timestamp) { 3 | $.ajax(base_url+"/model/events/"+timestamp, { success: function(data, code, xhr) { 4 | if (data.messages) { 5 | for (var i=0; i 0 12 | && message.data.val.split(' ').length > truncatewords) { 13 | show_val = message.data.val.split(' ').slice(0, truncatewords).join(' ') + "..."; 14 | } 15 | $("#"+elem_id).html(show_val); 16 | } 17 | } 18 | } 19 | if (data.timestamp) { 20 | listen_for_events(data.timestamp); 21 | } 22 | }, 23 | timeout: 60 * 1000 24 | }); 25 | } 26 | function heartbeat_loop() { 27 | $.ajax(base_url+"/model/heartbeat/"+watch_id, { type: "POST" }); 28 | setTimeout(heartbeat_loop, 30 * 1000); 29 | } 30 | 31 | function watch_topic(topic, timestamp) { 32 | $.ajax(base_url+"/model/watch", { 33 | type: "POST", 34 | data: { topic_string: topic }, 35 | success: function(data, code, xhr) { 36 | if (data.watch_id != undefined) { 37 | watch_id = data.watch_id; 38 | listen_for_events(timestamp); 39 | setTimeout(heartbeat_loop, 30 * 1000); 40 | } 41 | } 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /apps/cb_admin/init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # 3 | # Chicago Boss Init System 4 | # 5 | # @author: Jose Luis Gordo Romero 6 | # 7 | # ------------------------------------------------------------------- 8 | # The shell commands are automatically generated by the boss rebar 9 | # plugin/driver, all configuration params and paths are in boss.config 10 | # ------------------------------------------------------------------- 11 | 12 | cd `dirname $0` 13 | 14 | case "${1:-''}" in 15 | 'start') 16 | # Start Boss in production mode 17 | echo "starting boss in production mode..." 18 | START=$(./rebar boss c=start_cmd|grep -v "==>") 19 | $START 20 | ;; 21 | 22 | 'start-dev') 23 | # Start Boss in development mode 24 | START_DEV=$(./rebar boss c=start_dev_cmd|grep -v "==>") 25 | $START_DEV 26 | ;; 27 | 28 | 'stop') 29 | # Stop Boss daemon 30 | echo "stopping boss..." 31 | STOP=$(./rebar boss c=stop_cmd|grep -v "==>") 32 | # After hours of shell quoting problems with the erl command, 33 | # eval with the command quoted works!!! 34 | eval "$STOP" 35 | ;; 36 | 37 | 'reload') 38 | # Boss hot code reload <-- only the actual node, not the entire cluster 39 | echo "Hot code reload, (WARN: Only this node)" 40 | RELOAD=$(./rebar boss c=reload_cmd|grep -v "==>") 41 | eval "$RELOAD" 42 | ;; 43 | 44 | 'restart') 45 | # Boss complete restart 46 | echo "Restarting (stop-start) boss..." 47 | $0 stop 48 | $0 start 49 | ;; 50 | *) 51 | echo "Chicago Boss Boot System" 52 | echo "Usage: $SELF start|start-dev|stop|reload|restart" 53 | exit 1 54 | ;; 55 | esac -------------------------------------------------------------------------------- /apps/draw/init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # 3 | # Chicago Boss Init System 4 | # 5 | # @author: Jose Luis Gordo Romero 6 | # 7 | # ------------------------------------------------------------------- 8 | # The shell commands are automatically generated by the boss rebar 9 | # plugin/driver, all configuration params and paths are in boss.config 10 | # ------------------------------------------------------------------- 11 | 12 | cd `dirname $0` 13 | 14 | case "${1:-''}" in 15 | 'start') 16 | # Start Boss in production mode 17 | echo "starting boss in production mode..." 18 | START=$(./rebar boss c=start_cmd|grep -v "==>") 19 | $START 20 | ;; 21 | 22 | 'start-dev') 23 | # Start Boss in development mode 24 | START_DEV=$(./rebar boss c=start_dev_cmd|grep -v "==>") 25 | $START_DEV 26 | ;; 27 | 28 | 'stop') 29 | # Stop Boss daemon 30 | echo "stopping boss..." 31 | STOP=$(./rebar boss c=stop_cmd|grep -v "==>") 32 | # After hours of shell quoting problems with the erl command, 33 | # eval with the command quoted works!!! 34 | eval "$STOP" 35 | ;; 36 | 37 | 'reload') 38 | # Boss hot code reload <-- only the actual node, not the entire cluster 39 | echo "Hot code reload, (WARN: Only this node)" 40 | RELOAD=$(./rebar boss c=reload_cmd|grep -v "==>") 41 | eval "$RELOAD" 42 | ;; 43 | 44 | 'restart') 45 | # Boss complete restart 46 | echo "Restarting (stop-start) boss..." 47 | $0 stop 48 | $0 start 49 | ;; 50 | *) 51 | echo "Chicago Boss Boot System" 52 | echo "Usage: $SELF start|start-dev|stop|reload|restart" 53 | exit 1 54 | ;; 55 | esac 56 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/admin/layouts/shared/_sidebar.html: -------------------------------------------------------------------------------- 1 |

Community

2 | 6 | 7 |

Learn

8 | 15 | 16 |

Develop

17 | 23 | 24 |

Deploy

25 | 28 | -------------------------------------------------------------------------------- /rel/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/draw/src/view/draw/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | whiteboard 5 | 6 | 7 | 8 | 9 | 10 |
11 | 57 | 58 | 59 |
60 | 61 | 62 | -------------------------------------------------------------------------------- /apps/cb_admin/src/lib/cb_admin_lib.erl: -------------------------------------------------------------------------------- 1 | -module(cb_admin_lib). 2 | 3 | -export([push_update/3, uptime/0, mask_ipv4_address/2, require_ip_address/1]). 4 | 5 | push_update(updated, {Record, Attr, OldVal, NewVal}, Channel) -> 6 | boss_mq:push(Channel, [{ev, updated}, {data, [{id, Record:id()}, {attr, Attr}, {val, NewVal}]}]). 7 | 8 | uptime() -> 9 | {UpTime, _} = erlang:statistics(wall_clock), 10 | {D, {H, M, S}} = calendar:seconds_to_daystime(UpTime div 1000), 11 | lists:flatten(io_lib:format("~p days, ~p hours, ~p minutes and ~p seconds", [D,H,M,S])). 12 | 13 | mask_ipv4_address({I1, I2, I3, I4}, MaskInt) -> 14 | ((I1 bsl 24 + I2 bsl 16 + I3 bsl 8 + I4) band ((1 bsl 32) - (1 bsl (32 - MaskInt)))). 15 | 16 | require_ip_address(Req) -> 17 | ClientIp = case Req:header(x_forwarded_for) of 18 | undefined -> Req:peer_ip(); 19 | IP when is_tuple(IP)-> IP; 20 | IP when is_list(IP) -> list_to_tuple(lists:map(fun erlang:list_to_integer/1, string:tokens(IP, "."))) 21 | end, 22 | Authorized = lists:foldr(fun 23 | (IPBlock, false) -> 24 | case string:tokens(IPBlock, "/") of 25 | [IPAddress] -> 26 | IPAddress =:= string:join(lists:map(fun erlang:integer_to_list/1, 27 | tuple_to_list(ClientIp)), "."); 28 | [IPAddress, Mask] -> 29 | MaskInt = list_to_integer(Mask), 30 | IPAddressTuple = list_to_tuple(lists:map(fun erlang:list_to_integer/1, string:tokens(IPAddress, "."))), 31 | mask_ipv4_address(ClientIp, MaskInt) =:= mask_ipv4_address(IPAddressTuple, MaskInt) 32 | end; 33 | (_, true) -> 34 | true 35 | end, false, boss_env:get_env(cb_admin, allow_ip_blocks, 36 | ["192.168.0.0/16", "127.0.0.1", "10.0.0.0/16"])), 37 | case Authorized of 38 | true -> 39 | {ok, local}; 40 | _ -> 41 | {redirect, [{controller, "admin"}, {action, "access_denied"}]} 42 | end. 43 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/admin/layouts/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Chicago Boss Admin - {% block title %}{% endblock %} 7 | 8 | {% block meta_scripts %}{% endblock %} 9 | 10 | 11 |
12 |
13 |
14 |
15 | {% block right_submenu %}{% endblock %} 16 |
17 | {% block left_submenu %}{% endblock %} 18 |
19 | 20 | 31 |
32 | 35 |
36 | {% include "admin/layouts/shared/_alerts.html" %} 37 |
{% block contextual %}{% endblock %}
38 |
39 | {% block body %}{% endblock %} 40 |
41 |
42 |
43 |
44 | 45 | 48 | 49 | 56 |
57 |
58 | 59 | 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Draw is a demo of the websocket protocol in ChicagoBoss 2 | ======================================================= 3 | 4 | Background 5 | ---------- 6 | 7 | `open whiteboard` is a drawing websocket javascript application, 8 | you can find more information on the given link. 9 | 10 | https://developer.mozilla.org/fr/demosdetail/open-whiteboard 11 | 12 | 13 | draw come as an otp application 14 | 15 | see also [draw](http://github.com/naga-framework/draw) with naga 16 | 17 | Quickstart 18 | ---------- 19 | 20 | Get draw 21 | 22 | ```sh 23 | >git clone http://github.com/mihawk/draw.git 24 | >cd draw 25 | >make rel 26 | ``` 27 | 28 | Start Draw 29 | 30 | ```sh 31 | 32 | >cd draw 33 | >rel/draw/bin/draw start 34 | 35 | ``` 36 | or get a console to see log :) 37 | 38 | ```bash 39 | 40 | >rel/draw/bin/draw console 41 | 42 | ``` 43 | 44 | 45 | Open `http://localhost:8001/draw` in your browser, 46 | open a second web browser on the same url, or a tab 47 | in the first browser then start to draw. :) 48 | 49 | draw is shipped with cb_admin, `http://localhost:8001/admin` 50 | 51 | 52 | Digging in 53 | ---------- 54 | 55 | Behind the scenes, you should look at: 56 | 57 | [drawing.js](https://github.com/mihawk/draw/blob/master/apps/draw/priv/static/drawings.js#L11) 58 | 59 | [draw_draw_protocol_websocket.erl](https://github.com/mihawk/draw/blob/master/apps/draw/src/websocket/draw_draw_protocol_websocket.erl) 60 | 61 | Application directory canva 62 | --------------------------- 63 | 64 | 65 | ```sh 66 | 67 | . 68 | ├── apps <----- boss app folder 69 | │ ├── cb_admin 70 | │ └── draw 71 | ├── deps 72 | │ ├── boss <----- all yours deps boss ... and yours 73 | │ └── ... 74 | ├── dist 75 | │ └── draw-.tar.gz <----- your tarball 76 | ├── Makefile 77 | ├── rebar 78 | ├── rebar.config 79 | └── rel 80 | ├── files 81 | │ ├── draw 82 | │ ├── draw.cmd 83 | │ ├── erl 84 | │ ├── install_upgrade.escript 85 | │ ├── nodetool 86 | │ ├── start_erl.cmd 87 | │ ├── sys.config 88 | │ └── vm.args 89 | ├── reltool.config 90 | └── vars.config 91 | 92 | ``` 93 | -------------------------------------------------------------------------------- /apps/draw/priv/static/style.css: -------------------------------------------------------------------------------- 1 | body, ul{ 2 | margin: 0; 3 | padding: 0; 4 | } 5 | body, canvas { 6 | margin: 0; 7 | padding: 0; 8 | border: 0; 9 | font-size: 100%; 10 | font: inherit; 11 | vertical-align: baseline; 12 | } 13 | #wrapper{ 14 | margin: 0 auto; 15 | width: 100%; 16 | height: 100%; 17 | } 18 | 19 | #canvas{ 20 | cursor: crosshair; 21 | position: absolute; 22 | z-index: 2; 23 | } 24 | #remotecanvas { 25 | position: absolute; 26 | z-index: 1; 27 | } 28 | .menu ul { 29 | background: none repeat scroll 0 0 #333333; 30 | height: 2em; 31 | list-style: none outside none; 32 | padding : 0; 33 | } 34 | .menu li { 35 | float: left; 36 | padding:0; 37 | border-right: 1px solid #cccccc; 38 | } 39 | 40 | .menu label { 41 | line-height: 2em; 42 | color: #CCCCCC; 43 | text-align: center; 44 | text-decoration: none; 45 | padding: 1em; 46 | } 47 | 48 | .menu input { 49 | width:3em; 50 | } 51 | .menu li a:hover { 52 | background-color: #CCCCC4; 53 | color: #333333; 54 | cursor: pointer; 55 | } 56 | 57 | 58 | .menu li a { 59 | line-height: 2em; 60 | color: #CCCCCC; 61 | padding: 1em 1.5em; 62 | text-align: center; 63 | text-decoration: none; 64 | } 65 | .menu:after{ 66 | clear:both; 67 | } 68 | 69 | /*submenu*/ 70 | .menu li ul{ 71 | background:none; 72 | display:none; 73 | height:auto; 74 | padding:0; 75 | position:absolute; 76 | z-index:3; 77 | } 78 | 79 | .menu li ul li{ 80 | background:#333333; 81 | border: none; 82 | } 83 | .menu li:hover ul{ 84 | display:block; 85 | } 86 | .menu li li { 87 | display:block; 88 | height:2em; 89 | float:none; 90 | margin:0; 91 | padding:0; 92 | } 93 | .menu li:hover li a{ 94 | background:none; 95 | line-height: 2em; 96 | color: #CCCCCC; 97 | padding: 0 1.5em; 98 | } 99 | .menu li ul a{ 100 | display:block; 101 | height:2em; 102 | font-style:normal; 103 | margin:0; 104 | padding: 0; 105 | text-align:left; 106 | } 107 | .menu li ul a:hover, .menu li ul li:hover a{ 108 | 109 | background-color: #CCCCC4; 110 | color: #333333; 111 | border:0; 112 | text-decoration:none; 113 | } -------------------------------------------------------------------------------- /apps/draw/src/websocket/draw_websocket_test_websocket.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author chan sisowath 3 | %%% @copyright (C) 2012, mihawk 4 | %%% @doc 5 | %%% 6 | %%% 7 | %%% powered by ChicagoBoss http://www.chicagoboss.org 8 | %%% @end 9 | %%% Created : 18 Jul 2012 by mihawk 10 | %%%------------------------------------------------------------------- 11 | -module(draw_websocket_test_websocket, [Req, SessionId]). 12 | -behaviour(boss_service_handler). 13 | 14 | -record(state,{users}). 15 | 16 | %% API 17 | -export([init/0, 18 | handle_incoming/4, 19 | handle_join/3, 20 | handle_broadcast/2, 21 | handle_close/4, 22 | handle_info/2, 23 | terminate/2]). 24 | 25 | init() -> 26 | io:format("~p (~p) starting...~n", [?MODULE, self()]), 27 | %timer:send_interval(1000, ping), 28 | {ok, #state{users=dict:new()}}. 29 | 30 | handle_join(ServiceName, WebSocketId, State) -> 31 | error_logger:info_msg("~p ~p ~p", [ServiceName, WebSocketId, SessionId]), 32 | #state{users=Users} = State, 33 | {noreply, #state{users=dict:store(WebSocketId,SessionId,Users)}}. 34 | 35 | 36 | handle_close(Reason, ServiceName, WebSocketId, State) -> 37 | #state{users=Users} = State, 38 | io:format("ServiceName ~p, WebSocketId ~p, SessiondId ~p, close for Reason ~p~n", 39 | [ServiceName, WebSocketId, SessionId, Reason]), 40 | {noreply, #state{users=dict:erase(WebSocketId, Users)}}. 41 | 42 | handle_broadcast(Message, State) -> 43 | io:format("Broadcast Message ~p~n",[Message]), 44 | {noreply, State}. 45 | 46 | handle_incoming(_ServiceName, WebSocketId, Message, State) -> 47 | WebSocketId ! {text, << "That's what she said! ", Message/binary >>}, 48 | {noreply, State}. 49 | 50 | handle_info(state, State) -> 51 | #state{users=Users} = State, 52 | error_logger:info_msg("state:~p~n", [Users]), 53 | {noreply, State}; 54 | 55 | handle_info(ping, State) -> 56 | error_logger:info_msg("pong:~p~n", [now()]), 57 | {noreply, State}; 58 | 59 | handle_info(tic_tac, State) -> 60 | #state{users=Users} = State, 61 | Fun = fun(X) when is_pid(X)-> X ! {text, "tic tac"} end, 62 | All = dict:fetch_keys(Users), 63 | [Fun(E) || E <- All], 64 | {noreply, State}; 65 | 66 | handle_info(_Info, State) -> 67 | {noreply, State}. 68 | 69 | terminate(_Reason, _State) -> 70 | ok. 71 | 72 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/lang/big_red_button.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/layouts/admin.html" %} 2 | {% block title %}International Command Center: Big Red Button{% endblock %} 3 | 4 | {% block left_submenu %} 5 | {% include "lang/shared/_lang_left_submenu.html" with automatic_translator="true" %} 6 | {% endblock %} 7 | 8 | {% block right_submenu %} 9 | {% include "lang/shared/_lang_right_submenu.html" %} 10 | {% endblock %} 11 | 12 | {% block meta_scripts %} 13 | 15 | 16 | 38 | {% endblock %} 39 | 40 | {% block body %} 41 | {% if applications %} 42 |

Applications: 43 | {% for app in applications %} 44 | {% ifequal app this_application %} 45 | {{ app }} 46 | {% else %} 47 | {{ app }} 48 | {% endifequal %} 49 | {% endfor %} 50 |

51 | {% endif %} 52 |

The Big Red Button fills in all the blanks in your language files with the Google Translate API.

53 |
54 | 55 |
56 | 57 |

Language files that will be created or updated:

58 | 59 | 60 |
LanguageStatus
61 | 62 |

Language files that will be unaffected: 63 | {% for lang in languages %} 64 | {% if not lang.untranslated_strings|length %} 65 | {{ lang.code }} 66 | {% endif %} 67 | {% endfor %} 68 |

69 | 70 | {% endblock %} 71 | -------------------------------------------------------------------------------- /apps/cb_admin/src/lib/cb_admin_model_lib.erl: -------------------------------------------------------------------------------- 1 | -module(cb_admin_model_lib). 2 | -export([encode_csv_value/1, parse_csv/1]). 3 | 4 | encode_csv_value(Val) when is_binary(Val) -> 5 | encode_csv_value(binary_to_list(Val)); 6 | encode_csv_value(Val) when is_atom(Val) -> 7 | encode_csv_value(atom_to_list(Val)); 8 | encode_csv_value({_, _, _} = Val) -> 9 | encode_csv_value(erlydtl_filters:date(calendar:now_to_datetime(Val), "F d, Y H:i:s")); 10 | encode_csv_value({{_, _, _}, {_, _, _}} = Val) -> 11 | encode_csv_value(erlydtl_filters:date(Val, "F d, Y H:i:s")); 12 | encode_csv_value(Val) -> 13 | encode_csv_value(Val, []). 14 | 15 | encode_csv_value([], Acc) -> 16 | [$"|lists:reverse([$"|Acc])]; 17 | encode_csv_value([$"|T], Acc) -> 18 | encode_csv_value(T, [$", $" | Acc]); 19 | encode_csv_value([H|T], Acc) -> 20 | encode_csv_value(T, [H|Acc]). 21 | 22 | 23 | % Taken from http://blog.vmoroz.com/2011/01/csv-in-erlang.html 24 | parse_csv(Data) when is_binary(Data) -> parse_csv(binary_to_list(Data)); 25 | parse_csv(Data) -> parse(Data, [], [], []). 26 | 27 | parse([$\r|Data], Field, Fields, Lines) -> parse_r(Data, Field, Fields, Lines); 28 | parse([$\n|Data], Field, Fields, Lines) -> parse(Data, [], [], [[Field|Fields]|Lines]); 29 | parse([$,|Data], Field, Fields, Lines) -> parse(Data, [], [Field|Fields], Lines); 30 | parse([$"|Data], [], Fields, Lines) -> parse_q(Data, [], Fields, Lines); 31 | parse([C|Data], Field, Fields, Lines) -> parse(Data, [C|Field], Fields, Lines); 32 | parse([], [], [], Lines) -> lists:reverse( 33 | [lists:reverse( 34 | [lists:reverse(F) || F <- L] 35 | ) || L <- Lines] 36 | ); 37 | parse([], Field, Fields, Lines) -> parse([], [], [], [[Field|Fields]|Lines]). 38 | 39 | parse_r([$\n|_] = Data, Field, Fields, Lines) -> parse(Data, Field, Fields, Lines). 40 | 41 | parse_q([$"|Data], Field, Fields, Lines) -> parse_qq(Data, Field, Fields, Lines); 42 | parse_q([C|Data], Field, Fields, Lines) -> parse_q(Data, [C|Field], Fields, Lines). 43 | 44 | parse_qq([$"|Data], Field, Fields, Lines) -> parse_q(Data, [$"|Field], Fields, Lines); 45 | parse_qq([C|_] = Data, Field, Fields, Lines) 46 | when C == $,; C == $\r; C == $\n -> parse(Data, Field, Fields, Lines); 47 | parse_qq([], Field, Fields, Lines) -> parse([], Field, Fields, Lines). 48 | 49 | -------------------------------------------------------------------------------- /apps/draw/boss.config: -------------------------------------------------------------------------------- 1 | [{boss, [ 2 | {path, "../../deps/boss"}, 3 | {applications, [draw,cb_admin]}, 4 | {db_host, "192.168.188.81"}, 5 | {db_port, 1978}, 6 | {db_adapter, mock}, 7 | {log_dir, "log"}, 8 | {server, cowboy}, 9 | {port, 8001}, 10 | {session_adapter, mock}, 11 | {session_key, "_boss_session"}, 12 | {session_exp_time, 525600} 13 | ]}, 14 | { draw, [ 15 | {path, "../draw"}, 16 | {base_url, "/"} 17 | ]}, 18 | {cb_admin, [ 19 | {path, "../cb_admin"}, 20 | {allow_ip_blocks, ["127.0.0.1"]}, 21 | {base_url, "/admin"}, 22 | {model_modules, []}, 23 | {websocket_modules, []}, 24 | {controller_modules,[ 25 | cb_admin_admin_controller, 26 | cb_admin_incoming_mail_controller, 27 | cb_admin_lang_controller, 28 | cb_admin_model_controller, 29 | cb_admin_outgoing_mail_controller, 30 | cb_admin_routes_controller, 31 | cb_admin_upgrade_controller 32 | ]}, 33 | {view_modules,[ 34 | cb_admin_view_admin_access_denied_html, 35 | cb_admin_view_admin_index_html, 36 | cb_admin_view_admin_layouts_admin_html, 37 | cb_admin_view_admin_layouts_shared__alerts_html, 38 | cb_admin_view_admin_layouts_shared__main_menu_html, 39 | cb_admin_view_admin_layouts_shared__sidebar_html, 40 | cb_admin_view_admin_splash_html, 41 | cb_admin_view_lang_big_red_button_html, 42 | cb_admin_view_lang_create_html, 43 | cb_admin_view_lang_delete_html, 44 | cb_admin_view_lang_shared__lang_left_submenu_html, 45 | cb_admin_view_lang_shared__lang_right_submenu_html, 46 | cb_admin_view_lang_show_html, 47 | cb_admin_view_lib_tags, 48 | cb_admin_view_model_create_html, 49 | cb_admin_view_model_delete_html, 50 | cb_admin_view_model_edit_html, 51 | cb_admin_view_model_model_html, 52 | cb_admin_view_model_show_html, 53 | cb_admin_view_model_upload_html, 54 | cb_admin_view_routes_index_html, 55 | cb_admin_view_upgrade_upgrade_html 56 | ]} 57 | ]} 58 | ]. 59 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/lang/create.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/layouts/admin.html" %} 2 | {% block title %}Create a language file{% endblock %} 3 | 4 | {% block left_submenu %} 5 | {% include "lang/shared/_lang_left_submenu.html" with new_language="true" %} 6 | {% endblock %} 7 | 8 | {% block right_submenu %} 9 | {% include "lang/shared/_lang_right_submenu.html" %} 10 | {% endblock %} 11 | 12 | {% block meta_scripts %} 13 | 15 | 50 | {% endblock %} 51 | {% block body %} 52 | {% if applications %} 53 |

Applications: 54 | {% for app in applications %} 55 | {% ifequal app this_application %} 56 | {{ app }} 57 | {% else %} 58 | {{ app }} 59 | {% endifequal %} 60 | {% endfor %} 61 |

62 | {% endif %} 63 |

Create a new language

64 |
65 |
66 |
67 |

68 | 69 | 70 |

71 |
72 |
73 | 74 |
75 | {% endblock %} 76 | 77 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/model/show.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/layouts/admin.html" %} 2 | {% block title %}{{ record.id }} (Chicago Boss Admin Interface){% endblock %} 3 | {% block meta_scripts %} 4 | 5 | 6 | 21 | {% endblock %} 22 | {% block body %} 23 |

Displaying {{ record.id }} (edit)

24 |

Back to the {{ type }} list

25 | {% for key, val, datatype in attributes %} 26 | {% if not datatype == "id" %} 27 | {{ key }}
28 | {% if datatype == "datetime" %} 29 | {{ val|date:"N j, Y, P" }} 30 | {% else %} 31 | {% if datatype == "string" or datatype == "binary" %} 32 | {{ val|linebreaksbr }} 33 | {% else %} 34 | {{ val }} 35 | {% endif %} 36 | {% endif %} 37 |

38 | {% endif %} 39 | {% endfor %} 40 | 41 | {% for key, val in record.belongs_to %} 42 | {% if val %} 43 | {{ key }} 44 | 45 | 46 | {% for attr in val.attribute_names %} 47 | 48 | {% endfor %} 49 | 50 | 51 | {% for attr_name, attr_val in val.attributes %} 52 | {% ifequal "id" attr_name %} 53 | 54 | {% else %} 55 | {% if "_time" in attr_name %} 56 | 57 | {% else %} 58 | 59 | {% endif %} 60 | {% endifequal %} 61 | {% endfor %} 62 | 63 |
{{ attr }}
{{ attr_val }}{{ attr_val|date:"N j, Y, P" }}{{ attr_val|truncatewords:8 }}


64 | {% endif %} 65 | {% endfor %} 66 |
67 | 68 |
69 | {% endblock %} 70 | -------------------------------------------------------------------------------- /rel/reltool.config: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- 2 | %% ex: ft=erlang ts=4 sw=4 et 3 | {sys, [ 4 | {lib_dirs, ["../apps", "../deps"]}, 5 | {erts, [{mod_cond, derived}, {app_file, strip}]}, 6 | %{app_file, strip}, 7 | {rel, "draw", "0.3.1", 8 | [ 9 | kernel, 10 | stdlib, 11 | sasl, 12 | inets, 13 | crypto, 14 | public_key, 15 | ssl, 16 | lager, 17 | boss_db, 18 | mochicow, 19 | simple_bridge, 20 | boss, 21 | cb_admin, 22 | draw 23 | ]}, 24 | {rel, "start_clean", "", 25 | [ 26 | kernel, 27 | stdlib 28 | ]}, 29 | {boot_rel, "draw"}, 30 | {profile, embedded}, 31 | {incl_cond, derived}, 32 | {mod_cond, derived}, 33 | {excl_archive_filters, [".*"]}, %% Do not archive built libs 34 | {excl_sys_filters, ["^bin/.*", "^erts.*/bin/(dialyzer|typer)", 35 | "^erts.*/(doc|info|include|lib|man|src)"]}, 36 | {excl_app_filters, [".gitignore"]}, 37 | {app, ranch, [{mod_cond, app}, {incl_cond, include}]}, 38 | {app, mochiweb, [{mod_cond, app}, {incl_cond, include}]}, 39 | %{app, misultin, [{mod_cond, app}, {incl_cond, include}]}, 40 | {app, cowboy, [{mod_cond, app}, {incl_cond, include}]}, 41 | {app, erlydtl, [{mod_cond, app}, {incl_cond, include}]}, 42 | {app, jaderl, [{mod_cond, app}, {incl_cond, include}]}, 43 | {app, gen_smtp, [{mod_cond, app}, {incl_cond, include}]}, 44 | {app, poolboy, [{mod_cond, app}, {incl_cond, include}]}, 45 | {app, mochicow, [{mod_cond, app}, {incl_cond, include}]}, 46 | {app, simple_bridge, [{mod_cond, app}, {incl_cond, include}]}, 47 | {app, lager, [{mod_cond, app}, {incl_cond, include}]}, 48 | {app, tinymq, [{mod_cond, app}, {incl_cond, include}]}, 49 | {app, tiny_pq, [{mod_cond, app}, {incl_cond, include}]}, 50 | {app, boss_db, [{mod_cond, app}, {incl_cond, include}]}, 51 | {app, draw, [{mod_cond, app}, {incl_cond, include}]} 52 | ]}. 53 | 54 | {target_dir, "draw"}. 55 | 56 | {overlay_vars, "vars.config"}. 57 | 58 | {overlay, [ 59 | {mkdir, "data"}, 60 | {mkdir, "log/sasl"}, 61 | {mkdir, "etc"}, 62 | {template, "files/erl", "{{erts_vsn}}/bin/erl"}, 63 | {template, "files/erl", "{{erts_vsn}}/bin/erl"}, 64 | {template, "files/nodetool", "{{erts_vsn}}/bin/nodetool"}, 65 | {template, "files/draw", "bin/draw"}, 66 | {template, "files/draw.cmd", "bin/draw.cmd"}, 67 | {template, "files/start_erl.cmd", "bin/start_erl.cmd"}, 68 | {template, "files/install_upgrade.escript", "bin/install_upgrade.escript"}, 69 | {template, "files/sys.config", "releases/{{rel_vsn}}/sys.config"}, 70 | {template, "files/vm.args", "releases/{{rel_vsn}}/vm.args"} 71 | ]}. 72 | -------------------------------------------------------------------------------- /rel/files/draw.cmd: -------------------------------------------------------------------------------- 1 | @setlocal 2 | 3 | @set node_name=yunio_filemeta 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% eleases 10 | 11 | @rem Parse ERTS version and release version from start_erl.data 12 | @for /F "usebackq tokens=1,2" %%I in ("%releases_dir% tart_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% m.args 18 | @set sys_config=%releases_dir%%release_version% ys.config 19 | @set node_boot_script=%releases_dir%%release_version%%node_name% 20 | @set clean_boot_script=%releases_dir%%release_version% tart_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%rts-%erts_version%in 26 | 27 | @set service_name=%node_name%_%release_version% 28 | 29 | @set erlsrv="%erts_bin%rlsrv.exe" 30 | @set epmd="%erts_bin%pmd.exe" 31 | @set escript="%erts_bin%script.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%in tart_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%ininstall_upgrade.escript %node_name% %erlang_cookie% %2 92 | @goto :EOF 93 | 94 | :set_trim 95 | @set %1=%2 96 | @goto :EOF 97 | -------------------------------------------------------------------------------- /rel/files/sys.config: -------------------------------------------------------------------------------- 1 | [{boss, [ 2 | {path, "../../deps/boss"}, 3 | {applications, [draw,cb_admin]}, 4 | %{db_host, "192.168.188.81"}, 5 | %{db_port, 1978}, 6 | %{db_adapter, mock}, 7 | {log_dir, "log"}, 8 | {server, cowboy}, 9 | {port, 8001}, 10 | {session_adapter, mock}, 11 | {session_key, "_boss_session"}, 12 | {session_exp_time, 525600} 13 | %,{websocket_timeout, 5000} 14 | ]}, 15 | { draw, [ 16 | {path, "../draw"}, 17 | {base_url, "/"}, 18 | 19 | %%draw_custom_filters 20 | %%draw_custom_tags 21 | %%draw_view_lib_tags 22 | 23 | {model_modules, []}, 24 | {websocket_modules, [ 25 | draw_draw_protocol_websocket, 26 | draw_websocket_test_websocket 27 | ]}, 28 | {controller_modules,[ 29 | draw_draw_controller, 30 | draw_incoming_mail_controller, 31 | draw_outgoing_mail_controller 32 | ]}, 33 | {view_modules,[draw_view_draw_index_html]} 34 | ]}, 35 | {cb_admin, [ 36 | {path, "../cb_admin"}, 37 | {allow_ip_blocks, ["127.0.0.1"]}, 38 | {base_url, "/admin"}, 39 | {model_modules, []}, 40 | {websocket_modules, []}, 41 | {controller_modules,[ 42 | cb_admin_admin_controller, 43 | cb_admin_incoming_mail_controller, 44 | cb_admin_lang_controller, 45 | cb_admin_model_controller, 46 | cb_admin_outgoing_mail_controller, 47 | cb_admin_routes_controller, 48 | cb_admin_upgrade_controller]}, 49 | {view_modules,[ 50 | cb_admin_view_admin_access_denied_html, 51 | cb_admin_view_admin_index_html, 52 | cb_admin_view_admin_layouts_admin_html, 53 | cb_admin_view_admin_layouts_shared__alerts_html, 54 | cb_admin_view_admin_layouts_shared__main_menu_html, 55 | cb_admin_view_admin_layouts_shared__sidebar_html, 56 | cb_admin_view_admin_splash_html, 57 | cb_admin_view_lang_big_red_button_html, 58 | cb_admin_view_lang_create_html, 59 | cb_admin_view_lang_delete_html, 60 | cb_admin_view_lang_shared__lang_left_submenu_html, 61 | cb_admin_view_lang_shared__lang_right_submenu_html, 62 | cb_admin_view_lang_show_html, 63 | cb_admin_view_lib_tags, 64 | cb_admin_view_model_create_html, 65 | cb_admin_view_model_delete_html, 66 | cb_admin_view_model_edit_html, 67 | cb_admin_view_model_model_html, 68 | cb_admin_view_model_show_html, 69 | cb_admin_view_model_upload_html, 70 | cb_admin_view_routes_index_html, 71 | cb_admin_view_upgrade_upgrade_html 72 | ]} 73 | ]} 74 | ]. 75 | -------------------------------------------------------------------------------- /apps/cb_admin/priv/static/lang.js: -------------------------------------------------------------------------------- 1 | function initialize() { 2 | for (var lang in google.language.Languages) { 3 | var code = google.language.Languages[lang]; 4 | if (google.language.isTranslatable(code)) { 5 | var messages = new Array(); 6 | if (!untranslated_messages[code]) { 7 | for (var i=0; i 0) { 12 | for (var i=0; i b.lang ? 1 : -1 }); 20 | var table = $("#new_language_table"); 21 | for (var i=0; i'+lang.lang+' ('+lang.code.toLowerCase()+')'+''+ 24 | ''+ 25 | (all_messages.length - lang.messages.length)+ 26 | ''); 27 | } 28 | $("button").click(function() { 29 | did_alert = false; 30 | $(this).attr('disabled', true); 31 | startTrans(0, 0); 32 | }); 33 | } 34 | function startTrans(lang_index, msg_index) { 35 | if (lang_index == langs.length) { 36 | return; 37 | } 38 | var lang = langs[lang_index]; 39 | var message = lang.messages[msg_index]; 40 | google.language.translate(message.orig, "en", lang.code, function(result) { 41 | if (!result.error) { 42 | if (result.translation) { 43 | message.trans = result.translation; 44 | var numTrans = parseInt($("#"+lang.lang.toLowerCase()+"_status").text()); 45 | $("#"+lang.lang.toLowerCase()+"_status").text(numTrans + 1); 46 | if (msg_index == lang.messages.length - 1) { 47 | $.ajax({ 48 | type: "POST", 49 | url: "/admin/lang/"+lang.code+"/json", 50 | data: { 51 | messages: lang.messages 52 | }, 53 | success: function(data, textStatus, xhr) { 54 | $("#"+lang.lang.toLowerCase()+"_status").text("OK"); 55 | startTrans(lang_index+1, 0); 56 | }, 57 | error: function(xhr, textStatus, error) { 58 | alert("Error updating "+lang.lang+": "+error); 59 | } 60 | }); 61 | } else { 62 | setTimeout(function() { 63 | startTrans(lang_index, msg_index+1); 64 | }, 500); 65 | } 66 | } 67 | } else { 68 | if (!did_alert) { 69 | alert('Error contacting Google: '+result.error.message); 70 | did_alert = true; 71 | } 72 | } 73 | }); 74 | } 75 | 76 | -------------------------------------------------------------------------------- /apps/cb_admin/src/view/model/model.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/layouts/admin.html" %} 2 | {% block title %}Models data management{% endblock %} 3 | {% block meta_scripts %} 4 | 5 | 6 | 16 | {% endblock %} 17 | 18 | {% block body %} 19 |

Models data management

20 |

21 | {% for model in models %} 22 | {% ifequal model this_model %} 23 | {{ model }} 24 | {% else %} 25 | {{ model }} 26 | {% endifequal %} 27 | {% if not forloop.last %} 28 | | 29 | {% endif %} 30 | {% endfor %} 31 |

32 | {% if this_model %} 33 |

34 |

35 | 36 |     See also: Documentation for the {{ this_model }} model 37 |
38 |

39 | {% endif %} 40 | 41 | {% if attribute_names %} 42 | 43 | {% for attr in attribute_names %} 44 | 45 | {% endfor %} 46 | 47 | {% endif %} 48 | {% for record_id, record in records %} 49 | 50 | 51 | {% for key, val, datatype in record %} 52 | {% if datatype == "id" %} 53 | {% else %} 54 | {% if datatype == "datetime" %} 55 | 56 | {% else %} 57 | {% if datatype == "foreign_id" %} 58 | 59 | {% else %} 60 | {% if datatype == "string" or datatype == "binary" %} 61 | 62 | {% else %} 63 | 64 | {% endif %} 65 | {% endif %} 66 | {% endif %} 67 | {% endif %} 68 | {% endfor %} 69 | 70 | {% endfor %} 71 |
{{ attr }}
{{ record_id }}{{ val|date:"N j, Y, P" }}{{ val }}{{ val|truncatewords:8 }}{{ val }}
72 | {% if pages %} 73 | {% ifnotequal pages|length "1" %} 74 |

75 | Pages: 76 | {% for page in pages %} 77 | {% ifequal page this_page %} 78 | {{ page }} 79 | {% else %} 80 | {{ page }} 81 | {% endifequal %} 82 | {% endfor %} 83 |

84 | {% endifnotequal %} 85 | {% endif %} 86 | {% if this_model %} 87 |

Export as CSV - 88 | Bulk upload

89 | {% endif %} 90 | {% endblock %} 91 | -------------------------------------------------------------------------------- /apps/draw/src/view/draw/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Websocket client 5 | 6 | 83 | 84 | 85 | 86 | 90 | 91 | 92 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /apps/draw/src/websocket/draw_draw_protocol_websocket.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author chan sisowath 3 | %%% @copyright (C) 2012, mihawk 4 | %%% @doc 5 | %%% 6 | %%% draw_protocol is a demo to show how to 7 | %%% implement websocket service for realtime update 8 | %%% with the ChicagoBoss MVC Web Framwork, 9 | %%% draw_protocol is the backend server for 10 | %%% open whiteboard application: 11 | %%% 12 | %%% https://developer.mozilla.org/fr/demosdetail/open-whiteboard 13 | %%% 14 | %%% powered by ChicagoBoss http://www.chicagoboss.org 15 | %%% @end 16 | %%% Created : 18 Jul 2012 by mihawk 17 | %%%------------------------------------------------------------------- 18 | -module(draw_draw_protocol_websocket, [Req, SessionId]). 19 | -behaviour(boss_service_handler). 20 | 21 | -record(state,{users}). 22 | 23 | %% API 24 | -export([init/0, 25 | handle_incoming/4, 26 | handle_join/3, 27 | handle_broadcast/2, 28 | handle_close/4, 29 | handle_info/2, 30 | terminate/2]). 31 | 32 | %%-------------------------------------------------------------------- 33 | %% Function: init(Args) -> {ok, State} | 34 | %% {ok, State, Timeout} | 35 | %% ignore | 36 | %% {stop, Reason} 37 | %% Description: Initiates the server 38 | %%-------------------------------------------------------------------- 39 | init() -> 40 | io:format("~p (~p) starting...~n", [?MODULE, self()]), 41 | %timer:send_interval(1000, ping), 42 | {ok, #state{users=dict:new()}}. 43 | 44 | %%-------------------------------------------------------------------- 45 | %% to handle a connection to your service 46 | %%-------------------------------------------------------------------- 47 | handle_join(_ServiceName, WebSocketPid, State) -> 48 | #state{users=Users} = State, 49 | {noreply, #state{users=dict:store(WebSocketPid, SessionId ,Users)}}. 50 | %%-------------------------------------------------------------------- 51 | 52 | 53 | %%-------------------------------------------------------------------- 54 | %% to handle a close connection to you service 55 | %%-------------------------------------------------------------------- 56 | handle_close(Reason, ServiceName, WebSocketId, State) -> 57 | #state{users=Users} = State, 58 | io:format("Service ~p, WsPid ~p close for Reason ~p~n", 59 | [ServiceName, WebSocketId, Reason]), 60 | {noreply, #state{users=dict:erase(WebSocketId,Users)}}. 61 | %%-------------------------------------------------------------------- 62 | 63 | handle_broadcast(Message, State) -> 64 | io:format("Broadcast Message ~p~n",[Message]), 65 | {noreply, State}. 66 | 67 | %%-------------------------------------------------------------------- 68 | %% to handle incoming message to your service 69 | %% here is simple copy to all 70 | %%-------------------------------------------------------------------- 71 | handle_incoming(_ServiceName, WebSocketId, Message, State) -> 72 | #state{users=Users} = State, 73 | Fun = fun(X) when is_pid(X)-> X ! {text, Message} end, 74 | All = dict:fetch_keys(Users), 75 | [Fun(E) || E <- All, E /= WebSocketId], 76 | %% end, 77 | {noreply, State}. 78 | %%-------------------------------------------------------------------- 79 | 80 | 81 | handle_info(ping, State) -> 82 | error_logger:info_msg("pong:~p~n", [now()]), 83 | {noreply, State}; 84 | 85 | handle_info(state, State) -> 86 | #state{users=Users} = State, 87 | All = dict:fetch_keys(Users), 88 | error_logger:info_msg("state:~p~n", [All]), 89 | {noreply, State}; 90 | 91 | handle_info(_Info, State) -> 92 | {noreply, State}. 93 | 94 | terminate(_Reason, _State) -> 95 | %call boss_service:unregister(?SERVER), 96 | ok. 97 | 98 | %% Internal functions 99 | -------------------------------------------------------------------------------- /apps/draw/priv/init/draw_01_news.erl: -------------------------------------------------------------------------------- 1 | -module(draw_01_news). 2 | 3 | -export([init/0, stop/1]). 4 | 5 | % This script is first executed at server startup and should 6 | % return a list of WatchIDs that should be cancelled in the stop 7 | % function below (stop is executed if the script is ever reloaded). 8 | init() -> 9 | {ok, []}. 10 | 11 | stop(ListOfWatchIDs) -> 12 | lists:map(fun boss_news:cancel_watch/1, ListOfWatchIDs). 13 | 14 | %%%%%%%%%%% Ideas 15 | % boss_news:watch("user-42.*", 16 | % fun 17 | % (updated, {Donald, 'location', OldLocation, NewLocation}) -> 18 | % ; 19 | % (updated, {Donald, 'email_address', OldEmail, NewEmail}) 20 | % end), 21 | % 22 | % boss_news:watch("user-*.status", 23 | % fun(updated, {User, 'status', OldStatus, NewStatus}) -> 24 | % Followers = User:followers(), 25 | % lists:map(fun(Follower) -> 26 | % Follower:notify_status_update(User, NewStatus) 27 | % end, Followers) 28 | % end), 29 | % 30 | % boss_news:watch("users", 31 | % fun 32 | % (created, NewUser) -> 33 | % boss_mail:send(?WEBSITE_EMAIL_ADDRESS, 34 | % ?ADMINISTRATOR_EMAIL_ADDRESS, 35 | % "New account!", 36 | % "~p just created an account!~n", 37 | % [NewUser:name()]); 38 | % (deleted, OldUser) -> 39 | % ok 40 | % end), 41 | % 42 | % boss_news:watch("forum_replies", 43 | % fun 44 | % (created, Reply) -> 45 | % OrignalPost = Reply:original_post(), 46 | % OriginalAuthor = OriginalPost:author(), 47 | % case OriginalAuthor:is_online() of 48 | % true -> 49 | % boss_mq:push(OriginalAuthor:comet_channel(), <<"Someone replied!">>); 50 | % false -> 51 | % case OriginalAuthor:likes_email() of 52 | % true -> 53 | % boss_mail:send("website@blahblahblah", 54 | % OriginalAuthor:email_address(), 55 | % "Someone replied!" 56 | % "~p has replied to your post on ~p~n", 57 | % [(Reply:author()):name(), OriginalPost:title()]); 58 | % false -> 59 | % ok 60 | % end 61 | % end; 62 | % (_, _) -> ok 63 | % end), 64 | % 65 | % boss_news:watch("forum_categories", 66 | % fun 67 | % (created, NewCategory) -> 68 | % boss_mail:send(?WEBSITE_EMAIL_ADDRESS, 69 | % ?ADMINISTRATOR_EMAIL_ADDRESS, 70 | % "New category: "++NewCategory:name(), 71 | % "~p has created a new forum category called \"~p\"~n", 72 | % [(NewCategory:created_by()):name(), NewCategory:name()]); 73 | % (_, _) -> ok 74 | % end), 75 | % 76 | % boss_news:watch("forum_category-*.is_deleted", 77 | % fun 78 | % (updated, {ForumCategory, 'is_deleted', false, true}) -> 79 | % ; 80 | % (updated, {ForumCategory, 'is_deleted', true, false}) -> 81 | % end). 82 | 83 | % Invoking the API directly: 84 | %boss_news:deleted("person-42", OldAttrs), 85 | %boss_news:updated("person-42", OldAttrs, NewAttrs), 86 | %boss_news:created("person-42", NewAttrs) 87 | 88 | % Invoking the API via HTTP (with the admin application installed): 89 | % POST /admin/news_api/deleted/person-42 90 | % old[status] = something 91 | 92 | % POST /admin/news_api/updated/person-42 93 | % old[status] = blah 94 | % new[status] = barf 95 | 96 | % POST /admin/news_api/created/person-42 97 | % new[status] = something 98 | -------------------------------------------------------------------------------- /apps/cb_admin/priv/init/cb_admin_01_news.erl: -------------------------------------------------------------------------------- 1 | -module(cb_admin_01_news). 2 | 3 | -export([init/0, stop/1]). 4 | 5 | % This script is first executed at server startup and should 6 | % return a list of WatchIDs that should be cancelled in the stop 7 | % function below (stop is executed if the script is ever reloaded). 8 | init() -> 9 | {ok, []}. 10 | 11 | stop(ListOfWatchIDs) -> 12 | lists:map(fun boss_news:cancel_watch/1, ListOfWatchIDs). 13 | 14 | %%%%%%%%%%% Ideas 15 | % boss_news:watch_set("greetings", 16 | % fun(created, NewGreeting) -> 17 | % boss_mail:send("boss@evanmiller.org", 18 | % "emmiller@gmail.com", 19 | % "New greeting!", 20 | % "There is a new greeting: ~p~n", 21 | % [NewGreeting:greeting_text()]) 22 | % end. 23 | % boss_news:watch("user-42.*", 24 | % fun 25 | % (Donald, 'location', OldLocation, NewLocation) -> 26 | % ; 27 | % (Donald, 'email_address', OldEmail, NewEmail) 28 | % end), 29 | % 30 | % boss_news:watch("user-*.status", 31 | % fun(User, 'status', OldStatus, NewStatus) -> 32 | % Followers = User:followers(), 33 | % lists:map(fun(Follower) -> 34 | % Follower:notify_status_update(User, NewStatus) 35 | % end, Followers) 36 | % end), 37 | % 38 | % boss_news:watch_set("users", 39 | % fun 40 | % (created, NewUser) -> 41 | % boss_mail:send(?WEBSITE_EMAIL_ADDRESS, 42 | % ?ADMINISTRATOR_EMAIL_ADDRESS, 43 | % "New account!", 44 | % "~p just created an account!~n", 45 | % [NewUser:name()]); 46 | % (deleted, OldUser) -> 47 | % ok 48 | % end), 49 | % 50 | % boss_news:watch_set("forum_replies", 51 | % fun 52 | % (created, Reply) -> 53 | % OrignalPost = Reply:original_post(), 54 | % OriginalAuthor = OriginalPost:author(), 55 | % case OriginalAuthor:is_online() of 56 | % true -> 57 | % boss_mq:push(OriginalAuthor:comet_channel(), <<"Someone replied!">>); 58 | % false -> 59 | % case OriginalAuthor:likes_email() of 60 | % true -> 61 | % boss_mail:send("website@blahblahblah", 62 | % OriginalAuthor:email_address(), 63 | % "Someone replied!" 64 | % "~p has replied to your post on ~p~n", 65 | % [(Reply:author()):name(), OriginalPost:title()]); 66 | % false -> 67 | % ok 68 | % end 69 | % end; 70 | % (_ _) -> ok 71 | % end), 72 | % 73 | % boss_news:watch_set("forum_categories", 74 | % fun 75 | % (created, NewCategory) -> 76 | % boss_mail:send(?WEBSITE_EMAIL_ADDRESS, 77 | % ?ADMINISTRATOR_EMAIL_ADDRESS, 78 | % "New category: "++NewCategory:name(), 79 | % "~p has created a new forum category called \"~p\"~n", 80 | % [(NewCategory:created_by()):name(), NewCategory:name()]); 81 | % (_, _) -> ok 82 | % end), 83 | % 84 | % boss_news:watch("forum_category-*.is_deleted", 85 | % fun 86 | % (ForumCategory, 'is_deleted', false, true) -> 87 | % ; 88 | % (ForumCategory, 'is_deleted', true, false) -> 89 | % end). 90 | 91 | %boss_news:deleted("person-42", OldAttrs), 92 | %boss_news:updated("person-42", OldAttrs, NewAttrs), 93 | %boss_news:created("person-42", NewAttrs) 94 | 95 | % POST /admin/news_api/deleted/person-42 96 | % old[status] = something 97 | 98 | % POST /admin/news_api/updated/person-42 99 | % old[status] = blah 100 | % new[status] = barf 101 | 102 | % POST /admin/news_api/created/person-42 103 | % new[status] = something 104 | -------------------------------------------------------------------------------- /apps/cb_admin/src/controller/cb_admin_lang_controller.erl: -------------------------------------------------------------------------------- 1 | -module(cb_admin_lang_controller, [Req, SessionID]). 2 | -compile(export_all). 3 | -default_action(show). 4 | 5 | before_(_) -> 6 | cb_admin_lib:require_ip_address(Req). 7 | 8 | show('GET', [], Auth) -> 9 | Applications = boss_web:get_all_applications(), 10 | {ok, [{lang_section, true}, {applications, Applications}]}; 11 | show('GET', [App], Auth) -> 12 | Applications = boss_web:get_all_applications(), 13 | Languages = boss_files:language_list(list_to_atom(App)), 14 | {ok, [{lang_section, true}, {this_application, App}, 15 | {applications, Applications}, {languages, Languages}]}; 16 | show('GET', [App, Lang], Auth) -> 17 | OriginalLang = boss_env:get_env(assume_locale, "en"), 18 | AppAtom = list_to_atom(App), 19 | Applications = boss_web:get_all_applications(), 20 | Languages = boss_files:language_list(AppAtom), 21 | {Untranslated, Translated} = boss_lang:extract_strings(AppAtom, Lang), 22 | LastModified = filelib:last_modified(boss_files:lang_path(AppAtom, Lang)), 23 | {ok, [{this_lang, Lang}, {languages, Languages}, 24 | {this_application, App}, {applications, Applications}, 25 | {original_lang, OriginalLang}, 26 | {untranslated_messages, Untranslated}, 27 | {translated_messages, Translated}, 28 | {last_modified, LastModified}, 29 | {lang_section, true}], 30 | [{"Cache-Control", "no-cache"}]}. 31 | 32 | edit('POST', [App, Lang|Fmt], Auth) -> 33 | WithBlanks = Req:post_param("trans_all_with_blanks"), 34 | AppAtom = list_to_atom(App), 35 | LangFile = boss_files:lang_path(AppAtom, Lang), 36 | {ok, IODevice} = file:open(LangFile, [write, append]), 37 | lists:map(fun(Message) -> 38 | Original = proplists:get_value("orig", Message), 39 | Translation = proplists:get_value("trans", Message), 40 | case Translation of 41 | "" -> 42 | case WithBlanks of 43 | undefined -> ok; 44 | _ -> boss_lang:lang_write_to_file(IODevice, Original, Translation) 45 | end; 46 | _ -> boss_lang:lang_write_to_file(IODevice, Original, Translation) 47 | end 48 | end, Req:deep_post_param(["messages"])), 49 | file:close(IODevice), 50 | Trans_pid=boss_web:translator_pid(AppAtom), 51 | boss_translator:reload(Trans_pid,Lang), 52 | case Fmt of 53 | ["json"] -> {json, [{success, true}]}; 54 | [] -> {redirect, [{action, "show"}, {app, App}, {lang, Lang}]} 55 | end. 56 | 57 | create('GET', [], Auth) -> 58 | {ok, [{lang_section, true}, {applications, boss_web:get_all_applications()}]}; 59 | create('GET', [App], Auth) -> 60 | AppAtom = list_to_atom(App), 61 | {ok, [{lang_section, true}, {applications, boss_web:get_all_applications()}, 62 | {this_application, App}, {languages, boss_files:language_list(AppAtom)}]}; 63 | create('POST', [App], Auth) -> 64 | % TODO sanitize 65 | AppAtom = list_to_atom(App), 66 | NewLang = Req:post_param("language"), 67 | boss_lang:create_lang(AppAtom, NewLang), 68 | {redirect, [{action, "show"}, {app, App}, {lang, NewLang}]}. 69 | 70 | delete('GET', [App, Lang], Auth) -> 71 | {ok, [{lang_section, true}, {this_application, App}, {applications, boss_web:get_all_applications()}, 72 | {this_lang, Lang}]}; 73 | delete('POST', [App, Lang], Auth) -> 74 | AppAtom = list_to_atom(App), 75 | boss_lang:delete_lang(AppAtom, Lang), 76 | boss_web:reload_translation(Lang), 77 | {redirect, [{action, "show"}]}. 78 | 79 | big_red_button('GET', [App], Auth) -> 80 | AppAtom = list_to_atom(App), 81 | Languages = lists:map(fun(Lang) -> 82 | {Untranslated, Translated} = boss_lang:extract_strings(AppAtom, Lang), 83 | [{code, Lang}, {untranslated_strings, Untranslated}] 84 | end, boss_files:language_list(AppAtom)), 85 | {ok, [{lang_section, true}, {this_application, App}, {applications, boss_web:get_all_applications()}, 86 | {languages, Languages}, {strings, boss_lang:extract_strings(AppAtom)}]}. 87 | 88 | -------------------------------------------------------------------------------- /rel/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/cb_admin/src/view/lang/show.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/layouts/admin.html" %} 2 | {% block title %}International Command Center{% endblock %} 3 | 4 | {% block left_submenu %} 5 | {% include "lang/shared/_lang_left_submenu.html" with index="true" %} 6 | {% endblock %} 7 | 8 | {% block right_submenu %} 9 | {% include "lang/shared/_lang_right_submenu.html" %} 10 | {% endblock %} 11 | 12 | {% block meta_scripts %} 13 | 15 | 45 | {% endblock %} 46 | {% block body %} 47 | 48 | {% if applications %} 49 |

Applications: 50 | {% for app in applications %} 51 | {% ifequal app this_application %} 52 | {{ app }} 53 | {% else %} 54 | {{ app }} 55 | {% endifequal %} 56 | {% endfor %} 57 |

58 | {% endif %} 59 | 60 |

International Command Center

61 | 62 | {% if this_lang %} 63 | 69 | {% endif %} 70 | 71 |

Manage Languages

72 | 73 | {% if this_application %} 74 |

75 |

76 |

77 | {% if languages %} 78 | Your language files: 79 | {% for lang in languages %} 80 | {% ifequal lang this_lang %} 81 | {{ lang }} 82 | {% else %} 83 | {{ lang }} 84 | {% endifequal %} 85 | {% endfor %} 86 | {% endif %} 87 |      88 |      89 | 90 |      91 |

92 |
93 | 94 | {% if this_lang %} 95 |
96 |

Untranslated messages

97 | {% endif %} 98 | 99 | {% if untranslated_messages %} 100 |

{{ untranslated_messages|length }} untranslated messages: (Fill in the blanks with Google! EXPERIMENTAL FEATURE)

101 |
102 | 103 | 104 | 105 | 106 | 107 | {% for message in untranslated_messages %} 108 | 109 | 116 | 117 | {% endfor %} 118 | 119 |
Original/Translation
110 | 111 | 112 | 113 | 114 | 115 |
120 |
121 |
122 |

123 | 124 | 125 |

126 |
127 |
128 |

129 | 130 | 131 |

132 |
133 |
134 |
135 | {% else %} 136 | {% if this_lang %} 137 |

No untranslated string messages found

138 | {% endif %} 139 | {% endif %} 140 | 141 | {% if this_lang %} 142 |
143 |

Translated messages

144 | {% endif %} 145 | 146 | {% if translated_messages %} 147 |

{{ translated_messages|length }} translated messages:

148 | 149 | 150 | 151 | 152 | 153 | {% for orig, msg in translated_messages %} 154 | 155 | 156 | 157 | {% endfor %} 158 | 159 |
OriginalTranslation
{{ orig }}{{ msg }}
160 | {% if last_modified %} 161 |

Last modified: {{ last_modified|date:"N j, Y, P" }}

162 | {% endif %} 163 | {% else %} 164 | {% if this_lang %} 165 |

No translated string messages found

166 | {% endif %} 167 | {% endif %} 168 | 169 | {% if this_lang %} 170 |
171 |

Actions

172 |

Delete this language file...

173 | {% endif %} 174 | 175 | {% endif %}{# this_application #} 176 | 177 | {% endblock %} 178 | -------------------------------------------------------------------------------- /apps/draw/priv/static/drawings.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | 4 | var canvas = document.getElementById("canvas"), 5 | ctx = canvas.getContext("2d"), 6 | remotecanvas = document.getElementById("remotecanvas"), 7 | remotectx = remotecanvas.getContext("2d"), 8 | $cvs = $("#canvas"), 9 | top = $cvs.offset().top, 10 | left = $cvs.offset().left, 11 | wsc = new WebSocket("ws://localhost:8001/websocket/draw_protocol", "draw_protocol"), 12 | mySocketId = -1; 13 | 14 | 15 | var resizeCvs = function() { 16 | ctx.canvas.width = remotectx.canvas.width = $(window).width(); 17 | ctx.canvas.height = remotectx.canvas.height = $(window).height(); 18 | }; 19 | 20 | var initializeCvs = function () { 21 | ctx.lineCap = remotectx.lineCap = "round"; 22 | resizeCvs(); 23 | ctx.save(); 24 | remotectx.save(); 25 | ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); 26 | remotectx.clearRect(0,0, remotectx.canvas.width, remotectx.canvas.height); 27 | ctx.restore(); 28 | remotectx.restore(); 29 | }; 30 | 31 | var draw = { 32 | isDrawing: false, 33 | mousedown: function(coordinates) { 34 | ctx.beginPath(); 35 | ctx.moveTo(coordinates.x, coordinates.y); 36 | this.isDrawing = true; 37 | }, 38 | mousemove: function(coordinates) { 39 | if (this.isDrawing) { 40 | ctx.lineTo(coordinates.x, coordinates.y); 41 | ctx.stroke(); 42 | } 43 | }, 44 | mouseup: function(coordinates) { 45 | this.isDrawing = false; 46 | ctx.lineTo(coordinates.x, coordinates.y); 47 | ctx.stroke(); 48 | ctx.closePath(); 49 | }, 50 | touchstart: function(coordinates){ 51 | ctx.beginPath(); 52 | ctx.moveTo(coordinates.x, coordinates.y); 53 | this.isDrawing = true; 54 | }, 55 | touchmove: function(coordinates){ 56 | if (this.isDrawing) { 57 | ctx.lineTo(coordinates.x, coordinates.y); 58 | ctx.stroke(); 59 | } 60 | }, 61 | touchend: function(coordinates){ 62 | if (this.isDrawing) { 63 | this.touchmove(coordinates); 64 | this.isDrawing = false; 65 | } 66 | } 67 | }; 68 | var remotedraw = { 69 | isDrawing: false, 70 | mousedown: function(coordinates) { 71 | remotectx.beginPath(); 72 | remotectx.moveTo(coordinates.x, coordinates.y); 73 | this.isDrawing = true; 74 | }, 75 | mousemove: function(coordinates) { 76 | if (this.isDrawing) { 77 | remotectx.lineTo(coordinates.x, coordinates.y); 78 | remotectx.stroke(); 79 | } 80 | }, 81 | mouseup: function(coordinates) { 82 | this.isDrawing = false; 83 | remotectx.lineTo(coordinates.x, coordinates.y); 84 | remotectx.stroke(); 85 | remotectx.closePath(); 86 | }, 87 | touchstart: function(coordinates){ 88 | remotectx.beginPath(); 89 | remotectx.moveTo(coordinates.x, coordinates.y); 90 | this.isDrawing = true; 91 | }, 92 | touchmove: function(coordinates){ 93 | if (this.isDrawing) { 94 | remotectx.lineTo(coordinates.x, coordinates.y); 95 | remotectx.stroke(); 96 | } 97 | }, 98 | touchend: function(coordinates){ 99 | if (this.isDrawing) { 100 | this.touchmove(coordinates); 101 | this.isDrawing = false; 102 | } 103 | } 104 | }; 105 | // create a function to pass touch events and coordinates to drawer 106 | function setupDraw(event, isRemote){ 107 | 108 | var coordinates = {}; 109 | var evt = {}; 110 | evt.type = event.type; 111 | evt.socketid = mySocketId; 112 | evt.lineWidth = ctx.lineWidth; 113 | evt.strokeStyle = ctx.strokeStyle; 114 | if (event.type.indexOf("touch") != -1 ){ 115 | evt.targetTouches = [{ pageX: 0, pageY: 0 }]; 116 | evt.targetTouches[0].pageX = event.targetTouches[0].pageX || 0; 117 | evt.targetTouches[0].pageY = event.targetTouches[0].pageY || 0; 118 | coordinates.x = event.targetTouches[0].pageX - left; 119 | coordinates.y = event.targetTouches[0].pageY - top; 120 | } else { 121 | evt.pageX = event.pageX; 122 | evt.pageY = event.pageY; 123 | coordinates.x = event.pageX - left; 124 | coordinates.y = event.pageY - top; 125 | } 126 | if (event.strokeStyle) { 127 | remotectx.strokeStyle = event.strokeStyle; 128 | remotectx.lineWidth = event.lineWidth; 129 | } 130 | 131 | 132 | if (!isRemote) { 133 | wsc.send(JSON.stringify(evt)); 134 | draw[event.type](coordinates); 135 | } else { 136 | remotedraw[event.type](coordinates); 137 | } 138 | }; 139 | 140 | window.addEventListener("mousedown", setupDraw, false); 141 | window.addEventListener("mousemove", setupDraw, false); 142 | window.addEventListener("mouseup", setupDraw, false); 143 | canvas.addEventListener('touchstart',setupDraw, false); 144 | canvas.addEventListener('touchmove',setupDraw, false); 145 | canvas.addEventListener('touchend',setupDraw, false); 146 | 147 | document.body.addEventListener('touchmove',function(event){ 148 | event.preventDefault(); 149 | },false); 150 | 151 | $('#clear').click(function (e) { 152 | initializeCvs(true); 153 | $("#sizer").val(""); 154 | }); 155 | 156 | $("#draw").click(function (e) { 157 | e.preventDefault(); 158 | $("label[for='sizer']").text("Line Size:"); 159 | }); 160 | 161 | 162 | $("#colors li").click(function (e) { 163 | e.preventDefault(); 164 | $("label[for='sizer']").text("Line Size:"); 165 | ctx.strokeStyle = $(this).css("background-color"); 166 | }); 167 | 168 | 169 | $("#sizer").change(function (e) { 170 | ctx.lineWidth = parseInt($(this).val(), 10); 171 | }); 172 | 173 | initializeCvs(); 174 | 175 | 176 | window.onresize = function() { 177 | resizeCvs(); 178 | }; 179 | 180 | 181 | wsc.onmessage = function(event) { 182 | if (event.data.indexOf("socketid_") !== -1) { 183 | mySocketId = event.data.split("_")[1]; 184 | } else { 185 | var dt = JSON.parse(event.data); 186 | setupDraw(dt, true); 187 | } 188 | 189 | } 190 | }); 191 | -------------------------------------------------------------------------------- /apps/cb_admin/src/test/functional/cb_admin_test.erl: -------------------------------------------------------------------------------- 1 | -module(cb_admin_test). 2 | -compile(export_all). 3 | 4 | start() -> 5 | boss_web_test:get_request("/admin", [], 6 | [ fun boss_assert:http_ok/1, 7 | fun(Res) -> boss_assert:link_with_text("\"An Evening With Chicago Boss\"", Res) end, 8 | fun(Res) -> boss_assert:link_with_text("chicagoboss.org", Res) end, 9 | fun(Res) -> boss_assert:link_with_text("Chicago Boss Google Group", Res) end 10 | ], 11 | [ "Admin interface at /admin", 12 | fun(Response1) -> 13 | boss_web_test:follow_link("/admin", Response1, 14 | [ fun boss_assert:http_ok/1, 15 | fun(Res) -> boss_assert:tag_with_text("title", "Chicago Boss Admin - General Info", Res) end 16 | ], 17 | [ "Click Models", 18 | fun(Response1a) -> 19 | boss_web_test:follow_link("Models", Response1a, 20 | [ fun boss_assert:http_ok/1, 21 | fun (Res) -> boss_assert:link_with_text("greeting", Res) end 22 | ], 23 | [ "Click greeting", 24 | fun(Response2) -> 25 | boss_web_test:follow_link("greeting", Response2, 26 | [ fun boss_assert:http_ok/1, 27 | fun (Res) -> boss_assert:link_with_text("Documentation for the greeting model", Res) end 28 | ], 29 | [ "Click + New greeting", 30 | fun({_, GreetingUrl, _, _} = Response3) -> 31 | boss_web_test:submit_form("new", [], Response3, 32 | [ fun boss_assert:http_ok/1, 33 | fun(Res) -> boss_assert:link_with_text("Back to the greeting list", Res) end 34 | ], 35 | [ "Submit valid greeting", 36 | fun(Response4) -> 37 | ValidGreetingText = "Hello", 38 | boss_web_test:submit_form("create", [{"greeting_text", ValidGreetingText}], Response4, 39 | [ fun boss_assert:http_redirect/1 ], 40 | [ "Redirect", 41 | fun(Response5) -> 42 | boss_web_test:follow_redirect(Response5, 43 | [ fun boss_assert:http_ok/1, 44 | fun(Res) -> boss_assert:link_with_text("Back to the greeting list", Res) end 45 | ], 46 | [ "Delete record", 47 | fun(Response6) -> 48 | boss_web_test:submit_form("delete", [], Response6, 49 | [ fun boss_assert:http_ok/1 ], 50 | [ "Really delete", 51 | fun(Response7) -> 52 | boss_web_test:submit_form("delete", [], Response7, 53 | [ fun boss_assert:http_redirect/1, 54 | fun(Res) -> boss_assert:location_header(GreetingUrl, Res) end 55 | ], []) end ]) end, 56 | "Back to greeting list", 57 | fun(Response6) -> 58 | boss_web_test:follow_link("Back to the greeting list", Response6, 59 | [ fun boss_assert:http_ok/1, 60 | fun(Res) -> boss_assert:tag_with_text("td", ValidGreetingText, Res) end 61 | ], []) end ]) end ]) end, 62 | "Submit invalid greeting", 63 | fun(Response4) -> 64 | boss_web_test:submit_form("create", [{"greeting_text", "Hi"}], Response4, 65 | [ fun boss_assert:http_ok/1, 66 | fun(Res) -> boss_assert:tag_with_text("li", "Greeting must be at least 4 characters long!", Res) end 67 | ], []) end ]) end ]) end ]) end ]) end, 68 | "EDoc at /doc", 69 | fun(Response1) -> 70 | boss_web_test:follow_link("/doc", Response1, 71 | [ 72 | fun boss_assert:http_ok/1, 73 | fun(Res) -> boss_assert:link_with_text("Function Index", Res) end, 74 | fun(Res) -> boss_assert:link_with_text("Function Details", Res) end, 75 | fun(Res) -> boss_assert:link_with_text("attribute_names/0*", Res) end, 76 | fun(Res) -> boss_assert:link_with_text("attributes/0*", Res) end, 77 | fun(Res) -> boss_assert:link_with_text("attributes/1*", Res) end, 78 | fun(Res) -> boss_assert:link_with_text("belongs_to/0*", Res) end, 79 | fun(Res) -> boss_assert:link_with_text("belongs_to_names/0*", Res) end, 80 | fun(Res) -> boss_assert:link_with_text("greeting_text/0*", Res) end, 81 | fun(Res) -> boss_assert:link_with_text("greeting_text/1*", Res) end, 82 | fun(Res) -> boss_assert:link_with_text("id/0*", Res) end, 83 | fun(Res) -> boss_assert:link_with_text("id/1*", Res) end, 84 | fun(Res) -> boss_assert:link_with_text("save/0*", Res) end, 85 | fun(Res) -> boss_assert:link_with_text("validate/0*", Res) end, 86 | fun(Res) -> boss_assert:link_with_text("validation_tests/0*", Res) end 87 | ], []) 88 | end 89 | % , 90 | % 91 | % "Check for confirmation email", 92 | % fun(Response1) -> 93 | % boss_web_test:read_email("test@test.com", "Confirmation email", 94 | % [ 95 | % fun(Email2) -> boss_assert:link_with_text("Click here to confirm", Email) end 96 | % ], 97 | % [ 98 | % "Click confirmation link", 99 | % fun(Email2) -> 100 | % boss_web_test:follow_link("Click here to confirm", Email, 101 | % [ 102 | % fun(Response3) -> boss_assert:tag_with_text( 103 | % ], 104 | % end 105 | ]). 106 | -------------------------------------------------------------------------------- /apps/draw/priv/rebar/boss_plugin.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Jose Luis Gordo Romero 3 | %%% @doc Chicago Boss rebar plugin 4 | %%% Manage compilation/configuration/scripts stuff the rebar way 5 | %%% @end 6 | %%%------------------------------------------------------------------- 7 | -module(boss_plugin). 8 | 9 | -export([boss/2, 10 | pre_compile/2, 11 | pre_eunit/2]). 12 | 13 | -define(BOSS_PLUGIN_CLIENT_VERSION, 1). 14 | -define(BOSS_CONFIG_FILE, "boss.config"). 15 | -define(BOSS_TEST_CONFIG_FILE, "boss.test.config"). 16 | 17 | %% ==================================================================== 18 | %% Public API 19 | %% ==================================================================== 20 | 21 | %%-------------------------------------------------------------------- 22 | %% @doc boss command 23 | %% @spec boss(_Config, _AppFile) -> ok | {error, Reason} 24 | %% Boss enabled rebar commands, usage: 25 | %% ./rebar boss c=command 26 | %% @end 27 | %%-------------------------------------------------------------------- 28 | boss(RebarConf, AppFile) -> 29 | case is_base_dir(RebarConf) of 30 | true -> 31 | Command = rebar_config:get_global(RebarConf, c, "help"), 32 | {ok, BossConf} = init(RebarConf, AppFile, Command), 33 | case boss_rebar:run(?BOSS_PLUGIN_CLIENT_VERSION, Command, RebarConf, BossConf, AppFile) of 34 | {error, command_not_found} -> 35 | io:format("ERROR: boss command not found.~n"), 36 | boss_rebar:help(), 37 | halt(1); 38 | {error, Reason} -> 39 | io:format("ERROR: executing ~s task: ~s~n", [Command, Reason]), 40 | halt(1); 41 | ok -> ok 42 | end; 43 | false -> ok 44 | end. 45 | 46 | %%-------------------------------------------------------------------- 47 | %% @doc initializes the rebar boss connector plugin 48 | %% @spec init(Config, AppFile) -> {ok, BossConf} | {error, Reason} 49 | %% Set's the ebin cb_apps and loads the connector 50 | %% @end 51 | %%-------------------------------------------------------------------- 52 | init(RebarConf, AppFile, Command) -> 53 | %% Compile and load the boss_rebar code, this can't be compiled 54 | %% as a normal boss lib without the rebar source dep 55 | %% The load of ./rebar boss: 56 | %% - Rebar itself searchs in rebar.config for {plugin_dir, ["priv/rebar"]}. 57 | %% - Rebar itself compile this plugin and adds it to the execution chain 58 | %% - This plugin compiles and loads the boss_rebar code in ["cb/priv/rebar"], 59 | %% so we can extend/bugfix/tweak the framework without the need of manually 60 | %% recopy code to user apps 61 | {BossConf, BossConfFile} = boss_config(Command), 62 | BossPath = case boss_config_value(boss, path, BossConf) of 63 | {error, _} -> 64 | filename:join([rebar_config:get_xconf(RebarConf, base_dir, undefined), "deps", "boss"]); 65 | Val -> Val 66 | end, 67 | RebarErls = rebar_utils:find_files(filename:join([BossPath, "priv", "rebar"]), ".*\\.erl\$"), 68 | 69 | rebar_log:log(debug, "Auto-loading boss rebar modules ~p~n", [RebarErls]), 70 | 71 | lists:map(fun(F) -> 72 | case compile:file(F, [binary]) of 73 | error -> 74 | io:format("FATAL: Failed compilation of ~s module~n", [F]), 75 | halt(1); 76 | {ok, M, Bin} -> 77 | {module, _} = code:load_binary(M, F, Bin), 78 | rebar_log:log(debug, "Loaded ~s~n", [M]) 79 | end 80 | end, RebarErls), 81 | 82 | %% add all cb_apps defined in config file to code path 83 | %% including the deps ebin dirs 84 | 85 | case is_base_dir(RebarConf) of 86 | true -> 87 | code:add_paths(["ebin" | filelib:wildcard(rebar_config:get_xconf(RebarConf, base_dir, undefined) ++ "/deps/*/ebin")]); 88 | false -> 89 | code:add_paths(filelib:wildcard(rebar_utils:get_cwd() ++ "/../*/ebin")) 90 | end, 91 | %% Load the config in the environment 92 | boss_rebar:init_conf(BossConf), 93 | 94 | {ok, BossConf}. 95 | 96 | %%-------------------------------------------------------------------- 97 | %% @doc pre_compile hook 98 | %% @spec pre_compile(_Config, AppFile) -> ok | {error, Reason} 99 | %% Pre compile hook, compile the boss way 100 | %% Compatibility hook, the normal ./rebar compile command works, 101 | %% but only calls the ./rebar boss c=compile and halts (default 102 | %% rebar task never hits) 103 | %% @end 104 | %%-------------------------------------------------------------------- 105 | pre_compile(RebarConf, AppFile) -> 106 | case is_boss_app(AppFile) orelse is_base_dir(RebarConf) of 107 | true -> 108 | {ok, BossConf} = init(RebarConf, AppFile, "compile"), 109 | boss_rebar:run(?BOSS_PLUGIN_CLIENT_VERSION, compile, RebarConf, BossConf, AppFile), 110 | halt(0); 111 | false -> ok 112 | end. 113 | 114 | %%-------------------------------------------------------------------- 115 | %% @doc pre_eunit hook 116 | %% @spec pre_eunit(RebarConf, AppFile) -> ok | {error, Reason} 117 | %% Pre eunit hook, .eunit compilation the boss way 118 | %% Compatibility hook, the normal ./rebar eunit command works, 119 | %% but only calls the ./rebar boss c=test_eunit and halts 120 | %% (default rebar task never hits) 121 | %% @end 122 | %%-------------------------------------------------------------------- 123 | pre_eunit(RebarConf, AppFile) -> 124 | case is_boss_app(AppFile) orelse is_base_dir(RebarConf) of 125 | true -> 126 | {ok, BossConf} = init(RebarConf, AppFile, "test_eunit"), 127 | boss_rebar:run(?BOSS_PLUGIN_CLIENT_VERSION, test_eunit, RebarConf, BossConf, AppFile), 128 | halt(0); 129 | false -> ok 130 | end. 131 | 132 | %% =================================================================== 133 | %% Internal functions 134 | %% =================================================================== 135 | 136 | is_boss_app(Filename)-> 137 | BossFileForApp = filename:join([filename:dirname(Filename), "..", "boss.config"]), 138 | filelib:is_file(BossFileForApp). 139 | 140 | %% Checks if the current dir (rebar execution) is the base_dir 141 | %% Used to prevent run boss tasks in deps directory 142 | is_base_dir(RebarConf) -> 143 | filename:absname(rebar_utils:get_cwd()) =:= rebar_config:get_xconf(RebarConf, base_dir, undefined). 144 | 145 | %% Gets the boss.config central configuration file 146 | boss_config(Command) -> 147 | IsTest = lists:prefix("test_", Command), 148 | BossConfFile = get_config_file(IsTest), 149 | ConfigData = file:consult(BossConfFile), 150 | validate_config_data(BossConfFile, ConfigData). 151 | 152 | get_config_file(false) -> 153 | ?BOSS_CONFIG_FILE; 154 | get_config_file(true) -> 155 | case file:read_file_info(?BOSS_TEST_CONFIG_FILE) of 156 | {ok, _} -> ?BOSS_TEST_CONFIG_FILE; 157 | _ -> ?BOSS_CONFIG_FILE 158 | end. 159 | 160 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 161 | 162 | validate_config_data(BossConfFile, {error, {Line, _Mod, [Term|_]}}) -> 163 | io:format("FATAL: Config file ~p has a syntax error on line ~p, ~n ~p ~n~n", 164 | [BossConfFile, Line, Term]), 165 | halt(1); 166 | validate_config_data(BossConfFile, {error, enoent}) -> 167 | io:format("FATAL: Config file ~p not found.~n", [BossConfFile]), 168 | halt(1); 169 | validate_config_data(BossConfFile, {ok, [BossConfig]}) -> 170 | {BossConfig, BossConfFile}. 171 | 172 | 173 | 174 | %%-------------------------------------------------------------------- 175 | %% @doc Get Boss config value app, key 176 | %% @spec boss_config_value(App, Key) -> Value | {error, Reason} 177 | %% Searchs in boss config for a given App and Key 178 | %% @end 179 | %%-------------------------------------------------------------------- 180 | boss_config_value(App, Key, BossConfig) -> 181 | case lists:keyfind(App, 1, BossConfig) of 182 | false -> 183 | {error, boss_config_app_not_found}; 184 | {App, AppConfig} -> 185 | case lists:keyfind(Key, 1, AppConfig) of 186 | false -> 187 | {error, boss_config_app_setting_not_found}; 188 | {Key, KeyConfig} -> 189 | KeyConfig 190 | end 191 | end. 192 | -------------------------------------------------------------------------------- /apps/cb_admin/priv/rebar/boss_plugin.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Jose Luis Gordo Romero 3 | %%% @doc Chicago Boss rebar plugin 4 | %%% Manage compilation/configuration/scripts stuff the rebar way 5 | %%% @end 6 | %%%------------------------------------------------------------------- 7 | -module(boss_plugin). 8 | 9 | -export([boss/2, 10 | pre_compile/2, 11 | pre_eunit/2]). 12 | 13 | -define(BOSS_PLUGIN_CLIENT_VERSION, 1). 14 | -define(BOSS_CONFIG_FILE, "boss.config"). 15 | -define(BOSS_TEST_CONFIG_FILE, "boss.test.config"). 16 | 17 | %% ==================================================================== 18 | %% Public API 19 | %% ==================================================================== 20 | 21 | %%-------------------------------------------------------------------- 22 | %% @doc boss command 23 | %% @spec boss(_Config, _AppFile) -> ok | {error, Reason} 24 | %% Boss enabled rebar commands, usage: 25 | %% ./rebar boss c=command 26 | %% @end 27 | %%-------------------------------------------------------------------- 28 | boss(RebarConf, AppFile) -> 29 | case is_base_dir(RebarConf) of 30 | true -> 31 | Command = rebar_config:get_global(RebarConf, c, "help"), 32 | {ok, BossConf} = init(RebarConf, AppFile, Command), 33 | case boss_rebar:run(?BOSS_PLUGIN_CLIENT_VERSION, Command, RebarConf, BossConf, AppFile) of 34 | {error, command_not_found} -> 35 | io:format("ERROR: boss command not found.~n"), 36 | boss_rebar:help(), 37 | halt(1); 38 | {error, Reason} -> 39 | io:format("ERROR: executing ~s task: ~s~n", [Command, Reason]), 40 | halt(1); 41 | ok -> ok 42 | end; 43 | false -> ok 44 | end. 45 | 46 | %%-------------------------------------------------------------------- 47 | %% @doc initializes the rebar boss connector plugin 48 | %% @spec init(Config, AppFile) -> {ok, BossConf} | {error, Reason} 49 | %% Set's the ebin cb_apps and loads the connector 50 | %% @end 51 | %%-------------------------------------------------------------------- 52 | init(RebarConf, AppFile, Command) -> 53 | %% Compile and load the boss_rebar code, this can't be compiled 54 | %% as a normal boss lib without the rebar source dep 55 | %% The load of ./rebar boss: 56 | %% - Rebar itself searchs in rebar.config for {plugin_dir, ["priv/rebar"]}. 57 | %% - Rebar itself compile this plugin and adds it to the execution chain 58 | %% - This plugin compiles and loads the boss_rebar code in ["cb/priv/rebar"], 59 | %% so we can extend/bugfix/tweak the framework without the need of manually 60 | %% recopy code to user apps 61 | {BossConf, BossConfFile} = boss_config(Command), 62 | BossPath = case boss_config_value(boss, path, BossConf) of 63 | {error, _} -> 64 | filename:join([rebar_config:get_xconf(RebarConf, base_dir, undefined), "deps", "boss"]); 65 | Val -> Val 66 | end, 67 | RebarErls = rebar_utils:find_files(filename:join([BossPath, "priv", "rebar"]), ".*\\.erl\$"), 68 | 69 | rebar_log:log(debug, "Auto-loading boss rebar modules ~p~n", [RebarErls]), 70 | 71 | lists:map(fun(F) -> 72 | case compile:file(F, [binary]) of 73 | error -> 74 | io:format("FATAL: Failed compilation of ~s module~n", [F]), 75 | halt(1); 76 | {ok, M, Bin} -> 77 | {module, _} = code:load_binary(M, F, Bin), 78 | rebar_log:log(debug, "Loaded ~s~n", [M]) 79 | end 80 | end, RebarErls), 81 | 82 | %% add all cb_apps defined in config file to code path 83 | %% including the deps ebin dirs 84 | 85 | case is_base_dir(RebarConf) of 86 | true -> 87 | code:add_paths(["ebin" | filelib:wildcard(rebar_config:get_xconf(RebarConf, base_dir, undefined) ++ "/deps/*/ebin")]); 88 | false -> 89 | code:add_paths(filelib:wildcard(rebar_utils:get_cwd() ++ "/../*/ebin")) 90 | end, 91 | %% Load the config in the environment 92 | boss_rebar:init_conf(BossConf), 93 | 94 | {ok, BossConf}. 95 | 96 | %%-------------------------------------------------------------------- 97 | %% @doc pre_compile hook 98 | %% @spec pre_compile(_Config, AppFile) -> ok | {error, Reason} 99 | %% Pre compile hook, compile the boss way 100 | %% Compatibility hook, the normal ./rebar compile command works, 101 | %% but only calls the ./rebar boss c=compile and halts (default 102 | %% rebar task never hits) 103 | %% @end 104 | %%-------------------------------------------------------------------- 105 | pre_compile(RebarConf, AppFile) -> 106 | case is_boss_app(AppFile) orelse is_base_dir(RebarConf) of 107 | true -> 108 | {ok, BossConf} = init(RebarConf, AppFile, "compile"), 109 | boss_rebar:run(?BOSS_PLUGIN_CLIENT_VERSION, compile, RebarConf, BossConf, AppFile), 110 | halt(0); 111 | false -> ok 112 | end. 113 | 114 | %%-------------------------------------------------------------------- 115 | %% @doc pre_eunit hook 116 | %% @spec pre_eunit(RebarConf, AppFile) -> ok | {error, Reason} 117 | %% Pre eunit hook, .eunit compilation the boss way 118 | %% Compatibility hook, the normal ./rebar eunit command works, 119 | %% but only calls the ./rebar boss c=test_eunit and halts 120 | %% (default rebar task never hits) 121 | %% @end 122 | %%-------------------------------------------------------------------- 123 | pre_eunit(RebarConf, AppFile) -> 124 | case is_boss_app(AppFile) orelse is_base_dir(RebarConf) of 125 | true -> 126 | {ok, BossConf} = init(RebarConf, AppFile, "test_eunit"), 127 | boss_rebar:run(?BOSS_PLUGIN_CLIENT_VERSION, test_eunit, RebarConf, BossConf, AppFile), 128 | halt(0); 129 | false -> ok 130 | end. 131 | 132 | %% =================================================================== 133 | %% Internal functions 134 | %% =================================================================== 135 | 136 | is_boss_app(Filename)-> 137 | BossFileForApp = filename:join([filename:dirname(Filename), "..", "boss.config"]), 138 | filelib:is_file(BossFileForApp). 139 | 140 | %% Checks if the current dir (rebar execution) is the base_dir 141 | %% Used to prevent run boss tasks in deps directory 142 | is_base_dir(RebarConf) -> 143 | filename:absname(rebar_utils:get_cwd()) =:= rebar_config:get_xconf(RebarConf, base_dir, undefined). 144 | 145 | %% Gets the boss.config central configuration file 146 | boss_config(Command) -> 147 | IsTest = lists:prefix("test_", Command), 148 | BossConfFile = get_config_file(IsTest), 149 | ConfigData = file:consult(BossConfFile), 150 | validate_config_data(BossConfFile, ConfigData). 151 | 152 | get_config_file(false) -> 153 | ?BOSS_CONFIG_FILE; 154 | get_config_file(true) -> 155 | case file:read_file_info(?BOSS_TEST_CONFIG_FILE) of 156 | {ok, _} -> ?BOSS_TEST_CONFIG_FILE; 157 | _ -> ?BOSS_CONFIG_FILE 158 | end. 159 | 160 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 161 | 162 | validate_config_data(BossConfFile, {error, {Line, _Mod, [Term|_]}}) -> 163 | io:format("FATAL: Config file ~p has a syntax error on line ~p, ~n ~p ~n~n", 164 | [BossConfFile, Line, Term]), 165 | halt(1); 166 | validate_config_data(BossConfFile, {error, enoent}) -> 167 | io:format("FATAL: Config file ~p not found.~n", [BossConfFile]), 168 | halt(1); 169 | validate_config_data(BossConfFile, {ok, [BossConfig]}) -> 170 | {BossConfig, BossConfFile}. 171 | 172 | 173 | 174 | %%-------------------------------------------------------------------- 175 | %% @doc Get Boss config value app, key 176 | %% @spec boss_config_value(App, Key) -> Value | {error, Reason} 177 | %% Searchs in boss config for a given App and Key 178 | %% @end 179 | %%-------------------------------------------------------------------- 180 | boss_config_value(App, Key, BossConfig) -> 181 | case lists:keyfind(App, 1, BossConfig) of 182 | false -> 183 | {error, boss_config_app_not_found}; 184 | {App, AppConfig} -> 185 | case lists:keyfind(Key, 1, AppConfig) of 186 | false -> 187 | {error, boss_config_app_setting_not_found}; 188 | {Key, KeyConfig} -> 189 | KeyConfig 190 | end 191 | end. 192 | -------------------------------------------------------------------------------- /apps/cb_admin/src/controller/cb_admin_model_controller.erl: -------------------------------------------------------------------------------- 1 | -module(cb_admin_model_controller, [Req, SessionID]). 2 | -compile(export_all). 3 | -default_action(model). 4 | 5 | -define(RECORDS_PER_PAGE, 100). 6 | 7 | before_(_) -> 8 | cb_admin_lib:require_ip_address(Req). 9 | 10 | heartbeat('POST', [WatchName], Authorization) -> 11 | boss_news:extend_watch(list_to_integer(WatchName)), 12 | {output, ""}. 13 | 14 | watch('POST', [], Authorization) -> 15 | TopicString = Req:post_param("topic_string"), 16 | {ok, WatchId} = boss_news:watch(TopicString, fun cb_admin_lib:push_update/3, "admin"++SessionID, 60), 17 | {json, [{watch_id, WatchId}]}. 18 | 19 | events('GET', [Since], Authorization) -> 20 | io:format("Pulling events...~n", []), 21 | {ok, Timestamp, Messages} = boss_mq:pull("admin" ++ SessionID, list_to_integer(Since), 30), 22 | {json, [{messages, Messages}, {timestamp, Timestamp}]}. 23 | 24 | model('GET', [], Authorization) -> 25 | {ok, [{model_section, true}, {records, []}, 26 | {models, boss_web:get_all_models()}, 27 | {this_model, ""}, {topic_string, ""}, 28 | {timestamp, now()}]}; 29 | model('GET', [ModelName], Authorization) -> 30 | model('GET', [ModelName, "1"], Authorization); 31 | model('GET', [ModelName, PageName], Authorization) -> 32 | Page = list_to_integer(PageName), 33 | Model = list_to_atom(ModelName), 34 | RecordCount = boss_db:count(Model), 35 | Records = boss_db:find(Model, [], [{limit, ?RECORDS_PER_PAGE}, 36 | {offset, (Page - 1) * ?RECORDS_PER_PAGE}, descending]), 37 | TopicString = string:join(lists:map(fun(Record) -> Record:id() ++ ".*" end, Records), ", "), 38 | AttributesWithDataTypes = lists:map(fun(Record) -> 39 | {Record:id(), lists:map(fun({Key, Val}) -> 40 | {Key, Val, boss_db:data_type(Key, Val)} 41 | end, Record:attributes())} 42 | end, Records), 43 | AttributeNames = case length(Records) of 44 | 0 -> []; 45 | _ -> (lists:nth(1, Records)):attribute_names() 46 | end, 47 | Pages = lists:seq(1, ((RecordCount-1) div ?RECORDS_PER_PAGE)+1), 48 | {ok, 49 | [{records, AttributesWithDataTypes}, {attribute_names, AttributeNames}, 50 | {models, boss_web:get_all_models()}, {this_model, ModelName}, 51 | {pages, Pages}, {this_page, Page}, {model_section, true}, 52 | {topic_string, TopicString}, {timestamp, boss_mq:now("admin"++SessionID)}], 53 | [{"Cache-Control", "no-cache"}]}. 54 | 55 | csv('GET', [ModelName], Authorization) -> 56 | Model = list_to_atom(ModelName), 57 | [First|_] = Records = boss_db:find(Model, [], [descending]), 58 | FirstLine = [lists:foldr(fun 59 | (Attr, []) -> 60 | [atom_to_list(Attr)]; 61 | (Attr, Acc) -> 62 | [atom_to_list(Attr), ","|Acc] 63 | end, [], First:attribute_names()), "\n"], 64 | RecordLines = lists:map(fun(Record) -> 65 | [lists:foldr(fun 66 | ({_Key, Val}, []) -> 67 | [cb_admin_model_lib:encode_csv_value(Val)]; 68 | ({_Key, Val}, Acc) -> 69 | [cb_admin_model_lib:encode_csv_value(Val), ","|Acc] 70 | end, [], Record:attributes()), "\n"] 71 | end, Records), 72 | {output, [FirstLine, RecordLines], [{"Content-Type", "text/csv"}, 73 | {"Content-Disposition", "attachment;filename="++ModelName++".csv"}]}. 74 | 75 | upload('GET', [ModelName], Authorization) -> 76 | Module = list_to_atom(ModelName), 77 | DummyRecord = boss_record_lib:dummy_record(Module), 78 | {ok, [{type, ModelName}, {attributes, DummyRecord:attribute_names()}]}; 79 | upload('POST', [ModelName], Authorization) -> 80 | Module = list_to_atom(ModelName), 81 | DummyRecord = boss_record_lib:dummy_record(Module), 82 | [{uploaded_file, FileName, Location, Length}] = Req:post_files(), 83 | {ok, FileBytes} = file:read_file(Location), 84 | [Head|Rest] = cb_admin_model_lib:parse_csv(FileBytes), 85 | RecordsToSave = lists:map(fun(Line) -> 86 | {_, Record} = lists:foldl(fun(Val, {Counter, Acc}) -> 87 | AttrName = lists:nth(Counter, Head), 88 | Attr = list_to_atom(AttrName), 89 | {Counter + 1, Acc:set(Attr, Val)} 90 | end, {1, DummyRecord}, Line), 91 | Record 92 | end, Rest), 93 | % TODO put these in a transaction 94 | lists:foldl(fun(Record, ok) -> 95 | {ok, _} = Record:save(), 96 | ok 97 | end, ok, RecordsToSave), 98 | {redirect, [{action, "model"}, {model_name, ModelName}]}. 99 | 100 | show('GET', [RecordId], Authorization) -> 101 | Record = boss_db:find(RecordId), 102 | AttributesWithDataTypes = lists:map(fun({Key, Val}) -> 103 | {Key, Val, boss_db:data_type(Key, Val)} 104 | end, Record:attributes()), 105 | {ok, [{'record', Record}, {'attributes', AttributesWithDataTypes}, 106 | {'type', boss_db:type(RecordId)}, {timestamp, boss_mq:now("admin"++SessionID)}]}. 107 | 108 | edit('GET', [RecordId], Authorization) -> 109 | Record = boss_db:find(RecordId), 110 | {ok, [{'record', Record}]}; 111 | edit('POST', [RecordId], Authorization) -> 112 | Record = boss_db:find(RecordId), 113 | NewRecord = lists:foldr(fun 114 | ('id', Acc) -> 115 | Acc; 116 | (Attr, Acc) -> 117 | AttrName = atom_to_list(Attr), 118 | Val = Req:post_param(AttrName), 119 | case lists:suffix("_time", AttrName) of 120 | true -> 121 | case Val of "now" -> Acc:set(Attr, erlang:now()); 122 | _ -> Acc 123 | end; 124 | false -> Acc:set(Attr, Val) 125 | end 126 | end, Record, Record:attribute_names()), 127 | case NewRecord:save() of 128 | {ok, SavedRecord} -> 129 | {redirect, [{action, "show"}, {record_id, RecordId}]}; 130 | {error, Errors} -> 131 | {ok, [{errors, Errors}, {record, NewRecord}]} 132 | end. 133 | 134 | delete('GET', [RecordId], Authorization) -> 135 | {ok, [{'record', boss_db:find(RecordId)}]}; 136 | delete('POST', [RecordId], Authorization) -> 137 | Type = boss_db:type(RecordId), 138 | boss_db:delete(RecordId), 139 | {redirect, [{action, "model"}, {model_name, atom_to_list(Type)}]}. 140 | 141 | create(Method, [RecordType], Authorization) -> 142 | case lists:member(RecordType, boss_web:get_all_models()) of 143 | true -> 144 | Module = list_to_atom(RecordType), 145 | DummyRecord = boss_record_lib:dummy_record(Module), 146 | case Method of 147 | 'GET' -> 148 | {ok, [{type, RecordType}, {'record', DummyRecord}]}; 149 | 'POST' -> 150 | Record = lists:foldr(fun 151 | ('id', Acc) -> Acc:set(id, 'id'); 152 | (Attr, Acc) -> 153 | AttrName = atom_to_list(Attr), 154 | Val = Req:post_param(AttrName), 155 | Val1 = case lists:suffix("_time", AttrName) of 156 | true -> 157 | case Val of 158 | "now" -> erlang:now(); 159 | _ -> "" 160 | end; 161 | _ -> Val 162 | end, 163 | Acc:set(Attr, Val1) 164 | end, DummyRecord, DummyRecord:attribute_names()), 165 | case Record:save() of 166 | {ok, SavedRecord} -> 167 | {redirect, [{action, "show"}, {record_id, SavedRecord:id()}]}; 168 | {error, Errors} -> 169 | {ok, [{errors, Errors}, {type, RecordType}, {'record', Record}]} 170 | end 171 | end; 172 | _ -> 173 | {error, "Nonesuch model."} 174 | end. 175 | 176 | -------------------------------------------------------------------------------- /rel/files/draw: -------------------------------------------------------------------------------- 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/cb_admin/priv/static/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* load the default Redmine stylesheet */ 2 | @import url(application_orig.css); 3 | 4 | /* 5 | _/ _/ 6 | _/_/_/ _/_/ _/_/ _/_/_/ _/ _/ _/ _/_/_/ 7 | _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ 8 | _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ 9 | _/ _/ _/ _/_/ _/_/_/ _/_/_/ _/ _/_/_/ 10 | 11 | 12 | Theme: Innerboard 13 | Updated: 2011-01-25 14 | Author: www.ytrip.im 15 | 16 | */ 17 | 18 | html { 19 | background-color:#ccc; 20 | margin:0; 21 | padding:0; 22 | } 23 | a:link, 24 | a:visited { color:#2c64d7; } 25 | a:hover { color:#000; } 26 | body { text-align:center; 27 | font-family: "Helvetica Neue","Yahei", "Luxi Sans", "DejaVu Sans", Tahoma, "Hiragino Sans GB", STHeiti !important; 28 | color:#333; 29 | font-size:14px; 30 | margin:0; 31 | padding:0; 32 | padding-bottom:30px; 33 | text-align:center; 34 | } 35 | 36 | #wrapper { 37 | width:1200px; 38 | margin:0 auto; 39 | background:#FFF; 40 | text-align:left; 41 | -moz-border-radius: 9px; 42 | -webkit-border-radius:9px; 43 | border-radius:9px; 44 | -webkit-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2); 45 | -moz-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2); 46 | -o-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2); 47 | box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2); 48 | } 49 | 50 | #top-menu { 51 | position:relative; 52 | padding:23px 13px 6px 13px; 53 | line-height:23px; 54 | font-size:12px; 55 | height:22px; 56 | background:#FFF; 57 | margin-top:58px; 58 | border-bottom:1px solid #DDD; 59 | color:#666; 60 | -moz-border-radius:9px 9px 0 0; 61 | -webkit-border-radius:9px 9px 0 0; 62 | border-radius:9px 9px 0 0; 63 | } 64 | #top-menu a { color:green; } 65 | 66 | #header { margin: 0; width:100%; position:absolute; left:0; } 67 | #header { 68 | top:0px; 69 | background: #444; 70 | text-align:center; 71 | padding:0; 72 | overflow:hidden; 73 | height:auto; 74 | padding-top:6px; 75 | color: #333; 76 | } 77 | #header h1 { width: 1174px; margin:0 auto; font-size:18px; font-weight:bold; color:#FFF; height:22px; } 78 | #quick-search { float:none; text-align:right; width: 1174px; height:25px; margin:0 auto; margin-bottom:-25px; position:relative; } 79 | 80 | #main-menu { 81 | position:static; 82 | left:0; 83 | margin:0; 84 | background: #a6c2ed; 85 | width:auto; 86 | background: -webkit-gradient(linear, left top, left bottom, from(#a6c2ed), to(#7a9fd3)); 87 | background: -moz-linear-gradient(top, #a6c2ed, #b7d0f4); 88 | } 89 | #main-menu ul { 90 | width:1174px; 91 | padding:0 13px; 92 | padding: 0; padding-top:10px; 93 | height: 26px; overflow:hidden; 94 | text-align:left; 95 | margin:0 auto; 96 | } 97 | #main-menu li {margin: 0; padding: 0; margin-left:10px;} 98 | #main-menu li {margin: 0; padding: 0;display: inline;list-style: none; border: none;} 99 | #main-menu li a:link, 100 | #main-menu li a:visited { 101 | text-decoration: none; 102 | float: left; 103 | font-size: 13px; 104 | line-height: 15px; 105 | font-weight: normal; 106 | padding: 6px 13px 5px; 107 | margin: 0 2px; 108 | color: #FFF; 109 | -moz-border-radius:4px 4px 0 0; 110 | -webkit-border-radius:4px 4px 0 0; 111 | border-radius:4px 4px 0 0; 112 | background-color: #588ddd; 113 | } 114 | #main-menu li a.selected, 115 | #main-menu li a.selected:hover { 116 | color: #333; 117 | text-decoration: none; 118 | padding-bottom: 9px; 119 | background-color: #fff; 120 | } 121 | #main-menu li a:hover { 122 | color: #fff; 123 | background-color: #51a521; 124 | } 125 | 126 | 127 | 128 | #main { background:#F0F0F0; } 129 | #content { 130 | background: #fff; 131 | padding:13px; 132 | padding-top:25px; 133 | font-size:14px; 134 | line-height:160%; 135 | width: 870px; 136 | } 137 | #footer { border: 0px; clear:both; color:#666666; 138 | -moz-border-radius: 0 9px 9px 0; 139 | -webkit-border-radius: 0 0 9px 0; 140 | border-radius: 0 0 9px 9px; 141 | text-align:center; border-top:1px solid #EEE;padding:13px; padding-top:30px;} 142 | 143 | 144 | #sidebar { width:260px; margin-right:10px; line-height:200%; padding:13px;} 145 | #sidebar h3 { color: #333; font-size:12px; border-bottom:1px solid #CCC; padding:6px 0; } 146 | 147 | 148 | p.subtitle { 149 | font-style:normal; 150 | } 151 | 152 | /* Headers */ 153 | h1, h2, h3, h4 { font-family: helvetica neue, helvetica, "microsoft sans serif", arial, sans-serif; } 154 | .wiki h1, .wiki h2, .wiki h3, .wiki h4 { font-family: helvetica neue, helvetica, "microsoft sans serif", arial, sans-serif; } 155 | h1 { color: #cde9a7; font-size: 24px; font-weight: normal; margin:0pt 0pt 0pt 0.25em; padding:0pt 0pt 10px; text-align:left; } 156 | h2, h3, h4, .wiki h1, .wiki h2, .wiki h3 { border-bottom: 0px;} 157 | 158 | h2, .wiki h1 { 159 | } 160 | .wiki h1 { font-family: helvetica, "microsoft sans serif", arial, sans-serif; } 161 | .wiki h2 { background-color: #fff; } 162 | 163 | h3, h4 { font-weight: bold;color:#333;} 164 | 165 | 166 | /* Links */ 167 | 168 | 169 | /* Settings menu */ 170 | 171 | #content .tabs ul li a { 172 | font-weight: normal; 173 | font-size: .9em; 174 | border-top: 1px #fff solid; 175 | border-right: 1px #fff solid; 176 | border-left: 1px #fff solid; 177 | background: #fff; 178 | -moz-border-radius-topleft: .3em; -webkit-border-top-left-radius: .3em; 179 | -moz-border-radius-topright: .3em; -webkit-border-top-right-radius: .3em; 180 | } 181 | 182 | #content .tabs ul li a:hover { 183 | background: #fff; 184 | color: #FFA500; 185 | } 186 | 187 | #content .tabs ul li a.selected { 188 | font-weight: normal; 189 | color:#455138; 190 | -moz-border-radius-topleft: .3em; -webkit-border-top-left-radius: .3em; 191 | -moz-border-radius-topright: .3em; -webkit-border-top-right-radius: .3em; 192 | } 193 | 194 | /* Tables */ 195 | table.list { border: none; } 196 | table.list { border:1px solid #ddd; width:100%; } 197 | table.list tr td {border-top: 1px solid #ddd; border-left: 1px solid #ddd; padding:5px;background:#F9F9F9;} 198 | table.list tr.odd td {background:#FFF; } 199 | table.list tr:hover td{ background:#f2f7ff; cursor:default;} 200 | table.list tr td.first { border-left: 0px;} 201 | table.list th { 202 | color:#555; 203 | border-top:1px solid #DDD; 204 | background:#F0F0F0; 205 | padding:5px; 206 | } 207 | table.list thead th { 208 | background:#f5f5f5; 209 | border-top: 0px; 210 | background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f0f0f0)); 211 | background: -moz-linear-gradient(top, #fff, #f0f0f0); 212 | } 213 | table.list th a { font-weight:normal; color:#555; } 214 | /* Issues grid styles by priorities (provided by Wynn Netherland) */ 215 | table.list tr.issue a { color: #3c3c3c; } 216 | 217 | 218 | p.breadcrumb { 219 | background-color:#EEEEEE; 220 | border-bottom:1px solid white; 221 | font-size:0.9em; 222 | margin:-6px -10px 6px; 223 | padding:6px; 224 | text-indent:15px; 225 | } 226 | 227 | /* Fields */ 228 | input[type='text'], input[type='password'], textarea { font-size: 13px; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; } 229 | input[type="text"], textarea, select { padding: 2px; } 230 | input[type="text"]:focus, textarea:focus, select:focus { } 231 | option { } 232 | input#issue_subject { font-size: 200%; width: 90%; } 233 | input#issue_subject, #project_description { width: 92.5%; } 234 | textarea#issue_description { width: 93%;} 235 | 236 | /* Misc */ 237 | input[type="text"], textarea, select { 238 | border: 1px #989898 solid; 239 | background-color: #F5F5F5; 240 | -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; 241 | -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; 242 | -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; 243 | -moz-border-radius-bottomright: 3px; -webkit-border-bottom-right-radius: 3px; 244 | } 245 | 246 | .nodata, #login-form table, .flash.notice { 247 | border-width: 1px; 248 | -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; 249 | -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; 250 | -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; 251 | -moz-border-radius-bottomright: 3px; -webkit-border-bottom-right-radius: 3px; 252 | } 253 | 254 | .box { 255 | background-color: #fcfcfc; 256 | border-top: 1px #eee solid; 257 | border-right: 1px #ccc solid; 258 | border-bottom: 1px #ccc solid; 259 | border-left: 1px #eee solid; 260 | -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; 261 | -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; 262 | -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; 263 | -moz-border-radius-bottomright: 3px; -webkit-border-bottom-right-radius: 3px; 264 | } 265 | 266 | #attachments_fields input[type="text"] { 267 | width: 39%; 268 | } 269 | 270 | .jstElements { margin-right: 3.5em;} 271 | .tabular label { font-weight: normal;} 272 | 273 | .contextual { margin-top: 4px; margin-right: 1em; } 274 | .contextual a{ color:#333;} 275 | 276 | 277 | table.list thead th { 278 | font-size:.8em; 279 | } 280 | 281 | tr.message td.last_message { 282 | font-size:.9em; 283 | } 284 | 285 | div#activity dl, #search-results { 286 | margin-left:0em; 287 | } 288 | div#activity dd, #search-results dd { 289 | font-size:1em; 290 | } 291 | 292 | span.description { margin: .5em 0;} 293 | 294 | hr { background:#eee none repeat scroll 0% 0%; } 295 | 296 | table.progress { border:1px solid #51A521; } 297 | table.progress td.closed { background:#66c427; } 298 | 299 | h3.version a { font-weight:bold; margin-top:13px; } 300 | 301 | 302 | div.issue { 303 | background: #FFF; 304 | -moz-border-radius: 8px; 305 | -webkit-border-radius: 8px; 306 | border-radius: 8px; 307 | padding:10px; 308 | } 309 | div.issue h3 { color:#c41100; } 310 | div.issue hr { background:none; border-bottom:1px dashed #DDD; margin-top:8px;} 311 | div.issue p { margin:0; padding:8px 0; line-height:180%;} 312 | 313 | div.attachments p { 314 | -moz-border-radius:14px 0 14px 0 ; 315 | -webkit-border-radius:14px 0 14px 0 ; 316 | border-radius:14px 0 14px 0 ; 317 | background: #e6f2c1; /* Image fallback */ 318 | padding:5px 8px; 319 | display:inline-block; 320 | margin-right:20px; 321 | margin-bottom:8px; 322 | color:#999; 323 | } 324 | div.attachments a { color:#118200; font-weight:bold;} 325 | 326 | .my-project { padding-left:0px; padding-right: 18px; background: url(/images/fav.png) no-repeat right 50%; } 327 | -------------------------------------------------------------------------------- /apps/cb_admin/priv/static/stylesheets/application_orig.css: -------------------------------------------------------------------------------- 1 | body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; } 2 | 3 | h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;} 4 | h1 {margin:0; padding:0; font-size: 24px;} 5 | h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;} 6 | h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;} 7 | h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;} 8 | 9 | /***** Layout *****/ 10 | #wrapper {background: white;} 11 | 12 | #top-menu {background: #2C4056; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;} 13 | #top-menu ul {margin: 0; padding: 0;} 14 | #top-menu li { 15 | float:left; 16 | list-style-type:none; 17 | margin: 0px 0px 0px 0px; 18 | padding: 0px 0px 0px 0px; 19 | white-space:nowrap; 20 | } 21 | #top-menu a {color: #fff; margin-right: 8px; font-weight: bold;} 22 | #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; } 23 | 24 | #account {float:right;} 25 | 26 | #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;} 27 | #header a {color:#f8f8f8;} 28 | #header h1 a.ancestor { font-size: 80%; } 29 | #quick-search {float:right;} 30 | 31 | #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;} 32 | #main-menu ul {margin: 0; padding: 0;} 33 | #main-menu li { 34 | float:left; 35 | list-style-type:none; 36 | margin: 0px 2px 0px 0px; 37 | padding: 0px 0px 0px 0px; 38 | white-space:nowrap; 39 | } 40 | #main-menu li a { 41 | display: block; 42 | color: #fff; 43 | text-decoration: none; 44 | font-weight: bold; 45 | margin: 0; 46 | padding: 4px 10px 4px 10px; 47 | } 48 | #main-menu li a:hover {background:#759FCF; color:#fff;} 49 | #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;} 50 | 51 | #admin-menu ul {margin: 0; padding: 0;} 52 | #admin-menu li {margin: 0; padding: 0 0 12px 0; list-style-type:none;} 53 | 54 | #admin-menu a { background-position: 0% 40%; background-repeat: no-repeat; padding-left: 20px; padding-top: 2px; padding-bottom: 3px;} 55 | #admin-menu a.projects { background-image: url(../images/projects.png); } 56 | #admin-menu a.users { background-image: url(../images/user.png); } 57 | #admin-menu a.groups { background-image: url(../images/group.png); } 58 | #admin-menu a.roles { background-image: url(../images/database_key.png); } 59 | #admin-menu a.trackers { background-image: url(../images/ticket.png); } 60 | #admin-menu a.issue_statuses { background-image: url(../images/ticket_edit.png); } 61 | #admin-menu a.workflows { background-image: url(../images/ticket_go.png); } 62 | #admin-menu a.custom_fields { background-image: url(../images/textfield.png); } 63 | #admin-menu a.enumerations { background-image: url(../images/text_list_bullets.png); } 64 | #admin-menu a.settings { background-image: url(../images/changeset.png); } 65 | #admin-menu a.plugins { background-image: url(../images/plugin.png); } 66 | #admin-menu a.info { background-image: url(../images/help.png); } 67 | 68 | #main {background-color:#EEEEEE;} 69 | 70 | #sidebar{ float: right; width: 22%; position: relative; z-index: 9; padding: 0; margin: 0;} 71 | * html #sidebar{ width: 22%; } 72 | #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; } 73 | #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; } 74 | * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; } 75 | #sidebar .contextual { margin-right: 1em; } 76 | 77 | #content { width: 75%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; } 78 | * html #content{ width: 75%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;} 79 | html>body #content { min-height: 600px; } 80 | * html body #content { height: 600px; } /* IE */ 81 | 82 | #main.nosidebar #sidebar{ display: none; } 83 | #main.nosidebar #content{ width: auto; border-right: 0; } 84 | 85 | #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;} 86 | 87 | #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; } 88 | #login-form table td {padding: 6px;} 89 | #login-form label {font-weight: bold;} 90 | #login-form input#username, #login-form input#password { width: 300px; } 91 | 92 | input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; } 93 | 94 | .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; } 95 | 96 | /***** Links *****/ 97 | a, a:link, a:visited{ color: #2A5685; text-decoration: none; } 98 | a:hover, a:active{ color: #c61a1a; text-decoration: underline;} 99 | a img{ border: 0; } 100 | 101 | a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; } 102 | 103 | /***** Tables *****/ 104 | table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; } 105 | table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; } 106 | table.list td { vertical-align: top; } 107 | table.list td.id { width: 2%; text-align: center;} 108 | table.list td.checkbox { width: 15px; padding: 0px;} 109 | table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; } 110 | table.list td.buttons a { padding-right: 0.6em; } 111 | table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; } 112 | 113 | tr.project td.name a { white-space:nowrap; } 114 | 115 | tr.project.idnt td.name a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;} 116 | tr.project.idnt-1 td.name {padding-left: 0.5em;} 117 | tr.project.idnt-2 td.name {padding-left: 2em;} 118 | tr.project.idnt-3 td.name {padding-left: 3.5em;} 119 | tr.project.idnt-4 td.name {padding-left: 5em;} 120 | tr.project.idnt-5 td.name {padding-left: 6.5em;} 121 | tr.project.idnt-6 td.name {padding-left: 8em;} 122 | tr.project.idnt-7 td.name {padding-left: 9.5em;} 123 | tr.project.idnt-8 td.name {padding-left: 11em;} 124 | tr.project.idnt-9 td.name {padding-left: 12.5em;} 125 | 126 | tr.issue { text-align: center; white-space: nowrap; } 127 | tr.issue td.subject, tr.issue td.category, td.assigned_to { white-space: normal; } 128 | tr.issue td.subject { text-align: left; } 129 | tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;} 130 | 131 | tr.issue.idnt td.subject a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;} 132 | tr.issue.idnt-1 td.subject {padding-left: 0.5em;} 133 | tr.issue.idnt-2 td.subject {padding-left: 2em;} 134 | tr.issue.idnt-3 td.subject {padding-left: 3.5em;} 135 | tr.issue.idnt-4 td.subject {padding-left: 5em;} 136 | tr.issue.idnt-5 td.subject {padding-left: 6.5em;} 137 | tr.issue.idnt-6 td.subject {padding-left: 8em;} 138 | tr.issue.idnt-7 td.subject {padding-left: 9.5em;} 139 | tr.issue.idnt-8 td.subject {padding-left: 11em;} 140 | tr.issue.idnt-9 td.subject {padding-left: 12.5em;} 141 | 142 | tr.entry { border: 1px solid #f8f8f8; } 143 | tr.entry td { white-space: nowrap; } 144 | tr.entry td.filename { width: 30%; } 145 | tr.entry td.size { text-align: right; font-size: 90%; } 146 | tr.entry td.revision, tr.entry td.author { text-align: center; } 147 | tr.entry td.age { text-align: right; } 148 | tr.entry.file td.filename a { margin-left: 16px; } 149 | 150 | tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;} 151 | tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);} 152 | 153 | tr.changeset td.author { text-align: center; width: 15%; } 154 | tr.changeset td.committed_on { text-align: center; width: 15%; } 155 | 156 | table.files tr.file td { text-align: center; } 157 | table.files tr.file td.filename { text-align: left; padding-left: 24px; } 158 | table.files tr.file td.digest { font-size: 80%; } 159 | 160 | table.members td.roles, table.memberships td.roles { width: 45%; } 161 | 162 | tr.message { height: 2.6em; } 163 | tr.message td.subject { padding-left: 20px; } 164 | tr.message td.created_on { white-space: nowrap; } 165 | tr.message td.last_message { font-size: 80%; white-space: nowrap; } 166 | tr.message.locked td.subject { background: url(../images/locked.png) no-repeat 0 1px; } 167 | tr.message.sticky td.subject { background: url(../images/bullet_go.png) no-repeat 0 1px; font-weight: bold; } 168 | 169 | tr.version.closed, tr.version.closed a { color: #999; } 170 | tr.version td.name { padding-left: 20px; } 171 | tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; } 172 | tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; } 173 | 174 | tr.user td { width:13%; } 175 | tr.user td.email { width:18%; } 176 | tr.user td { white-space: nowrap; } 177 | tr.user.locked, tr.user.registered { color: #aaa; } 178 | tr.user.locked a, tr.user.registered a { color: #aaa; } 179 | 180 | tr.time-entry { text-align: center; white-space: nowrap; } 181 | tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; } 182 | td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; } 183 | td.hours .hours-dec { font-size: 0.9em; } 184 | 185 | table.plugins td { vertical-align: middle; } 186 | table.plugins td.configure { text-align: right; padding-right: 1em; } 187 | table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; } 188 | table.plugins span.description { display: block; font-size: 0.9em; } 189 | table.plugins span.url { display: block; font-size: 0.9em; } 190 | 191 | table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; } 192 | table.list tbody tr.group span.count { color: #aaa; font-size: 80%; } 193 | 194 | table.list tbody tr:hover { background-color:#ffffdd; } 195 | table.list tbody tr.group:hover { background-color:inherit; } 196 | table td {padding:2px;} 197 | table p {margin:0;} 198 | .odd {background-color:#f6f7f8;} 199 | .even {background-color: #fff;} 200 | 201 | a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; } 202 | a.sort.asc { background-image: url(../images/sort_asc.png); } 203 | a.sort.desc { background-image: url(../images/sort_desc.png); } 204 | 205 | table.attributes { width: 100% } 206 | table.attributes th { vertical-align: top; text-align: left; } 207 | table.attributes td { vertical-align: top; } 208 | 209 | table.boards a.board, h3.comments { background: url(../images/comment.png) no-repeat 0% 50%; padding-left: 20px; } 210 | 211 | td.center {text-align:center;} 212 | 213 | h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; } 214 | 215 | div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; } 216 | div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; } 217 | div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; } 218 | div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; } 219 | 220 | #watchers ul {margin: 0; padding: 0;} 221 | #watchers li {list-style-type:none;margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;} 222 | #watchers select {width: 95%; display: block;} 223 | #watchers a.delete {opacity: 0.4;} 224 | #watchers a.delete:hover {opacity: 1;} 225 | #watchers img.gravatar {vertical-align: middle;margin: 0 4px 2px 0;} 226 | 227 | .highlight { background-color: #FCFD8D;} 228 | .highlight.token-1 { background-color: #faa;} 229 | .highlight.token-2 { background-color: #afa;} 230 | .highlight.token-3 { background-color: #aaf;} 231 | 232 | .box{ 233 | padding:6px; 234 | margin-bottom: 10px; 235 | background-color:#f6f6f6; 236 | color:#505050; 237 | line-height:1.5em; 238 | border: 1px solid #e4e4e4; 239 | } 240 | 241 | div.square { 242 | border: 1px solid #999; 243 | float: left; 244 | margin: .3em .4em 0 .4em; 245 | overflow: hidden; 246 | width: .6em; height: .6em; 247 | } 248 | .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;} 249 | .contextual input, .contextual select {font-size:0.9em;} 250 | .message .contextual { margin-top: 0; } 251 | 252 | .splitcontentleft{float:left; width:49%;} 253 | .splitcontentright{float:right; width:49%;} 254 | form {display: inline;} 255 | input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;} 256 | fieldset {border: 1px solid #e4e4e4; margin:0;} 257 | legend {color: #484848;} 258 | hr { width: 100%; height: 1px; background: #ccc; border: 0;} 259 | blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;} 260 | blockquote blockquote { margin-left: 0;} 261 | acronym { border-bottom: 1px dotted; cursor: help; } 262 | textarea.wiki-edit { width: 99%; } 263 | li p {margin-top: 0;} 264 | div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;} 265 | p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;} 266 | p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; } 267 | p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; } 268 | 269 | div.issue div.subject div div { padding-left: 16px; } 270 | div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;} 271 | div.issue div.subject>div>p { margin-top: 0.5em; } 272 | div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;} 273 | 274 | #issue_tree table.issues { border: 0; } 275 | #issue_tree td.checkbox {display:none;} 276 | 277 | fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; } 278 | fieldset.collapsible legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; } 279 | fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_collapsed.png); } 280 | 281 | fieldset#date-range p { margin: 2px 0 2px 0; } 282 | fieldset#filters table { border-collapse: collapse; } 283 | fieldset#filters table td { padding: 0; vertical-align: middle; } 284 | fieldset#filters tr.filter { height: 2em; } 285 | fieldset#filters td.add-filter { text-align: right; vertical-align: top; } 286 | .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; } 287 | 288 | div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;} 289 | div#issue-changesets .changeset { padding: 4px;} 290 | div#issue-changesets .changeset { border-bottom: 1px solid #ddd; } 291 | div#issue-changesets p { margin-top: 0; margin-bottom: 1em;} 292 | 293 | div#activity dl, #search-results { margin-left: 2em; } 294 | div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; } 295 | div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; } 296 | div#activity dt.me .time { border-bottom: 1px solid #999; } 297 | div#activity dt .time { color: #777; font-size: 80%; } 298 | div#activity dd .description, #search-results dd .description { font-style: italic; } 299 | div#activity span.project:after, #search-results span.project:after { content: " -"; } 300 | div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; } 301 | 302 | #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; } 303 | 304 | div#search-results-counts {float:right;} 305 | div#search-results-counts ul { margin-top: 0.5em; } 306 | div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; } 307 | 308 | dt.issue { background-image: url(../images/ticket.png); } 309 | dt.issue-edit { background-image: url(../images/ticket_edit.png); } 310 | dt.issue-closed { background-image: url(../images/ticket_checked.png); } 311 | dt.issue-note { background-image: url(../images/ticket_note.png); } 312 | dt.changeset { background-image: url(../images/changeset.png); } 313 | dt.news { background-image: url(../images/news.png); } 314 | dt.message { background-image: url(../images/message.png); } 315 | dt.reply { background-image: url(../images/comments.png); } 316 | dt.wiki-page { background-image: url(../images/wiki_edit.png); } 317 | dt.attachment { background-image: url(../images/attachment.png); } 318 | dt.document { background-image: url(../images/document.png); } 319 | dt.project { background-image: url(../images/projects.png); } 320 | dt.time-entry { background-image: url(../images/time.png); } 321 | 322 | #search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); } 323 | 324 | div#roadmap .related-issues { margin-bottom: 1em; } 325 | div#roadmap .related-issues td.checkbox { display: none; } 326 | div#roadmap .wiki h1:first-child { display: none; } 327 | div#roadmap .wiki h1 { font-size: 120%; } 328 | div#roadmap .wiki h2 { font-size: 110%; } 329 | 330 | div#version-summary { float:right; width:380px; margin-left: 16px; margin-bottom: 16px; background-color: #fff; } 331 | div#version-summary fieldset { margin-bottom: 1em; } 332 | div#version-summary .total-hours { text-align: right; } 333 | 334 | table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; } 335 | table#time-report tbody tr { font-style: italic; color: #777; } 336 | table#time-report tbody tr.last-level { font-style: normal; color: #555; } 337 | table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; } 338 | table#time-report .hours-dec { font-size: 0.9em; } 339 | 340 | form .attributes { margin-bottom: 8px; } 341 | form .attributes p { padding-top: 1px; padding-bottom: 2px; } 342 | form .attributes select { min-width: 50%; } 343 | 344 | ul.projects { margin: 0; padding-left: 1em; } 345 | ul.projects.root { margin: 0; padding: 0; } 346 | ul.projects ul.projects { border-left: 3px solid #e0e0e0; } 347 | ul.projects li.root { list-style-type:none; margin-bottom: 1em; } 348 | ul.projects li.child { list-style-type:none; margin-top: 1em;} 349 | ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; } 350 | .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; } 351 | 352 | #tracker_project_ids ul { margin: 0; padding-left: 1em; } 353 | #tracker_project_ids li { list-style-type:none; } 354 | 355 | ul.properties {padding:0; font-size: 0.9em; color: #777;} 356 | ul.properties li {list-style-type:none;} 357 | ul.properties li span {font-style:italic;} 358 | 359 | .total-hours { font-size: 110%; font-weight: bold; } 360 | .total-hours span.hours-int { font-size: 120%; } 361 | 362 | .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;} 363 | #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; } 364 | 365 | #workflow_copy_form select { width: 200px; } 366 | 367 | .pagination {font-size: 90%} 368 | p.pagination {margin-top:8px;} 369 | 370 | /***** Tabular forms ******/ 371 | .tabular p{ 372 | margin: 0; 373 | padding: 5px 0 8px 0; 374 | padding-left: 180px; /*width of left column containing the label elements*/ 375 | height: 1%; 376 | clear:left; 377 | } 378 | 379 | html>body .tabular p {overflow:hidden;} 380 | 381 | .tabular label{ 382 | font-weight: bold; 383 | float: left; 384 | text-align: right; 385 | margin-left: -180px; /*width of left column*/ 386 | width: 175px; /*width of labels. Should be smaller than left column to create some right 387 | margin*/ 388 | } 389 | 390 | .tabular label.floating{ 391 | font-weight: normal; 392 | margin-left: 0px; 393 | text-align: left; 394 | width: 270px; 395 | } 396 | 397 | .tabular label.block{ 398 | font-weight: normal; 399 | margin-left: 0px !important; 400 | text-align: left; 401 | float: none; 402 | display: block; 403 | width: auto; 404 | } 405 | 406 | .tabular label.inline{ 407 | float:none; 408 | margin-left: 5px !important; 409 | width: auto; 410 | } 411 | 412 | input#time_entry_comments { width: 90%;} 413 | 414 | #preview fieldset {margin-top: 1em; background: url(../images/draft.png)} 415 | 416 | .tabular.settings p{ padding-left: 300px; } 417 | .tabular.settings label{ margin-left: -300px; width: 295px; } 418 | .tabular.settings textarea { width: 99%; } 419 | 420 | fieldset.settings label { display: block; } 421 | 422 | .required {color: #bb0000;} 423 | .summary {font-style: italic;} 424 | 425 | #attachments_fields input[type=text] {margin-left: 8px; } 426 | 427 | div.attachments { margin-top: 12px; } 428 | div.attachments p { margin:4px 0 2px 0; } 429 | div.attachments img { vertical-align: middle; } 430 | div.attachments span.author { font-size: 0.9em; color: #888; } 431 | 432 | p.other-formats { text-align: right; font-size:0.9em; color: #666; } 433 | .other-formats span + span:before { content: "| "; } 434 | 435 | a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; } 436 | 437 | /* Project members tab */ 438 | div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% } 439 | div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% } 440 | div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; } 441 | div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; } 442 | div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; } 443 | div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; } 444 | 445 | table.members td.group { padding-left: 20px; background: url(../images/group.png) no-repeat 0% 50%; } 446 | 447 | input#principal_search, input#user_search {width:100%} 448 | 449 | * html div#tab-content-members fieldset div { height: 450px; } 450 | 451 | /***** Flash & error messages ****/ 452 | #errorExplanation, div.flash, .nodata, .warning { 453 | padding: 4px 4px 4px 30px; 454 | margin-bottom: 12px; 455 | font-size: 1.1em; 456 | border: 2px solid; 457 | } 458 | 459 | div.flash {margin-top: 8px;} 460 | 461 | div.flash.error, #errorExplanation { 462 | background: url(../images/exclamation.png) 8px 50% no-repeat; 463 | background-color: #ffe3e3; 464 | border-color: #dd0000; 465 | color: #880000; 466 | } 467 | 468 | div.flash.notice { 469 | background: url(../images/true.png) 8px 5px no-repeat; 470 | background-color: #dfffdf; 471 | border-color: #9fcf9f; 472 | color: #005f00; 473 | } 474 | 475 | div.flash.warning { 476 | background: url(../images/warning.png) 8px 5px no-repeat; 477 | background-color: #FFEBC1; 478 | border-color: #FDBF3B; 479 | color: #A6750C; 480 | text-align: left; 481 | } 482 | 483 | .nodata, .warning { 484 | text-align: center; 485 | background-color: #FFEBC1; 486 | border-color: #FDBF3B; 487 | color: #A6750C; 488 | } 489 | 490 | #errorExplanation ul { font-size: 0.9em;} 491 | #errorExplanation h2, #errorExplanation p { display: none; } 492 | 493 | /***** Ajax indicator ******/ 494 | #ajax-indicator { 495 | position: absolute; /* fixed not supported by IE */ 496 | background-color:#eee; 497 | border: 1px solid #bbb; 498 | top:35%; 499 | left:40%; 500 | width:20%; 501 | font-weight:bold; 502 | text-align:center; 503 | padding:0.6em; 504 | z-index:100; 505 | filter:alpha(opacity=50); 506 | opacity: 0.5; 507 | } 508 | 509 | html>body #ajax-indicator { position: fixed; } 510 | 511 | #ajax-indicator span { 512 | background-position: 0% 40%; 513 | background-repeat: no-repeat; 514 | background-image: url(../images/loading.gif); 515 | padding-left: 26px; 516 | vertical-align: bottom; 517 | } 518 | 519 | /***** Calendar *****/ 520 | table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;} 521 | table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; } 522 | table.cal thead th.week-number {width: auto;} 523 | table.cal tbody tr {height: 100px;} 524 | table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;} 525 | table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;} 526 | table.cal td p.day-num {font-size: 1.1em; text-align:right;} 527 | table.cal td.odd p.day-num {color: #bbb;} 528 | table.cal td.today {background:#ffffdd;} 529 | table.cal td.today p.day-num {font-weight: bold;} 530 | table.cal .starting a, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;} 531 | table.cal .ending a, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;} 532 | table.cal .starting.ending a, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;} 533 | p.cal.legend span {display:block;} 534 | 535 | /***** Tooltips ******/ 536 | .tooltip{position:relative;z-index:24;} 537 | .tooltip:hover{z-index:25;color:#000;} 538 | .tooltip span.tip{display: none; text-align:left;} 539 | 540 | div.tooltip:hover span.tip{ 541 | display:block; 542 | position:absolute; 543 | top:12px; left:24px; width:270px; 544 | border:1px solid #555; 545 | background-color:#fff; 546 | padding: 4px; 547 | font-size: 0.8em; 548 | color:#505050; 549 | } 550 | 551 | /***** Progress bar *****/ 552 | table.progress { 553 | border: 1px solid #D7D7D7; 554 | border-collapse: collapse; 555 | border-spacing: 0pt; 556 | empty-cells: show; 557 | text-align: center; 558 | float:left; 559 | margin: 1px 6px 1px 0px; 560 | } 561 | 562 | table.progress td { height: 0.9em; } 563 | table.progress td.closed { background: #BAE0BA none repeat scroll 0%; } 564 | table.progress td.done { background: #DEF0DE none repeat scroll 0%; } 565 | table.progress td.open { background: #FFF none repeat scroll 0%; } 566 | p.pourcent {font-size: 80%;} 567 | p.progress-info {clear: left; font-style: italic; font-size: 80%;} 568 | 569 | /***** Tabs *****/ 570 | #content .tabs {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;} 571 | #content .tabs ul {margin:0; position:absolute; bottom:0; padding-left:1em; width: 2000px; border-bottom: 1px solid #bbbbbb;} 572 | #content .tabs ul li { 573 | float:left; 574 | list-style-type:none; 575 | white-space:nowrap; 576 | margin-right:8px; 577 | background:#fff; 578 | position:relative; 579 | margin-bottom:-1px; 580 | } 581 | #content .tabs ul li a{ 582 | display:block; 583 | font-size: 0.9em; 584 | text-decoration:none; 585 | line-height:1.3em; 586 | padding:4px 6px 4px 6px; 587 | border: 1px solid #ccc; 588 | border-bottom: 1px solid #bbbbbb; 589 | background-color: #eeeeee; 590 | color:#777; 591 | font-weight:bold; 592 | } 593 | 594 | #content .tabs ul li a:hover { 595 | background-color: #ffffdd; 596 | text-decoration:none; 597 | } 598 | 599 | #content .tabs ul li a.selected { 600 | background-color: #fff; 601 | border: 1px solid #bbbbbb; 602 | border-bottom: 1px solid #fff; 603 | } 604 | 605 | #content .tabs ul li a.selected:hover { 606 | background-color: #fff; 607 | } 608 | 609 | div.tabs-buttons { position:absolute; right: 0; width: 48px; height: 24px; background: white; bottom: 0; border-bottom: 1px solid #bbbbbb; } 610 | 611 | button.tab-left, button.tab-right { 612 | font-size: 0.9em; 613 | cursor: pointer; 614 | height:24px; 615 | border: 1px solid #ccc; 616 | border-bottom: 1px solid #bbbbbb; 617 | position:absolute; 618 | padding:4px; 619 | width: 20px; 620 | bottom: -1px; 621 | } 622 | 623 | button.tab-left { 624 | right: 20px; 625 | background: #eeeeee url(../images/bullet_arrow_left.png) no-repeat 50% 50%; 626 | } 627 | 628 | button.tab-right { 629 | right: 0; 630 | background: #eeeeee url(../images/bullet_arrow_right.png) no-repeat 50% 50%; 631 | } 632 | 633 | /***** Auto-complete *****/ 634 | div.autocomplete { 635 | position:absolute; 636 | width:400px; 637 | margin:0; 638 | padding:0; 639 | } 640 | div.autocomplete ul { 641 | list-style-type:none; 642 | margin:0; 643 | padding:0; 644 | } 645 | div.autocomplete ul li { 646 | list-style-type:none; 647 | display:block; 648 | margin:-1px 0 0 0; 649 | padding:2px; 650 | cursor:pointer; 651 | font-size: 90%; 652 | border: 1px solid #ccc; 653 | border-left: 1px solid #ccc; 654 | border-right: 1px solid #ccc; 655 | background-color:white; 656 | } 657 | div.autocomplete ul li.selected { background-color: #ffb;} 658 | div.autocomplete ul li span.informal { 659 | font-size: 80%; 660 | color: #aaa; 661 | } 662 | 663 | #parent_issue_candidates ul li {width: 500px;} 664 | 665 | /***** Diff *****/ 666 | .diff_out { background: #fcc; } 667 | .diff_in { background: #cfc; } 668 | 669 | /***** Wiki *****/ 670 | div.wiki table { 671 | border: 1px solid #505050; 672 | border-collapse: collapse; 673 | margin-bottom: 1em; 674 | } 675 | 676 | div.wiki table, div.wiki td, div.wiki th { 677 | border: 1px solid #bbb; 678 | padding: 4px; 679 | } 680 | 681 | div.wiki .external { 682 | background-position: 0% 60%; 683 | background-repeat: no-repeat; 684 | padding-left: 12px; 685 | background-image: url(../images/external.png); 686 | } 687 | 688 | div.wiki a.new { 689 | color: #b73535; 690 | } 691 | 692 | div.wiki pre { 693 | margin: 1em 1em 1em 1.6em; 694 | padding: 2px 2px 2px 0; 695 | background-color: #fafafa; 696 | border: 1px solid #dadada; 697 | width:auto; 698 | overflow-x: auto; 699 | } 700 | 701 | div.wiki ul.toc { 702 | background-color: #ffffdd; 703 | border: 1px solid #e4e4e4; 704 | padding: 4px; 705 | line-height: 1.2em; 706 | margin-bottom: 12px; 707 | margin-right: 12px; 708 | margin-left: 0; 709 | display: table 710 | } 711 | * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */ 712 | 713 | div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; } 714 | div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; } 715 | div.wiki ul.toc li { list-style-type:none;} 716 | div.wiki ul.toc li.heading2 { margin-left: 6px; } 717 | div.wiki ul.toc li.heading3 { margin-left: 12px; font-size: 0.8em; } 718 | 719 | div.wiki ul.toc a { 720 | font-size: 0.9em; 721 | font-weight: normal; 722 | text-decoration: none; 723 | color: #606060; 724 | } 725 | div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;} 726 | 727 | a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; } 728 | a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; } 729 | h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; } 730 | 731 | div.wiki img { vertical-align: middle; } 732 | 733 | --------------------------------------------------------------------------------