├── .gitignore
├── .idea
├── .name
├── dictionaries
│ └── pablo.xml
├── encodings.xml
├── fs-mgmt.iml
├── inspectionProfiles
│ ├── Project_Default.xml
│ └── profiles_settings.xml
├── jsLibraryMappings.xml
├── modules.xml
├── runConfigurations
│ ├── Grunt_Build.xml
│ ├── Grunt_Bundle.xml
│ ├── Run.xml
│ └── Run2.xml
├── scopes
│ └── scope_settings.xml
├── vcs.xml
└── watcherTasks.xml
├── Gruntfile.js
├── README.md
├── app
├── fonts
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.svg
│ ├── glyphicons-halflings-regular.ttf
│ ├── glyphicons-halflings-regular.woff
│ └── glyphicons-halflings-regular.woff2
├── html
│ ├── calls
│ │ └── main.html
│ ├── conferences
│ │ └── main.html
│ └── main.html
├── images
│ ├── icon.icns
│ ├── icon.ico
│ └── icon.png
└── package.json
├── bower.json
├── images
└── blank.png
├── package.json
├── screenshots
├── v0.2.0
│ ├── 1.png
│ └── 2.png
├── v0.3.0
│ ├── 1.png
│ └── 2.png
├── v0.4.0
│ ├── 1.png
│ ├── 2.png
│ └── 3.png
├── v0.5.0
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ └── 5.png
├── v1.1.0
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ ├── 5.png
│ ├── 6.png
│ └── 7.png
└── v1.2.0
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ ├── 5.png
│ ├── 6.png
│ └── 7.png
└── src
├── css
├── app.less
├── calls.less
├── conferences.less
├── jquery.growl.less
├── settings.less
└── simple-sidebar.less
└── js
├── controllers
├── calls.js
├── conferences.js
└── main.js
├── directives
├── ngConfirmClick.js
├── ngModalClose.js
├── ngMomentAgo.js
└── ngPopover.js
└── services
├── AllSettled.js
├── GrowlService.js
├── LocalStorageService.js
└── freeswitch
├── FreeswitchClient.js
├── FreeswitchRouter.js
├── models
├── Call.js
├── Conference.js
├── Member.js
└── Server.js
└── parsers
├── CallListParser.js
└── ConferenceListParser.js
/.gitignore:
--------------------------------------------------------------------------------
1 | lib-cov
2 | *.seed
3 | *.log
4 | *.csv
5 | *.dat
6 | *.out
7 | *.pid
8 | *.gz
9 |
10 | pids
11 | logs
12 | results
13 | .idea/workspace.xml
14 |
15 | npm-debug.log
16 | node_modules
17 | bower_components
18 |
19 | build
20 | cache
21 | app/js/bundle*
22 | app/css/bundle*
23 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | fs-mgmt
--------------------------------------------------------------------------------
/.idea/dictionaries/pablo.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/fs-mgmt.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/jsLibraryMappings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Grunt_Build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Grunt_Bundle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Run2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/scopes/scope_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/watcherTasks.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | /*globals module */
2 | module.exports = function(grunt) {
3 | 'use strict';
4 |
5 | grunt.initConfig({
6 | pkg: grunt.file.readJSON('package.json'),
7 |
8 | nwjs: {
9 | options: {
10 | appName: 'Freeswitch Desktop',
11 | version: '0.19.5',
12 | flavor: 'normal',
13 | buildDir: './build',
14 | platforms: ['osx64'],
15 | winIco: "app/images/icon.ico",
16 | zip: true,
17 | macIcns: "app/images/icon.icns"
18 | },
19 | src: ['./app/**/*']
20 | },
21 |
22 | appdmg: {
23 | options: {
24 | title: 'Freeswitch Desktop',
25 | icon: 'app/images/icon.icns',
26 | background: 'images/blank.png',
27 | 'icon-size': 80,
28 | contents: [
29 | { "x": 350, "y": 100, "type": "link", "path": "/Applications" },
30 | { "x": 150, "y": 100, "type": "file", "path": "build/Freeswitch Desktop/osx64/Freeswitch Desktop.app" }
31 | ]
32 | },
33 | target: {
34 | dest: 'build/Freeswitch Desktop/osx64/Freeswitch Desktop.dmg'
35 | }
36 | },
37 |
38 | uglify: {
39 | options: {
40 | sourceMap: true,
41 | sourceMapIncludeSources: true
42 | },
43 | all: {
44 | files: {
45 | 'app/js/bundle-header.js': [
46 | 'bower_components/modernizr/modernizr.js'
47 | ],
48 | 'app/js/bundle.js': [
49 | 'bower_components/jquery/dist/jquery.js',
50 | 'bower_components/bootstrap/dist/js/bootstrap.js',
51 | 'bower_components/jquery-growl/javascripts/jquery.growl.js',
52 | 'bower_components/moment/moment.js',
53 | 'bower_components/angular/angular.js',
54 | 'bower_components/angular-sanitize/angular-sanitize.js',
55 | 'bower_components/angular-resource/angular-resource.js',
56 | 'bower_components/angular-ui-router/release/angular-ui-router.js',
57 | 'src/js/controllers/main.js',
58 | 'src/js/controllers/conferences.js',
59 | 'src/js/controllers/calls.js',
60 | 'src/js/directives/ngConfirmClick.js',
61 | 'src/js/directives/ngMomentAgo.js',
62 | 'src/js/directives/ngModalClose.js',
63 | 'src/js/directives/ngPopover.js',
64 | 'src/js/services/AllSettled.js',
65 | 'src/js/services/LocalStorageService.js',
66 | 'src/js/services/GrowlService.js',
67 | 'src/js/services/freeswitch/FreeswitchRouter.js',
68 | 'src/js/services/freeswitch/FreeswitchClient.js',
69 | 'src/js/services/freeswitch/parsers/ConferenceListParser.js',
70 | 'src/js/services/freeswitch/parsers/CallListParser.js',
71 | 'src/js/services/freeswitch/models/Member.js',
72 | 'src/js/services/freeswitch/models/Call.js',
73 | 'src/js/services/freeswitch/models/Conference.js',
74 | 'src/js/services/freeswitch/models/Server.js'
75 | ]
76 | }
77 | }
78 | },
79 |
80 | less: {
81 | options: {
82 | compress: true,
83 | cleancss: true,
84 | sourceMap: true
85 | },
86 | all: {
87 | files: {
88 | 'app/css/bundle.css': 'src/css/app.less'
89 | }
90 | }
91 | },
92 |
93 | clean: {
94 | all: {
95 | src: [
96 | 'app/js/bundle-header.js.map',
97 | 'app/js/bundle.js.map'
98 | ]
99 | }
100 | }
101 | });
102 |
103 | grunt.loadNpmTasks('grunt-nw-builder');
104 | grunt.loadNpmTasks('grunt-contrib-uglify');
105 | grunt.loadNpmTasks('grunt-newer');
106 | grunt.loadNpmTasks('grunt-contrib-clean');
107 | grunt.loadNpmTasks('grunt-contrib-less');
108 | grunt.loadNpmTasks('grunt-appdmg');
109 | grunt.registerTask('bundle', ['newer:uglify', 'less']);
110 | grunt.registerTask('build', ['bundle', 'clean', 'nwjs', 'appdmg']);
111 | grunt.registerTask('default', ['build']);
112 | };
113 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | freeswitch-desktop
2 | ==================
3 |
4 | Simple Freeswitch desktop management application. We use it at [Cinchcast](http://www.cinchcast.com/?utm_source=github&utm_medium=blog&utm_campaign=fsdesktopapp).
5 |
6 | ## binaries
7 |
8 | Binaries for every platform are available on the [releases page](https://github.com/pablote/fs-mgmt/releases)
9 |
10 | Latest: []()
11 |
12 | On OSX, if using [Homebrew](http://brew.sh/) and [Caskroom](http://caskroom.io/):
13 | ```
14 | brew cask install freeswitch-desktop
15 | ```
16 |
17 | ## build
18 |
19 | * Run ```grunt build```
20 | * Executables are on ```build/Freeswitch Desktop/{platform-name}/```
21 |
22 | ## screenshots
23 |
24 | 
25 |
26 | 
27 |
28 | 
29 |
30 | 
31 |
32 | 
33 |
34 | 
35 |
36 | 
37 |
--------------------------------------------------------------------------------
/app/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/app/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/app/fonts/glyphicons-halflings-regular.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/app/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/app/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/app/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/app/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/app/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/app/html/calls/main.html:
--------------------------------------------------------------------------------
1 |
2 |
19 |
68 |
71 |
72 |
73 |
74 |
78 |
79 |
{{ selectedCall | json }}
80 |
81 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/app/html/conferences/main.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/html/main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Freeswitch Desktop
5 |
6 |
7 |
8 |
9 |
10 |
13 |
65 |
68 |
69 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
98 |
99 |
100 |
101 |
105 |
106 |
{{ messageDialog.text }}
107 |
{{ messageDialog.preText }}
108 |
View details
109 |
110 |
{{ messageDialog.details | json }}
111 |
112 |
113 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/app/images/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/app/images/icon.icns
--------------------------------------------------------------------------------
/app/images/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/app/images/icon.ico
--------------------------------------------------------------------------------
/app/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/app/images/icon.png
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fs-mgmt",
3 | "description": "Freeswitch management desktop application",
4 | "version": "1.4.1",
5 | "main": "html/main.html",
6 | "window": {
7 | "title": "Freeswitch Desktop",
8 | "icon": "/images/icon.png",
9 | "width": 1280,
10 | "height": 768
11 | },
12 | "dependencies": {
13 | "cheerio": "^0.22.0",
14 | "moment": "^2.12.0",
15 | "string": "^3.3.1",
16 | "tabletojson": "^0.4.0",
17 | "underscore": "^1.8.3"
18 | },
19 | "chromium-args": "--enable-native-notifications"
20 | }
21 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fs-mgmt",
3 | "homepage": "https://github.com/pablote/fs-mgmt",
4 | "authors": [
5 | "Pablo Compagni "
6 | ],
7 | "ignore": [
8 | "**/.*",
9 | "node_modules",
10 | "bower_components",
11 | "test",
12 | "tests"
13 | ],
14 | "dependencies": {
15 | "jquery": "~2.1.4",
16 | "bootstrap": "~3.3.4",
17 | "modernizr": "~2.8.3",
18 | "moment": "~2.10.2",
19 | "jquery-growl": "~1.2.5",
20 | "angular": "~1.4.8",
21 | "angular-sanitize": "~1.4.8",
22 | "angular-resource": "~1.4.8",
23 | "angular-ui-router": "~0.2.15",
24 | "startbootstrap-simple-sidebar": "IronSummitMedia/startbootstrap-simple-sidebar#~1.0.4"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/images/blank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/images/blank.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fs-mgmt-build",
3 | "version": "1.4.1",
4 | "repository": {
5 | "type": "git",
6 | "url": "https://github.com/pablote/fs-mgmt"
7 | },
8 | "scripts": {
9 | "postinstall": "bower install"
10 | },
11 | "dependencies": {
12 | "grunt-nw-builder": "^3.1.0"
13 | },
14 | "devDependencies": {
15 | "grunt": "^1.0.1",
16 | "grunt-appdmg": "^0.4.0",
17 | "grunt-cli": "^1.2.0",
18 | "grunt-contrib-clean": "^1.0.0",
19 | "grunt-contrib-less": "^1.3.0",
20 | "grunt-contrib-uglify": "^2.0.0",
21 | "grunt-newer": "^1.2.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/screenshots/v0.2.0/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v0.2.0/1.png
--------------------------------------------------------------------------------
/screenshots/v0.2.0/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v0.2.0/2.png
--------------------------------------------------------------------------------
/screenshots/v0.3.0/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v0.3.0/1.png
--------------------------------------------------------------------------------
/screenshots/v0.3.0/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v0.3.0/2.png
--------------------------------------------------------------------------------
/screenshots/v0.4.0/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v0.4.0/1.png
--------------------------------------------------------------------------------
/screenshots/v0.4.0/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v0.4.0/2.png
--------------------------------------------------------------------------------
/screenshots/v0.4.0/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v0.4.0/3.png
--------------------------------------------------------------------------------
/screenshots/v0.5.0/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v0.5.0/1.png
--------------------------------------------------------------------------------
/screenshots/v0.5.0/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v0.5.0/2.png
--------------------------------------------------------------------------------
/screenshots/v0.5.0/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v0.5.0/3.png
--------------------------------------------------------------------------------
/screenshots/v0.5.0/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v0.5.0/4.png
--------------------------------------------------------------------------------
/screenshots/v0.5.0/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v0.5.0/5.png
--------------------------------------------------------------------------------
/screenshots/v1.1.0/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v1.1.0/1.png
--------------------------------------------------------------------------------
/screenshots/v1.1.0/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v1.1.0/2.png
--------------------------------------------------------------------------------
/screenshots/v1.1.0/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v1.1.0/3.png
--------------------------------------------------------------------------------
/screenshots/v1.1.0/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v1.1.0/4.png
--------------------------------------------------------------------------------
/screenshots/v1.1.0/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v1.1.0/5.png
--------------------------------------------------------------------------------
/screenshots/v1.1.0/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v1.1.0/6.png
--------------------------------------------------------------------------------
/screenshots/v1.1.0/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v1.1.0/7.png
--------------------------------------------------------------------------------
/screenshots/v1.2.0/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v1.2.0/1.png
--------------------------------------------------------------------------------
/screenshots/v1.2.0/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v1.2.0/2.png
--------------------------------------------------------------------------------
/screenshots/v1.2.0/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v1.2.0/3.png
--------------------------------------------------------------------------------
/screenshots/v1.2.0/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v1.2.0/4.png
--------------------------------------------------------------------------------
/screenshots/v1.2.0/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v1.2.0/5.png
--------------------------------------------------------------------------------
/screenshots/v1.2.0/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v1.2.0/6.png
--------------------------------------------------------------------------------
/screenshots/v1.2.0/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/screenshots/v1.2.0/7.png
--------------------------------------------------------------------------------
/src/css/app.less:
--------------------------------------------------------------------------------
1 | @import "../../bower_components/bootstrap/less/bootstrap.less";
2 | @import "../../bower_components/bootstrap/less/theme.less";
3 | @import "simple-sidebar.less";
4 | @import "jquery.growl.less";
5 | @import "settings.less";
6 | @import "conferences.less";
7 | @import "calls.less";
8 |
9 | .ng-cloak {
10 | display: none;
11 | }
12 |
13 | .height-all {
14 | height: 100% !important;
15 | }
16 |
17 | .height-all-expect-nav {
18 | height: ~"calc(100% - 72px)";
19 | }
20 |
21 | .height-all-expect-sub-nav {
22 | height: ~"calc(100% - 35px)";
23 | }
24 |
25 | .no-padding {
26 | padding: 0 !important;
27 | }
28 |
29 | .no-padding-left {
30 | padding-left: 0 !important;
31 | }
32 |
33 | .no-margin {
34 | margin: 0 !important;
35 | }
36 |
37 | .vertical-scroll {
38 | overflow-y: auto !important;
39 | }
40 |
41 | .vertical-crop {
42 | overflow-y: hidden !important;
43 | }
44 |
45 | .text-white {
46 | color: #dddddd;
47 | }
48 |
49 | .btn-no-outline {
50 | outline: none !important;
51 | }
52 |
53 | .navbar-noborder {
54 | border-radius: 0 !important;
55 | margin-bottom: 0;
56 | }
57 |
58 | .text-max-width-250 {
59 | max-width: 250px;
60 | white-space: nowrap;
61 | overflow: hidden;
62 | text-overflow: ellipsis;
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/src/css/calls.less:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pablote/fs-mgmt/8a2c0a62791ca0472ea9f8bf2c9c6990e7d1156f/src/css/calls.less
--------------------------------------------------------------------------------
/src/css/conferences.less:
--------------------------------------------------------------------------------
1 | .section-header {
2 | background: #eeeeee;
3 | padding-top: 5px;
4 | padding-bottom: 5px;
5 |
6 | span {
7 | line-height: 34px;
8 | vertical-align: middle;
9 | }
10 |
11 | button {
12 | .pull-right();
13 | }
14 |
15 | .checkbox {
16 | margin: 7px 10px 0 0;
17 | .pull-right();
18 | }
19 | }
20 |
21 | .list-server-item {
22 | .list-server-header {
23 | margin-top: 20px;
24 | margin-bottom: 10px;
25 |
26 | h3 {
27 | display: inline;
28 | vertical-align: middle;
29 | }
30 |
31 | .list-server-header-details {
32 | color: transparent;
33 | vertical-align: middle;
34 | }
35 | }
36 |
37 | &:hover {
38 | .list-server-header {
39 | .list-server-header-details {
40 | color: #AAAAAA;
41 | }
42 | }
43 | }
44 | }
45 |
46 | .list-conference-item {
47 | .list-conference-title {
48 | margin-bottom: 10px;
49 |
50 | .list-conference-name {
51 | display: inline;
52 | }
53 |
54 | .list-conference-title-details {
55 | color: transparent;
56 |
57 | &:hover {
58 | color: #AAAAAA;
59 | }
60 | }
61 | }
62 |
63 | &:hover {
64 | .list-conference-title {
65 | .list-conference-title-details {
66 | color: #AAAAAA;
67 | }
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/src/css/jquery.growl.less:
--------------------------------------------------------------------------------
1 | /* jQuery Growl
2 | * Copyright 2015 Kevin Sylvestre
3 | * 1.2.5
4 | */
5 | #growls {
6 | z-index: 50000;
7 | position: fixed; }
8 | #growls.default {
9 | top: 10px;
10 | right: 10px; }
11 | #growls.tl {
12 | top: 10px;
13 | left: 10px; }
14 | #growls.tr {
15 | top: 10px;
16 | right: 10px; }
17 | #growls.bl {
18 | bottom: 10px;
19 | left: 10px; }
20 | #growls.br {
21 | bottom: 10px;
22 | right: 10px; }
23 | #growls.tc {
24 | top: 10px;
25 | right: 10px;
26 | left: 10px; }
27 | #growls.bc {
28 | bottom: 10px;
29 | right: 10px;
30 | left: 10px; }
31 | #growls.tc .growl, #growls.bc .growl {
32 | margin-left: auto;
33 | margin-right: auto; }
34 |
35 | .growl {
36 | opacity: 0.8;
37 | filter: alpha(opacity=80);
38 | position: relative;
39 | border-radius: 4px;
40 | -webkit-transition: all 0.4s ease-in-out;
41 | -moz-transition: all 0.4s ease-in-out;
42 | transition: all 0.4s ease-in-out; }
43 | .growl.growl-incoming {
44 | opacity: 0;
45 | filter: alpha(opacity=0); }
46 | .growl.growl-outgoing {
47 | opacity: 0;
48 | filter: alpha(opacity=0); }
49 | .growl.growl-small {
50 | width: 200px;
51 | padding: 5px;
52 | margin: 5px; }
53 | .growl.growl-medium {
54 | width: 250px;
55 | padding: 10px;
56 | margin: 10px; }
57 | .growl.growl-large {
58 | width: 300px;
59 | padding: 15px;
60 | margin: 15px; }
61 | .growl.growl-default {
62 | color: #FFF;
63 | background: #7f8c8d; }
64 | .growl.growl-error {
65 | color: #FFF;
66 | background: #C0392B; }
67 | .growl.growl-notice {
68 | color: #FFF;
69 | background: #2ECC71; }
70 | .growl.growl-warning {
71 | color: #FFF;
72 | background: #F39C12; }
73 | .growl .growl-close {
74 | cursor: pointer;
75 | float: right;
76 | font-size: 14px;
77 | line-height: 18px;
78 | font-weight: normal;
79 | font-family: helvetica, verdana, sans-serif; }
80 | .growl .growl-title {
81 | font-size: 18px;
82 | line-height: 24px; }
83 | .growl .growl-message {
84 | font-size: 14px;
85 | line-height: 18px; }
86 |
--------------------------------------------------------------------------------
/src/css/settings.less:
--------------------------------------------------------------------------------
1 | .settings-panel {
2 | border-right: solid 1px #cccccc;
3 | background-color: #eeeeee !important;
4 | color: #333333;
5 | font-size: 11pt;
6 | }
7 |
8 | .settings-panel .settings-panel-content {
9 | margin: 10px;
10 | }
11 |
12 | .settings-panel .server-list {
13 | padding: 0;
14 | margin: 0;
15 | }
16 |
17 | .settings-panel .server-list .server-list-item {
18 | list-style: none;
19 | padding-bottom: 5px;
20 | }
21 |
22 | .settings-panel .server-list .server-list-item .server-list-item-remove {
23 | cursor: pointer;
24 | display: inline;
25 | }
26 |
27 | label.settings-title {
28 | font-size: 12pt;
29 | }
30 |
31 | .settings-panel a.settings-action {
32 | cursor: pointer;
33 | font-size: 9pt;
34 | }
35 |
36 | .settings-panel a.settings-action-disabled {
37 | cursor: not-allowed;
38 | font-size: 9pt;
39 | }
--------------------------------------------------------------------------------
/src/css/simple-sidebar.less:
--------------------------------------------------------------------------------
1 | /*!
2 | * Start Bootstrap - Simple Sidebar HTML Template (http://startbootstrap.com)
3 | * Code licensed under the Apache License v2.0.
4 | * For details, see http://www.apache.org/licenses/LICENSE-2.0.
5 | */
6 |
7 | /* Toggle Styles */
8 |
9 | #wrapper {
10 | padding-left: 0;
11 | -webkit-transition: all 0.5s ease;
12 | -moz-transition: all 0.5s ease;
13 | -o-transition: all 0.5s ease;
14 | transition: all 0.5s ease;
15 | }
16 |
17 | #wrapper.toggled {
18 | padding-left: 250px;
19 | }
20 |
21 | #sidebar-wrapper {
22 | z-index: 1000;
23 | position: fixed;
24 | left: 250px;
25 | width: 0;
26 | height: 100%;
27 | margin-left: -250px;
28 | overflow-y: auto;
29 | background: #000;
30 | -webkit-transition: all 0.5s ease;
31 | -moz-transition: all 0.5s ease;
32 | -o-transition: all 0.5s ease;
33 | transition: all 0.5s ease;
34 | }
35 |
36 | #wrapper.toggled #sidebar-wrapper {
37 | width: 250px;
38 | }
39 |
40 | #page-content-wrapper {
41 | width: 100%;
42 | position: absolute;
43 | padding: 15px;
44 | }
45 |
46 | #wrapper.toggled #page-content-wrapper {
47 | position: absolute;
48 | margin-right: -250px;
49 | }
50 |
51 | /* Sidebar Styles */
52 |
53 | .sidebar-nav {
54 | position: absolute;
55 | top: 0;
56 | width: 250px;
57 | margin: 0;
58 | padding: 0;
59 | list-style: none;
60 | }
61 |
62 | .sidebar-nav li {
63 | text-indent: 20px;
64 | line-height: 40px;
65 | }
66 |
67 | .sidebar-nav li a {
68 | display: block;
69 | text-decoration: none;
70 | color: #999999;
71 | }
72 |
73 | .sidebar-nav li a:hover {
74 | text-decoration: none;
75 | color: #fff;
76 | background: rgba(255,255,255,0.2);
77 | }
78 |
79 | .sidebar-nav li a:active,
80 | .sidebar-nav li a:focus {
81 | text-decoration: none;
82 | }
83 |
84 | .sidebar-nav > .sidebar-brand {
85 | height: 65px;
86 | font-size: 18px;
87 | line-height: 60px;
88 | }
89 |
90 | .sidebar-nav > .sidebar-brand a {
91 | color: #999999;
92 | }
93 |
94 | .sidebar-nav > .sidebar-brand a:hover {
95 | color: #fff;
96 | background: none;
97 | }
98 |
99 | @media(min-width:768px) {
100 | #wrapper {
101 | padding-left: 250px;
102 | }
103 |
104 | #wrapper.toggled {
105 | padding-left: 0;
106 | }
107 |
108 | #sidebar-wrapper {
109 | width: 250px;
110 | }
111 |
112 | #wrapper.toggled #sidebar-wrapper {
113 | width: 0;
114 | }
115 |
116 | #page-content-wrapper {
117 | padding: 20px;
118 | position: relative;
119 | }
120 |
121 | #wrapper.toggled #page-content-wrapper {
122 | position: relative;
123 | margin-right: 0;
124 | }
125 | }
--------------------------------------------------------------------------------
/src/js/controllers/calls.js:
--------------------------------------------------------------------------------
1 | /*globals angular, console, nw */
2 | (function () {
3 | 'use strict';
4 | var module = angular.module('fsmgmt.controllers.calls', [
5 | 'fsmgmt.services.GrowlService',
6 | 'fsmgmt.services.freeswitch.FreeswitchRouter',
7 | 'fsmgmt.directives.ngConfirmClick',
8 | 'fsmgmt.directives.ngPopover',
9 | 'fsmgmt.directives.ngMomentAgo'
10 | ]);
11 |
12 | module.controller('CallsController', ['$scope', '$interval', 'growl', 'freeswitch',
13 | function ($scope, $interval, growl, freeswitch) {
14 | var u = require('underscore');
15 |
16 | // default values
17 | $scope.servers = [];
18 | $scope.selectedCall = null;
19 | $scope.autoRefresh = null; // interval object
20 | $scope.lastRefresh = null; // moment obj with datetime of last refresh
21 |
22 | // events
23 | $scope.$on("$destroy", function() {
24 | if ($scope.autoRefresh) {
25 | $scope.toggleAutoRefresh();
26 | }
27 | });
28 |
29 | // methods
30 | $scope.refresh = function () {
31 | var servers = [];
32 |
33 | u.each($scope.settings.serverList, function (server) {
34 | if (server.enabled) {
35 | servers.push({
36 | name: server.name,
37 | host: server.host,
38 | username: $scope.settings.username,
39 | password: $scope.settings.password
40 | });
41 | }
42 | });
43 |
44 | freeswitch.setTimeout($scope.settings.httpTimeoutMilliseconds);
45 | freeswitch
46 | .listCalls(servers)
47 | .then(function (fsListCallsResponse) {
48 | $scope.lastRefresh = moment();
49 | $scope.servers = fsListCallsResponse;
50 |
51 | setTimeout(function () { $('[data-toggle="tooltip"]').tooltip(); }, 1000);
52 | })
53 | .catch(function (error) {
54 | var msg = 'A problem occurred accessing the Freeswitch servers.';
55 | $scope.showModal({ title: 'Error', text: msg, details: error });
56 | })
57 | };
58 |
59 | $scope.selectCall = function (call) {
60 | $scope.selectedCall = call;
61 |
62 | $('#modalCallDetails').modal();
63 | };
64 |
65 | $scope.killCall = function (server, call) {
66 | freeswitch.kill(server, call)
67 | .then(function (killResponse) {
68 | growl.info(killResponse, 'Done');
69 | $scope.refresh();
70 | })
71 | .catch(function (killError) {
72 | growl.error(killError.toString(), "Failed");
73 | });
74 | };
75 |
76 | $scope.copyToClipboard = function (text) {
77 | var clipboard = nw.Clipboard.get();
78 | clipboard.set(text, 'text');
79 |
80 | growl.info(text, 'Copied to Clipboard');
81 | };
82 |
83 | $scope.toggleAutoRefresh = function () {
84 | if (!$scope.autoRefresh) {
85 | $scope.enableSettings(false);
86 | $scope.lastRefresh = null;
87 |
88 | $scope.refresh();
89 |
90 | $scope.autoRefresh = $interval(function() {
91 | $scope.refresh();
92 | }, $scope.settings.autoRefreshInterval * 1000);
93 | } else {
94 | $scope.enableSettings(true);
95 | $interval.cancel($scope.autoRefresh);
96 | delete $scope.autoRefresh;
97 | }
98 | };
99 | }
100 | ]);
101 | }());
--------------------------------------------------------------------------------
/src/js/controllers/conferences.js:
--------------------------------------------------------------------------------
1 | /*globals angular, console, nw */
2 | (function () {
3 | 'use strict';
4 | var module = angular.module('fsmgmt.controllers.conferences', [
5 | 'fsmgmt.services.GrowlService',
6 | 'fsmgmt.services.freeswitch.FreeswitchRouter',
7 | 'fsmgmt.directives.ngConfirmClick',
8 | 'fsmgmt.directives.ngPopover',
9 | 'fsmgmt.directives.ngMomentAgo'
10 | ]);
11 |
12 | module.controller('ConferencesController', ['$scope', '$interval', 'growl', 'freeswitch',
13 | function ($scope, $interval, growl, freeswitch) {
14 | var u = require('underscore');
15 |
16 | // default values
17 | $scope.servers = [];
18 | $scope.autoRefresh = null; // interval object
19 | $scope.lastRefresh = null; // moment obj with datetime of last refresh
20 |
21 | // events
22 | $scope.$on("$destroy", function() {
23 | if ($scope.autoRefresh) {
24 | $scope.toggleAutoRefresh();
25 | }
26 | });
27 |
28 | // methods
29 | $scope.refresh = function () {
30 | var servers = [];
31 |
32 | u.each($scope.settings.serverList, function (server) {
33 | if (server.enabled) {
34 | servers.push({
35 | name: server.name,
36 | host: server.host,
37 | username: $scope.settings.username,
38 | password: $scope.settings.password
39 | });
40 | }
41 | });
42 |
43 | freeswitch.setTimeout($scope.settings.httpTimeoutMilliseconds);
44 | freeswitch
45 | .listConferences(servers)
46 | .then(function (fsListResponse) {
47 | $scope.lastRefresh = moment();
48 | $scope.servers = fsListResponse;
49 | })
50 | .catch(function (error) {
51 | var msg = 'A problem occurred accessing the Freeswitch servers.';
52 | $scope.showModal({ title: 'Error', text: msg, details: error });
53 | })
54 | };
55 |
56 | $scope.toggleAutoRefresh = function () {
57 | if (!$scope.autoRefresh) {
58 | $scope.enableSettings(false);
59 | $scope.lastRefresh = null;
60 |
61 | $scope.refresh();
62 |
63 | $scope.autoRefresh = $interval(function() {
64 | $scope.refresh();
65 | }, $scope.settings.autoRefreshInterval * 1000);
66 | } else {
67 | $scope.enableSettings(true);
68 | $interval.cancel($scope.autoRefresh);
69 | delete $scope.autoRefresh;
70 | }
71 | };
72 |
73 | $scope.hangup = function (server, conference, member) {
74 | freeswitch
75 | .hangup(server, conference, member)
76 | .then(function (hangupResponse) {
77 | growl.info('Done', 'Hangup');
78 | })
79 | .then(function () {
80 | $scope.refresh();
81 | })
82 | .catch(function (error) {
83 | var msg = 'A problem occurred during hangup.' + error.toString();
84 | growl.info(msg, 'Hangup Error');
85 | });
86 | };
87 |
88 | $scope.copyToClipboard = function (text) {
89 | var clipboard = nw.Clipboard.get();
90 | clipboard.set(text, 'text');
91 |
92 | growl.info(text, 'Copied to Clipboard');
93 | };
94 |
95 | $scope.showRecordingStatus = function (server, conference) {
96 | freeswitch
97 | .recordingCheck(server, conference)
98 | .then(function (recordingCheckResponse) {
99 | $scope.showModal({
100 | title: 'Recording status',
101 | preText: recordingCheckResponse
102 | });
103 | })
104 | .catch(function (error) {
105 | var msg = 'A problem occurred during recording check.';
106 |
107 | $scope.showModal({
108 | title: 'Error',
109 | text: msg,
110 | details: error
111 | });
112 | });
113 | };
114 |
115 | }
116 | ]);
117 | }());
--------------------------------------------------------------------------------
/src/js/controllers/main.js:
--------------------------------------------------------------------------------
1 | /*globals angular, console, require, nw */
2 | (function () {
3 | 'use strict';
4 | var module = angular.module('fsmgmt.controllers.main', [
5 | 'ui.router',
6 | 'fsmgmt.services.LocalStorageService',
7 | 'fsmgmt.controllers.conferences',
8 | 'fsmgmt.controllers.calls',
9 | 'fsmgmt.directives.ngModalClose'
10 | ]);
11 |
12 | var consts = {
13 | StorageKeys: {
14 | FreeswitchServerList: 'settings-server-list',
15 | FreeswitchUsername: 'settings-username-v2',
16 | FreeswitchPassword: 'settings-password-v2',
17 | AutoRefreshInterval: 'settings-autorefresh-interval-v2',
18 | HttpTimeoutMilliseconds: 'settings-http-timeout-millis'
19 | }
20 | };
21 |
22 | module.controller('MainController', ['$scope', '$interval', 'localStorage',
23 | function ($scope, $interval, localStorage) {
24 | var u = require('underscore');
25 | window.moment = require('moment');
26 | window.moment.fn.fromNowOrNow = function (a) {
27 | if (Math.abs(moment().diff(this)) < 3000) {
28 | return 'just now';
29 | }
30 | return this.fromNow(a);
31 | };
32 |
33 | // default values
34 | $scope.isSettingsVisible = true;
35 | $scope.isSettingsDisabled = false;
36 | $scope.settings = {};
37 | $scope.messageDialog = {};
38 |
39 | localStorage.get(consts.StorageKeys.FreeswitchServerList).then(function(value) {
40 | if (value) {
41 | // compatibility, if no `enabled` property in object, then default it to true
42 | u.each(value, function (server) {
43 | if (!server.hasOwnProperty('enabled')) {
44 | server.enabled = true;
45 | }
46 | });
47 |
48 | $scope.settings.serverList = value;
49 | }
50 | else {
51 | $scope.settings.serverList = [];
52 | }
53 | });
54 |
55 | localStorage.get(consts.StorageKeys.FreeswitchUsername).then(function(value) {
56 | if (value) $scope.settings.username = value;
57 | });
58 |
59 | localStorage.get(consts.StorageKeys.FreeswitchPassword).then(function(value) {
60 | if (value) $scope.settings.password = value;
61 | });
62 |
63 | localStorage.get(consts.StorageKeys.AutoRefreshInterval).then(function(value) {
64 | if (value)
65 | $scope.settings.autoRefreshInterval = value;
66 | else
67 | $scope.settings.autoRefreshInterval = 5;
68 | });
69 |
70 | localStorage.get(consts.StorageKeys.HttpTimeoutMilliseconds).then(function(value) {
71 | if (value)
72 | $scope.settings.httpTimeoutMilliseconds = value;
73 | else
74 | $scope.settings.httpTimeoutMilliseconds = 2000;
75 | });
76 |
77 | // mac menus
78 | if (process.platform === 'darwin') {
79 | var nativeMenuBar = new nw.Menu({type: "menubar"});
80 | nativeMenuBar.createMacBuiltin("Freeswitch Desktop", {
81 | hideEdit: false,
82 | hideWindow: true
83 | });
84 |
85 | nw.Window.get().menu = nativeMenuBar;
86 | }
87 |
88 | // settings methods
89 | $scope.enableSettings = function (enable) {
90 | $scope.isSettingsDisabled = !enable;
91 | };
92 |
93 | $scope.toggleSettings = function () {
94 | $scope.isSettingsVisible = !$scope.isSettingsVisible;
95 | };
96 |
97 | $scope.addServer = function () {
98 | var host = prompt('Server host:');
99 |
100 | if (host) {
101 | var existingServers = u.where($scope.settings.serverList, { name: host });
102 |
103 | if (existingServers.length === 0) {
104 | $scope.settings.serverList.push({
105 | name: host,
106 | host: host
107 | });
108 | } else {
109 | alert ('There\'s already a server by that name');
110 | }
111 | }
112 | };
113 |
114 | $scope.removeServer = function (server) {
115 | $scope.settings.serverList = u.without($scope.settings.serverList, server);
116 | };
117 |
118 | $scope.selectAllServers = function () {
119 | if ($scope.settings && $scope.settings.serverList && !$scope.isSettingsDisabled) {
120 | u.each($scope.settings.serverList, function (server) {
121 | server.enabled = true;
122 | });
123 | }
124 | };
125 |
126 | $scope.deselectAllServers = function () {
127 | if ($scope.settings && $scope.settings.serverList && !$scope.isSettingsDisabled) {
128 | u.each($scope.settings.serverList, function (server) {
129 | server.enabled = false;
130 | });
131 | }
132 | };
133 |
134 | // modal dialog methods
135 | $scope.showModal = function(options) {
136 | $scope.messageDialog.title = options.title || '';
137 | $scope.messageDialog.text = options.text || null;
138 | $scope.messageDialog.details = options.details || null;
139 | $scope.messageDialog.preText = options.preText || null;
140 |
141 | $('#dlgMessage').modal();
142 | };
143 |
144 | $scope.onModalClose = function () {
145 | $scope.messageDialog = {};
146 | };
147 |
148 | // watches
149 | $scope.$watch("settings.serverList", function (newValue, oldValue) {
150 | localStorage.set(consts.StorageKeys.FreeswitchServerList, newValue);
151 | }, true);
152 |
153 | $scope.$watch("settings.username", function (newValue, oldValue) {
154 | localStorage.set(consts.StorageKeys.FreeswitchUsername, newValue);
155 | });
156 |
157 | $scope.$watch("settings.password", function (newValue, oldValue) {
158 | localStorage.set(consts.StorageKeys.FreeswitchPassword, newValue);
159 | });
160 |
161 | $scope.$watch("settings.autoRefreshInterval", function (newValue, oldValue) {
162 | localStorage.set(consts.StorageKeys.AutoRefreshInterval, newValue);
163 | });
164 |
165 | $scope.$watch("settings.httpTimeoutMilliseconds", function (newValue, oldValue) {
166 | localStorage.set(consts.StorageKeys.HttpTimeoutMilliseconds, newValue);
167 | });
168 | }
169 | ]);
170 |
171 | module.config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) {
172 | $urlRouterProvider.otherwise("/conferences");
173 |
174 | $stateProvider
175 | .state('conferences', { url: "/conferences", controller: 'ConferencesController', templateUrl: "conferences/main.html" })
176 | .state('calls', { url: "/calls", controller: 'CallsController', templateUrl: "calls/main.html" });
177 | }]);
178 | }());
--------------------------------------------------------------------------------
/src/js/directives/ngConfirmClick.js:
--------------------------------------------------------------------------------
1 | /*global angular, window */
2 | (function () {
3 | 'use strict';
4 | var module = angular.module('fsmgmt.directives.ngConfirmClick', []);
5 |
6 | module.directive('ngConfirmClick', [
7 | function () {
8 | return {
9 | link: function (scope, element, attr) {
10 | var clickAction = attr.ngConfirmClick;
11 | var msg = attr.confirmMsg || "Are you sure?";
12 |
13 | element.bind('click', function (event) {
14 | if (window.confirm(msg)) {
15 | scope.$eval(clickAction);
16 | }
17 | });
18 | }
19 | };
20 | }]);
21 | }());
--------------------------------------------------------------------------------
/src/js/directives/ngModalClose.js:
--------------------------------------------------------------------------------
1 | /*global angular, window */
2 | (function () {
3 | 'use strict';
4 | var module = angular.module('fsmgmt.directives.ngModalClose', []);
5 |
6 | module.directive('ngModalClose', [
7 | function () {
8 | return {
9 | link: function (scope, element, attr) {
10 | var closeAction = attr.ngModalClose;
11 |
12 | element.bind('hidden.bs.modal', function (event) {
13 | scope.$eval(closeAction);
14 | });
15 | }
16 | };
17 | }]);
18 | }());
--------------------------------------------------------------------------------
/src/js/directives/ngMomentAgo.js:
--------------------------------------------------------------------------------
1 | /*global angular, window */
2 | (function () {
3 | 'use strict';
4 | var module = angular.module('fsmgmt.directives.ngMomentAgo', []);
5 |
6 | module.directive('ngMomentAgo', [
7 | function () {
8 | return {
9 | link: function (scope, element, attr) {
10 | var momentName = attr.ngMomentAgo;
11 | var momentFormat = attr.momentFormat;
12 |
13 | setInterval(function() {
14 | scope.$apply(function() {
15 | if (scope[momentName]) {
16 | var fromNow = scope[momentName].fromNowOrNow();
17 | element[0].innerHTML = (momentFormat) ? momentFormat.replace('%s', fromNow) : fromNow;
18 | }
19 | });
20 | }, 500);
21 | }
22 | };
23 | }]);
24 | }());
--------------------------------------------------------------------------------
/src/js/directives/ngPopover.js:
--------------------------------------------------------------------------------
1 | /*global angular, window */
2 | (function () {
3 | 'use strict';
4 | var module = angular.module('fsmgmt.directives.ngPopover', []);
5 |
6 | module.directive('ngPopover', [
7 | function () {
8 | return {
9 | link: function (scope, element, attr) {
10 | $(element).popover();
11 | }
12 | };
13 | }]);
14 | }());
--------------------------------------------------------------------------------
/src/js/services/AllSettled.js:
--------------------------------------------------------------------------------
1 | /*global angular, localStorage */
2 | (function () {
3 | 'use strict';
4 | var module = angular.module('fsmgmt.services.AllSettled', []);
5 |
6 | // Add extra functionality to $q
7 | module.config(['$provide', function ($provide) {
8 | $provide.decorator('$q', ['$delegate', function ($delegate) {
9 | var $q = $delegate;
10 |
11 | // Extention for q
12 | $q.allSettled = $q.allSettled || function (promises) {
13 | var deferred = $q.defer();
14 | if (angular.isArray(promises)) {
15 | var states = [];
16 | var results = [];
17 | var didAPromiseFail = false;
18 |
19 | // First create an array for all promises setting their state to false (not completed)
20 | angular.forEach(promises, function (promise, key) {
21 | states[key] = false;
22 | });
23 |
24 | // Helper to check if all states are finished
25 | var checkStates = function (states, results, deferred, failed) {
26 | var allFinished = true;
27 | angular.forEach(states, function (state, key) {
28 | if (!state) {
29 | allFinished = false;
30 | return;
31 | }
32 | });
33 | if (allFinished) {
34 | if (failed) {
35 | deferred.reject(results);
36 | } else {
37 | deferred.resolve(results);
38 | }
39 | }
40 | }
41 |
42 | // Loop through the promises
43 | // a second loop to be sure that checkStates is called when all states are set to false first
44 | angular.forEach(promises, function (promise, key) {
45 | $q.when(promise).then(function (result) {
46 | states[key] = true;
47 | results[key] = result;
48 | checkStates(states, results, deferred, didAPromiseFail);
49 | }, function (reason) {
50 | states[key] = true;
51 | results[key] = reason;
52 | didAPromiseFail = true;
53 | checkStates(states, results, deferred, didAPromiseFail);
54 | });
55 | });
56 | } else {
57 | throw 'allSettled can only handle an array of promises (for now)';
58 | }
59 |
60 | return deferred.promise;
61 | };
62 |
63 | return $q;
64 | }]);
65 | }]);
66 | }());
--------------------------------------------------------------------------------
/src/js/services/GrowlService.js:
--------------------------------------------------------------------------------
1 | /*global angular, localStorage */
2 | (function () {
3 | 'use strict';
4 | var module = angular.module('fsmgmt.services.GrowlService', []);
5 |
6 | module.factory('growl', [function () {
7 | var GrowlService = function () {
8 | };
9 |
10 | GrowlService.prototype.info = function(message, title) {
11 | var options = {
12 | //icon: "http://yourimage.jpg",
13 | body: message
14 | };
15 |
16 | var notification = new Notification(title, options);
17 | };
18 |
19 | GrowlService.prototype.error = function(message, title) {
20 | $.growl.error({ title: title || '', message: message || ''});
21 | };
22 |
23 | GrowlService.prototype.notice = function(message, title) {
24 | $.growl.notice({ title: title || '', message: message || ''});
25 | };
26 |
27 | GrowlService.prototype.warn = function(message, title) {
28 | $.growl.warning({ title: title || '', message: message || ''});
29 | };
30 |
31 | return new GrowlService();
32 | }]);
33 | }());
--------------------------------------------------------------------------------
/src/js/services/LocalStorageService.js:
--------------------------------------------------------------------------------
1 | /*global angular, localStorage */
2 | (function () {
3 | 'use strict';
4 | var module = angular.module('fsmgmt.services.LocalStorageService', []);
5 |
6 | module.factory('localStorage', ['$q', function ($q) {
7 | var u = require('underscore');
8 |
9 | var LocalStorageService = function () {
10 | };
11 |
12 | LocalStorageService.prototype.get = function(key) {
13 | return $q(function (resolve, reject) {
14 | var value = localStorage.getItem(key);
15 |
16 | if (value) {
17 | value = JSON.parse(value);
18 | }
19 |
20 | resolve(value);
21 | });
22 | };
23 |
24 | LocalStorageService.prototype.set = function(key, value) {
25 | return $q(function (resolve, reject) {
26 | if (!u.isNull(value) && !u.isUndefined(value)) {
27 | localStorage.setItem(key, JSON.stringify(value));
28 | }
29 |
30 | resolve();
31 | });
32 | };
33 |
34 | return new LocalStorageService();
35 | }]);
36 | }());
--------------------------------------------------------------------------------
/src/js/services/freeswitch/FreeswitchClient.js:
--------------------------------------------------------------------------------
1 | /*global angular, console */
2 | (function () {
3 | 'use strict';
4 | var module = angular.module('fsmgmt.services.freeswitch.FreeswitchClient', [
5 | 'fsmgmt.services.freeswitch.parsers.ConferenceListParser',
6 | 'fsmgmt.services.freeswitch.parsers.CallListParser',
7 | 'fsmgmt.services.freeswitch.models.Server'
8 | ]);
9 |
10 | module.factory('FreeswitchClient', ['$q', '$http', 'ConferenceListParser', 'CallListParser', 'FreeswitchServer',
11 | function ($q, $http, ConferenceListParser, CallListParser, FreeswitchServer) {
12 | var u = require('underscore');
13 |
14 | var FreeswitchClient = function (server, requestTimeout) {
15 | this.host = server.host;
16 | this.username = server.username;
17 | this.password = server.password;
18 | this.name = server.name;
19 | this.basicAuthHeader = 'Basic ' + new Buffer(this.username + ':' + this.password).toString('base64');
20 | this.requestTimeout = requestTimeout || 3000;
21 | };
22 |
23 | function doGet(self, url) {
24 | return $q(function (resolve, reject) {
25 | $http
26 | .get(url, {
27 | headers: { Authorization: self.basicAuthHeader },
28 | timeout: self.requestTimeout
29 | })
30 | .then(function (getResponse) {
31 | if (getResponse.status == 200) {
32 | resolve(getResponse.data);
33 | } else {
34 | reject(getResponse);
35 | }
36 | })
37 | .catch(function (getError) {
38 | reject(getError);
39 | })
40 | });
41 | }
42 |
43 | FreeswitchClient.prototype.listConferences = function() {
44 | var self = this;
45 | return $q(function (resolve, reject) {
46 | doGet(self, 'http://' + self.host + ':8080/webapi/conference?list')
47 | .then(function(listResponse) {
48 | var parser = new ConferenceListParser();
49 | return parser.parse(listResponse);
50 | })
51 | .then(function (parseResponse) {
52 | var conferences = u.sortBy(parseResponse, "name");
53 |
54 | resolve(new FreeswitchServer({
55 | name: self.name,
56 | host: self.host,
57 | username: self.username,
58 | password: self.password,
59 | conferences: conferences
60 | }));
61 | })
62 | .catch(function (error) {
63 | error.server = {
64 | name: self.name,
65 | host: self.host
66 | };
67 | reject(error);
68 | });
69 | });
70 | };
71 |
72 | FreeswitchClient.prototype.listCalls = function() {
73 | var self = this;
74 | return $q(function (resolve, reject) {
75 | doGet(self, 'http://' + self.host + ':8080/webapi/show?detailed_calls')
76 | .then(function(listResponse) {
77 | var parser = new CallListParser();
78 | return parser.parse(listResponse);
79 | })
80 | .then(function (parseResponse) {
81 | resolve(new FreeswitchServer({
82 | name: self.name,
83 | host: self.host,
84 | username: self.username,
85 | password: self.password,
86 | calls: parseResponse
87 | }));
88 | })
89 | .catch(function (error) {
90 | error.server = {
91 | name: self.name,
92 | host: self.host
93 | };
94 | reject(error);
95 | });
96 | });
97 | };
98 |
99 | FreeswitchClient.prototype.hangup = function(conferenceName, memberId) {
100 | var whattohup = memberId || 'all';
101 | return doGet(this, 'http://' + this.host + ':8080/webapi/conference?' + conferenceName + ' hup ' + whattohup);
102 | };
103 |
104 | FreeswitchClient.prototype.recordingCheck = function(conferenceName) {
105 | return doGet(this, 'http://' + this.host + ':8080/webapi/conference?' + conferenceName + ' recording check');
106 | };
107 |
108 | FreeswitchClient.prototype.kill = function(call) {
109 | return doGet(this, 'http://' + this.host + ':8080/webapi/uuid_kill?' + call.uuid);
110 | };
111 |
112 | return FreeswitchClient;
113 | }
114 | ]);
115 | }());
--------------------------------------------------------------------------------
/src/js/services/freeswitch/FreeswitchRouter.js:
--------------------------------------------------------------------------------
1 | /*global angular, require */
2 | (function () {
3 | 'use strict';
4 | var module = angular.module('fsmgmt.services.freeswitch.FreeswitchRouter', [
5 | 'fsmgmt.services.freeswitch.FreeswitchClient',
6 | 'fsmgmt.services.freeswitch.models.Server',
7 | 'fsmgmt.services.AllSettled'
8 | ]);
9 |
10 | module.factory('freeswitch', ['$q', 'FreeswitchClient', 'FreeswitchServer', function ($q, FreeswitchClient, FreeswitchServer) {
11 | var u = require('underscore');
12 |
13 | var FreeswitchRouter = function () {
14 | this.timeout = null;
15 | };
16 |
17 | FreeswitchRouter.prototype.setTimeout = function (timeout) {
18 | this.timeout = timeout;
19 | };
20 |
21 | FreeswitchRouter.prototype.listConferences = function(servers) {
22 | var self = this;
23 | return $q(function (resolve, reject) {
24 | if (servers.length == 0) {
25 | resolve([]);
26 | } else {
27 | var responses = [];
28 |
29 | u.each(servers, function (server) {
30 | var client = new FreeswitchClient(server, self.timeout);
31 | responses.push(client.listConferences());
32 | });
33 |
34 | $q.allSettled(responses)
35 | .then(function(allResponses) {
36 | resolve(allResponses);
37 | })
38 | .catch(function(allResponses) {
39 | resolve(u.map(allResponses, function (response) {
40 | if (response instanceof FreeswitchServer) {
41 | return response;
42 | } else {
43 | console.log(JSON.stringify(response));
44 |
45 | return {
46 | name: response.server.name,
47 | host: response.server.host,
48 | error: (response.status === 0 || response.status === -1)
49 | ? "Connection refused"
50 | : '(' + response.status + ') ' + response.statusText
51 | }
52 | }
53 | }));
54 | });
55 | }
56 | });
57 | };
58 |
59 | FreeswitchRouter.prototype.listCalls = function(servers) {
60 | var self = this;
61 | return $q(function (resolve, reject) {
62 | var responses = [];
63 |
64 | u.each(servers, function (server) {
65 | var client = new FreeswitchClient(server, self.timeout);
66 | responses.push(client.listCalls());
67 | });
68 |
69 | $q.allSettled(responses)
70 | .then(function(allResponses) {
71 | resolve(allResponses);
72 | })
73 | .catch(function(allResponses) {
74 | resolve(u.map(allResponses, function (response) {
75 | if (response instanceof FreeswitchServer) {
76 | return response;
77 | } else {
78 | return {
79 | name: response.server.name,
80 | host: response.server.host,
81 | error: (response.status === 0) ? "Connection refused" : '(' + response.status + ') ' + response.statusText
82 | }
83 | }
84 | }));
85 | });
86 | });
87 | };
88 |
89 | FreeswitchRouter.prototype.hangup = function(server, conference, member) {
90 | var client = new FreeswitchClient(server, this.timeout);
91 | return client.hangup(conference.name, (member) ? member.id : null);
92 | };
93 |
94 | FreeswitchRouter.prototype.recordingCheck = function(server, conference) {
95 | var client = new FreeswitchClient(server, this.timeout);
96 | return client.recordingCheck(conference.name);
97 | };
98 |
99 | FreeswitchRouter.prototype.kill = function(server, call) {
100 | var client = new FreeswitchClient(server, this.timeout);
101 | return client.kill(call);
102 | };
103 |
104 | return new FreeswitchRouter();
105 | }]);
106 | }());
--------------------------------------------------------------------------------
/src/js/services/freeswitch/models/Call.js:
--------------------------------------------------------------------------------
1 | /*global angular, console */
2 | (function () {
3 | 'use strict';
4 | var module = angular.module('fsmgmt.services.freeswitch.models.Call', []);
5 |
6 | module.factory('Call', function () {
7 | var Call = function (callStr) {
8 | //console.log(JSON.stringify(callStr));
9 | this.bLeg = {};
10 | this.uuid = callStr[0];
11 | this.direction = callStr[2];
12 | this.created = callStr[4];
13 | this.createdEpoch = callStr[6];
14 | this.name = callStr[8];
15 | this.state = callStr[10];
16 | this.callerIdName = callStr[12];
17 | this.callerIdNumber = callStr[14];
18 | this.ipAddress = callStr[16];
19 | this.destination = callStr[18];
20 | this.application = callStr[20];
21 | this.applicationData = callStr[22];
22 | this.dialplan = callStr[24];
23 | this.context = callStr[26];
24 | this.readCodec = callStr[28];
25 | this.readRate = callStr[30];
26 | this.readBitrate = callStr[32];
27 | this.writeCodec = callStr[34];
28 | this.writeRate = callStr[36];
29 | this.writeBitrate = callStr[38];
30 | this.secure = callStr[40];
31 | this.hostname = callStr[42];
32 | this.presenceId = callStr[44];
33 | this.presenceData = callStr[46];
34 | this.accountCode = callStr[48];
35 | this.callState = callStr[50];
36 | this.calleeName = callStr[52];
37 | this.calleeNumber = callStr[54];
38 | this.calleeDirection = callStr[56];
39 | this.callUuid = callStr[58];
40 | this.sentCalleeName = callStr[60];
41 | this.sentCalleNum = callStr[62];
42 | this.bLeg.uuid = callStr[64];
43 | this.bLeg.direction = callStr[66];
44 | this.bLeg.created = callStr[68];
45 | this.bLeg.createdEpoch = callStr[70];
46 | this.bLeg.name = callStr[72];
47 | this.bLeg.state = callStr[74];
48 | this.bLeg.cidName = callStr[76];
49 | this.bLeg.cidNumber = callStr[78];
50 | this.bLeg.ipAddress = callStr[80];
51 | this.bLeg.destination = callStr[82];
52 | };
53 |
54 | return Call;
55 | });
56 | }());
--------------------------------------------------------------------------------
/src/js/services/freeswitch/models/Conference.js:
--------------------------------------------------------------------------------
1 | /*global angular, console */
2 | (function () {
3 | 'use strict';
4 | var module = angular.module('fsmgmt.services.freeswitch.models.Conference', []);
5 |
6 | module.factory('FreeswitchConference', function () {
7 | var FreeswitchConference = function (conferenceStr) {
8 | this.name= conferenceStr.substring(11, conferenceStr.indexOf(' ', 11)).s;
9 | this.members= [];
10 | this.flags = conferenceStr.substring(conferenceStr.indexOf('flags: ') + 7, conferenceStr.length - 1)
11 | .replaceAll('|', ', ').s;
12 | this.isRecording = this.flags.indexOf('recording') !== -1
13 | };
14 |
15 | return FreeswitchConference;
16 | });
17 | }());
--------------------------------------------------------------------------------
/src/js/services/freeswitch/models/Member.js:
--------------------------------------------------------------------------------
1 | /*global angular, console */
2 | (function () {
3 | 'use strict';
4 | var module = angular.module('fsmgmt.services.freeswitch.models.Member', []);
5 |
6 | module.factory('FreeswitchMember', function () {
7 | var FreeswitchMember = function (memberStr) {
8 | var memberAttributes = memberStr.split(';');
9 |
10 | this.id= memberAttributes[0];
11 | this.participantRegister= memberAttributes[1];
12 | this.channelId= memberAttributes[2];
13 | this.callerIdName= memberAttributes[3];
14 | this.callerIdNumber= memberAttributes[4];
15 | this.entitlements= memberAttributes[5];
16 | this.isInternalCall= (memberAttributes[1].indexOf("sofia/internal/conf_") > -1 ||
17 | memberAttributes[1].indexOf("sofia/internal/00000") > -1) || memberAttributes[1].indexOf("loopback") > -1
18 | };
19 |
20 | return FreeswitchMember;
21 | });
22 | }());
--------------------------------------------------------------------------------
/src/js/services/freeswitch/models/Server.js:
--------------------------------------------------------------------------------
1 | /*global angular, console */
2 | (function () {
3 | 'use strict';
4 | var module = angular.module('fsmgmt.services.freeswitch.models.Server', []);
5 |
6 | module.factory('FreeswitchServer', function () {
7 | var FreeswitchServer = function (options) {
8 | this.name = options.name;
9 | this.host = options.host;
10 | this.username = options.username;
11 | this.password = options.password;
12 | this.conferences = options.conferences;
13 | this.calls = options.calls;
14 | };
15 |
16 | return FreeswitchServer;
17 | });
18 | }());
--------------------------------------------------------------------------------
/src/js/services/freeswitch/parsers/CallListParser.js:
--------------------------------------------------------------------------------
1 | /*global angular, console */
2 | (function () {
3 | 'use strict';
4 | var module = angular.module('fsmgmt.services.freeswitch.parsers.CallListParser', [
5 | 'fsmgmt.services.freeswitch.models.Conference',
6 | 'fsmgmt.services.freeswitch.models.Call'
7 | ]);
8 |
9 | module.factory('CallListParser', ['$q', '$http', 'FreeswitchConference', 'Call',
10 | function ($q, $http, FreeswitchConference, Call) {
11 | var S = require('string');
12 | //var u = require('underscore');
13 | var cheerio = require('cheerio');
14 | var tabletojson = require('tabletojson');
15 |
16 | var CallListParser = function () {
17 | };
18 |
19 | CallListParser.prototype.parse = function(fsTextResponse) {
20 | return $q(function(resolve, reject) {
21 | var response = [];
22 |
23 | // parse html table in response into json object
24 | var $ = cheerio.load(fsTextResponse);
25 | var tableHtml = $('table').html();
26 |
27 | if (S(tableHtml).isEmpty()) {
28 | resolve(response);
29 | } else {
30 | var tableAsJson = tabletojson.convert('');
31 |
32 | // iterate results
33 | for (var i = 0; i < tableAsJson[0].length; i++) {
34 | if (tableAsJson[0].hasOwnProperty(i)) {
35 | var row = tableAsJson[0][i];
36 |
37 | // ignore row 0 which is the header row
38 | if (i > 0) {
39 | // build call based on row
40 | response.push(new Call(row));
41 | }
42 | }
43 | }
44 |
45 | resolve(response);
46 | }
47 | });
48 | };
49 |
50 | return CallListParser;
51 | }
52 | ]);
53 | }());
--------------------------------------------------------------------------------
/src/js/services/freeswitch/parsers/ConferenceListParser.js:
--------------------------------------------------------------------------------
1 | /*global angular, console */
2 | (function () {
3 | 'use strict';
4 | var module = angular.module('fsmgmt.services.freeswitch.parsers.ConferenceListParser', [
5 | 'fsmgmt.services.freeswitch.models.Conference',
6 | 'fsmgmt.services.freeswitch.models.Member'
7 | ]);
8 |
9 | module.factory('ConferenceListParser', ['$q', '$http', 'FreeswitchConference', 'FreeswitchMember',
10 | function ($q, $http, FreeswitchConference, FreeswitchMember) {
11 | var S = require('string');
12 | var u = require('underscore');
13 |
14 | var ConferenceListParser = function () {
15 | };
16 |
17 | ConferenceListParser.prototype.parse = function(fsTextResponse) {
18 | return $q(function(resolve, reject) {
19 | var response = [];
20 |
21 | if (S(fsTextResponse).contains('No active conferences.')) {
22 | resolve(response);
23 | } else {
24 | u.each(S(fsTextResponse).lines(), function (line) {
25 | line = S(line).replaceAll('', '').trim();
26 |
27 | if (!line.isEmpty()) {
28 | // starts new conference
29 | if (line.startsWith('Conference ')) {
30 | response.push(new FreeswitchConference(line));
31 | }
32 | // its a member in the current conference
33 | else {
34 | response[response.length - 1].members.push(new FreeswitchMember(line));
35 | }
36 | }
37 | });
38 |
39 | resolve(response);
40 | }
41 | });
42 | };
43 |
44 | return ConferenceListParser;
45 | }
46 | ]);
47 | }());
--------------------------------------------------------------------------------