├── .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 | 11 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Run2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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: [![GitHub release](https://img.shields.io/github/release/pablote/fs-mgmt.svg)]() 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 | ![App Screen 1](/screenshots/v1.2.0/1.png?raw=true "App Screen 1") 25 | 26 | ![App Screen 2](/screenshots/v1.2.0/2.png?raw=true "App Screen 2") 27 | 28 | ![App Screen 3](/screenshots/v1.2.0/3.png?raw=true "App Screen 3") 29 | 30 | ![App Screen 4](/screenshots/v1.2.0/4.png?raw=true "App Screen 4") 31 | 32 | ![App Screen 5](/screenshots/v1.2.0/5.png?raw=true "App Screen 5") 33 | 34 | ![App Screen 6](/screenshots/v1.2.0/6.png?raw=true "App Screen 6") 35 | 36 | ![App Screen 7](/screenshots/v1.2.0/7.png?raw=true "App Screen 7") 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 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | -------------------------------------------------------------------------------- /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 |
3 |
4 | 5 | 6 | 7 | 10 | 11 |
12 | 16 |
17 |
18 |
19 |
20 |
21 |
22 |

{{ server.name }}

23 | 24 |
{{ server.error }}
25 | 26 | No calls on this server 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | 49 | 54 | 55 | 56 | 62 | 63 | 64 |
UUIDDirectionCreatedStateCaller Id (Name)IP AddressDestinationActions
44 | {{ ::call.uuid }} 45 | {{ ::call.direction }}{{ ::call.created }}{{ ::call.state }} 50 |
51 | {{ ::call.callerIdName }} ({{ ::call.callerIdNumber }}) 52 |
53 |
{{ ::call.ipAddress }}{{ ::call.destination }} 57 | 60 | 61 |
65 |
66 |
67 |
68 | 71 | 87 |
-------------------------------------------------------------------------------- /app/html/conferences/main.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 | 7 | 10 | 11 |
12 | 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |

{{ server.name }}

24 | 25 | {{ server.conferences.length }} conference{{ server.conferences.length > 1 ? 's' : '' }} 26 | 27 |
28 | 29 |
{{ server.error }}
30 | 31 |
32 | No conferences on this server 33 |
34 | 35 |
36 | 37 |
38 |

{{ conference.name }}

39 | 40 | 43 | 44 | 46 | 47 | 50 | 51 | 53 | 54 | 55 | {{ conference.members.length }} member{{ conference.members.length > 1 ? 's' : '' }} 56 | 57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 80 | 85 | 86 | 90 | 91 | 92 |
IdRegisterChannel IdCaller IdEntitlementsActions
{{ ::member.id }}{{ ::member.participantRegister }} 75 | {{ ::member.channelId }} 76 | 79 | {{ ::member.callerIdName }} 81 | 82 | ({{ ::member.callerIdNumber }}) 83 | 84 | {{ ::member.entitlements }} 87 | 89 |
93 |
94 |
95 |
96 |
97 |
-------------------------------------------------------------------------------- /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 | 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('' + tableHtml + '
'); 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 | }());


--------------------------------------------------------------------------------