├── astconfman
├── utils
│ ├── __init__.py
│ ├── formatters.py
│ └── validators.py
├── instance
│ └── .gitignore
├── migrations
│ ├── README
│ ├── script.py.mako
│ ├── versions
│ │ ├── 1a9f196d43b2_.py
│ │ ├── 563f582d07fa_.py
│ │ ├── 2728b7328b78_.py
│ │ ├── 3a15e901e08c_.py
│ │ ├── d7c7f3be40a_.py
│ │ ├── c7c5c7d112c_.py
│ │ └── 2798bc43117a_.py
│ ├── alembic.ini
│ └── env.py
├── static
│ ├── logo.png
│ ├── favicon.ico
│ └── lang
│ │ ├── languages.png
│ │ └── languages.min.css
├── cron_job.sh
├── translations
│ ├── ru
│ │ └── LC_MESSAGES
│ │ │ ├── messages.mo
│ │ │ └── messages.po
│ └── messages.pot
├── babel.cfg
├── translate.sh
├── templates
│ ├── contact_create.html
│ ├── contact_import.html
│ ├── security
│ │ ├── _messages.html
│ │ ├── _macros.html
│ │ ├── _menu.html
│ │ ├── login_user.html
│ │ └── register_user.html
│ ├── conference_schedule_list.html
│ ├── conference_create.html
│ ├── admin
│ │ └── index.html
│ ├── conference_edit.html
│ ├── my_master.html
│ ├── action_conference.html
│ └── conference_details.html
├── run.py
├── forms.py
├── config.py
├── manage.py
├── app.py
├── asterisk.py
├── models.py
└── views.py
├── .gitignore
├── astconfman.service
├── requirements.txt
├── asterisk_etc
├── confbridge.conf
├── manager.conf
└── extensions.conf
├── README.md
└── LICENSE.txt
/astconfman/utils/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/astconfman/utils/formatters.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/astconfman/instance/.gitignore:
--------------------------------------------------------------------------------
1 | config.py
2 |
--------------------------------------------------------------------------------
/astconfman/migrations/README:
--------------------------------------------------------------------------------
1 | Generic single-database configuration.
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | astconfman/astconfman.db
3 | astconfman/astconfman.log
4 |
--------------------------------------------------------------------------------
/astconfman/static/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litnimax/astconfman/HEAD/astconfman/static/logo.png
--------------------------------------------------------------------------------
/astconfman/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litnimax/astconfman/HEAD/astconfman/static/favicon.ico
--------------------------------------------------------------------------------
/astconfman/static/lang/languages.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litnimax/astconfman/HEAD/astconfman/static/lang/languages.png
--------------------------------------------------------------------------------
/astconfman/cron_job.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd `dirname $0`
4 |
5 | source env/bin/activate
6 |
7 | ./manage.py start_conf $1
8 |
--------------------------------------------------------------------------------
/astconfman/translations/ru/LC_MESSAGES/messages.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litnimax/astconfman/HEAD/astconfman/translations/ru/LC_MESSAGES/messages.mo
--------------------------------------------------------------------------------
/astconfman/babel.cfg:
--------------------------------------------------------------------------------
1 | [ignore: env/**]
2 | [python: **.py]
3 | [jinja2: **/templates/**.html]
4 | encoding = utf-8
5 | extensions=jinja2.ext.autoescape,jinja2.ext.with_
6 |
--------------------------------------------------------------------------------
/astconfman/translate.sh:
--------------------------------------------------------------------------------
1 | pybabel extract -F babel.cfg -k lazy_gettext -o translations/messages.pot .
2 | pybabel update -i translations/messages.pot -d translations
3 | pybabel compile -d translations/
4 |
--------------------------------------------------------------------------------
/astconfman/templates/contact_create.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/model/create.html" %}
2 |
3 | {% block tail %}
4 |
7 | {% endblock %}
--------------------------------------------------------------------------------
/astconfman/templates/contact_import.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/master.html" %}
2 | {% import "admin/lib.html" as lib with context %}
3 |
4 | {% block body %}
5 | {{_('Import Contacts from a CSV file (phone, name):')}}
6 | {{ lib.render_form(form, dir_url) }}
7 | {{ error }}
8 | {% endblock %}
--------------------------------------------------------------------------------
/astconfman/templates/security/_messages.html:
--------------------------------------------------------------------------------
1 | {%- with messages = get_flashed_messages(with_categories=true) -%}
2 | {% if messages %}
3 |
4 | {% for category, message in messages %}
5 | - {{ message }}
6 | {% endfor %}
7 |
8 | {% endif %}
9 | {%- endwith %}
--------------------------------------------------------------------------------
/astconfman/templates/conference_schedule_list.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/model/list.html" %}
2 |
3 | {% block model_menu_bar %}
4 |
7 | {{ super() }}
8 | {% endblock %}
--------------------------------------------------------------------------------
/astconfman/run.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import sys
3 | from gevent.wsgi import WSGIServer
4 | from app import app
5 |
6 |
7 | if __name__=='__main__':
8 | server = WSGIServer((app.config['LISTEN_ADDRESS'],
9 | app.config['LISTEN_PORT']),
10 | app)
11 | server.serve_forever()
--------------------------------------------------------------------------------
/astconfman.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=AsteriskConferenceManager
3 | After=network.target
4 |
5 | [Service]
6 | WorkingDirectory=/etc/asterisk/astconfman/astconfman/
7 | ExecStart=/etc/asterisk/astconfman/env/bin/python /etc/asterisk/astconfman/astconfman/run.py
8 | Restart=always
9 |
10 | [Install]
11 | WantedBy=multi-user.target
12 | Alias=astconfman.service
13 |
--------------------------------------------------------------------------------
/astconfman/templates/conference_create.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/model/create.html" %}
2 |
3 | {% macro conference_participants_link() %}
4 |
12 | {% endmacro %}
--------------------------------------------------------------------------------
/astconfman/templates/admin/index.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/master.html' %}
2 |
3 | {% block body %}
4 | {% if config['BRAND_LOGO'] %}
5 |
12 | {% endif %}
13 |
14 | {% endblock %}
--------------------------------------------------------------------------------
/astconfman/templates/conference_edit.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/model/edit.html" %}
2 | {% import 'admin/actions.html' as actionlib with context %}
3 |
4 | {% macro conference_participants_link() %}
5 |
13 | {% endmacro %}
14 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Babel
2 | Flask==0.12.4
3 | SQLAlchemy==1.2.0b3
4 | flask-admin==1.3.0
5 | Flask-BabelEx
6 | Flask-Bootstrap
7 | Flask-SQLAlchemy
8 | Flask-WTF==0.14.3
9 | Flask-Script
10 | Flask-Migrate==2.7.0
11 | Flask-Security
12 | Jinja2
13 | MarkupSafe
14 | WTForms
15 | blinker
16 | gevent==1.2.2
17 | transliterate
18 | itsdangerous
19 | python-crontab
20 | pytz
21 | speaklater
22 | asterisk-ami
23 | alembic==0.8.6
24 | Werkzeug==0.11.10
25 | email_validator==1.1.3
26 |
--------------------------------------------------------------------------------
/asterisk_etc/confbridge.conf:
--------------------------------------------------------------------------------
1 | ; See https://wiki.asterisk.org/wiki/display/AST/ConfBridge#ConfBridge-ConferenceMenuConfigurationOptions
2 | [user_menu]
3 | type=menu
4 | 1=toggle_mute
5 | 2=dialplan_exec(confman-unmute-request,s,1)
6 | 3=admin_toggle_mute_participants
7 | 4=decrease_listening_volume
8 | 5=reset_listening_volume
9 | 6=increase_listening_volume
10 | 7=decrease_talking_volume
11 | 8=reset_talking_volume
12 | 9=increase_talking_volume
13 | 0=dialplan_exec(confman-invite-all,s,1)
14 |
--------------------------------------------------------------------------------
/astconfman/migrations/script.py.mako:
--------------------------------------------------------------------------------
1 | """${message}
2 |
3 | Revision ID: ${up_revision}
4 | Revises: ${down_revision}
5 | Create Date: ${create_date}
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = ${repr(up_revision)}
11 | down_revision = ${repr(down_revision)}
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 | ${imports if imports else ""}
16 |
17 | def upgrade():
18 | ${upgrades if upgrades else "pass"}
19 |
20 |
21 | def downgrade():
22 | ${downgrades if downgrades else "pass"}
23 |
--------------------------------------------------------------------------------
/asterisk_etc/manager.conf:
--------------------------------------------------------------------------------
1 | ;
2 | ; Asterisk Call Management support
3 | ;
4 |
5 | ; By default asterisk will listen on localhost only.
6 | [general]
7 | enabled = yes
8 | port = 5038
9 | bindaddr = 127.0.0.1
10 |
11 | [conf]
12 | secret = 7890ec8ff2955ec70a1b390b62f023da
13 | read = system,call,log,verbose,command,agent,user,config,command,dtmf,reporting,cdr,dialplan,originate,message
14 | writetimeout = 500
15 | ;On high load systems amount of messages may overhelm python parser
16 | ;to mitigate it use event filters
17 | eventfilter=ConfbridgeTalking
18 |
--------------------------------------------------------------------------------
/astconfman/migrations/versions/1a9f196d43b2_.py:
--------------------------------------------------------------------------------
1 | """empty message
2 |
3 | Revision ID: 1a9f196d43b2
4 | Revises: None
5 | Create Date: 2015-08-17 18:06:31.273894
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '1a9f196d43b2'
11 | down_revision = None
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | pass
20 | ### end Alembic commands ###
21 |
22 |
23 | def downgrade():
24 | ### commands auto generated by Alembic - please adjust! ###
25 | pass
26 | ### end Alembic commands ###
27 |
--------------------------------------------------------------------------------
/astconfman/migrations/versions/563f582d07fa_.py:
--------------------------------------------------------------------------------
1 | """empty message
2 |
3 | Revision ID: 563f582d07fa
4 | Revises: 1a9f196d43b2
5 | Create Date: 2015-08-17 18:06:50.928876
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '563f582d07fa'
11 | down_revision = '1a9f196d43b2'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.add_column('participant', sa.Column('is_invited', sa.Boolean(), nullable=True))
20 | ### end Alembic commands ###
21 |
22 |
23 | def downgrade():
24 | ### commands auto generated by Alembic - please adjust! ###
25 | op.drop_column('participant', 'is_invited')
26 | ### end Alembic commands ###
27 |
--------------------------------------------------------------------------------
/astconfman/templates/security/_macros.html:
--------------------------------------------------------------------------------
1 | {% macro render_field_with_errors(field) %}
2 |
3 |
13 | {% endmacro %}
14 |
15 | {% macro render_field(field) %}
16 | {{ field(class_='form-control', **kwargs)|safe }}
17 | {% endmacro %}
18 |
19 | {% macro render_checkbox_field(field) -%}
20 |
27 | {%- endmacro %}
--------------------------------------------------------------------------------
/astconfman/templates/security/_menu.html:
--------------------------------------------------------------------------------
1 | {% if security.registerable or security.recoverable or security.confirmable %}
2 | Menu
3 |
4 | - Login
5 | {% if security.registerable %}
6 | - Register
7 | {% endif %}
8 | {% if security.recoverable %}
9 | - Forgot password
10 | {% endif %}
11 | {% if security.confirmable %}
12 | - Confirm account
13 | {% endif %}
14 |
15 | {% endif %}
--------------------------------------------------------------------------------
/astconfman/migrations/versions/2728b7328b78_.py:
--------------------------------------------------------------------------------
1 | """empty message
2 |
3 | Revision ID: 2728b7328b78
4 | Revises: d7c7f3be40a
5 | Create Date: 2015-10-20 13:44:12.129389
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '2728b7328b78'
11 | down_revision = 'd7c7f3be40a'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.drop_table('conference_schedule')
20 | ### end Alembic commands ###
21 |
22 |
23 | def downgrade():
24 | ### commands auto generated by Alembic - please adjust! ###
25 | op.create_table('conference_schedule',
26 | sa.Column('id', sa.INTEGER(), nullable=False),
27 | sa.Column('entry', sa.VARCHAR(length=256), nullable=True),
28 | sa.PrimaryKeyConstraint('id')
29 | )
30 | ### end Alembic commands ###
31 |
--------------------------------------------------------------------------------
/astconfman/migrations/versions/3a15e901e08c_.py:
--------------------------------------------------------------------------------
1 | """empty message
2 |
3 | Revision ID: 3a15e901e08c
4 | Revises: 2728b7328b78
5 | Create Date: 2015-10-20 13:44:28.574687
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '3a15e901e08c'
11 | down_revision = '2728b7328b78'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.create_table('conference_schedule',
20 | sa.Column('id', sa.Integer(), nullable=False),
21 | sa.Column('entry', sa.String(length=256), nullable=True),
22 | sa.PrimaryKeyConstraint('id')
23 | )
24 | ### end Alembic commands ###
25 |
26 |
27 | def downgrade():
28 | ### commands auto generated by Alembic - please adjust! ###
29 | op.drop_table('conference_schedule')
30 | ### end Alembic commands ###
31 |
--------------------------------------------------------------------------------
/astconfman/migrations/versions/d7c7f3be40a_.py:
--------------------------------------------------------------------------------
1 | """empty message
2 |
3 | Revision ID: d7c7f3be40a
4 | Revises: 563f582d07fa
5 | Create Date: 2015-10-20 13:40:57.501602
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = 'd7c7f3be40a'
11 | down_revision = '563f582d07fa'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.create_table('conference_schedule',
20 | sa.Column('id', sa.Integer(), nullable=False),
21 | sa.Column('entry', sa.String(length=256), nullable=True),
22 | sa.PrimaryKeyConstraint('id')
23 | )
24 | ### end Alembic commands ###
25 |
26 |
27 | def downgrade():
28 | ### commands auto generated by Alembic - please adjust! ###
29 | op.drop_table('conference_schedule')
30 | ### end Alembic commands ###
31 |
--------------------------------------------------------------------------------
/astconfman/migrations/versions/c7c5c7d112c_.py:
--------------------------------------------------------------------------------
1 | """empty message
2 |
3 | Revision ID: c7c5c7d112c
4 | Revises: 3a15e901e08c
5 | Create Date: 2015-10-20 15:33:37.545510
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = 'c7c5c7d112c'
11 | down_revision = '3a15e901e08c'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.add_column('conference_schedule', sa.Column('conference', sa.Integer(), nullable=True))
20 | op.create_foreign_key(None, 'conference_schedule', 'conference', ['conference'], ['id'])
21 | ### end Alembic commands ###
22 |
23 |
24 | def downgrade():
25 | ### commands auto generated by Alembic - please adjust! ###
26 | op.drop_constraint(None, 'conference_schedule', type_='foreignkey')
27 | op.drop_column('conference_schedule', 'conference')
28 | ### end Alembic commands ###
29 |
--------------------------------------------------------------------------------
/astconfman/migrations/alembic.ini:
--------------------------------------------------------------------------------
1 | # A generic, single database configuration.
2 |
3 | [alembic]
4 | # template used to generate migration files
5 | # file_template = %%(rev)s_%%(slug)s
6 |
7 | # set to 'true' to run the environment during
8 | # the 'revision' command, regardless of autogenerate
9 | # revision_environment = false
10 |
11 |
12 | # Logging configuration
13 | [loggers]
14 | keys = root,sqlalchemy,alembic
15 |
16 | [handlers]
17 | keys = console
18 |
19 | [formatters]
20 | keys = generic
21 |
22 | [logger_root]
23 | level = WARN
24 | handlers = console
25 | qualname =
26 |
27 | [logger_sqlalchemy]
28 | level = WARN
29 | handlers =
30 | qualname = sqlalchemy.engine
31 |
32 | [logger_alembic]
33 | level = INFO
34 | handlers =
35 | qualname = alembic
36 |
37 | [handler_console]
38 | class = StreamHandler
39 | args = (sys.stderr,)
40 | level = NOTSET
41 | formatter = generic
42 |
43 | [formatter_generic]
44 | format = %(levelname)-5.5s [%(name)s] %(message)s
45 | datefmt = %H:%M:%S
46 |
--------------------------------------------------------------------------------
/astconfman/templates/security/login_user.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/master.html' %}
2 | {% from "security/_macros.html" import render_field, render_field_with_errors, render_checkbox_field %}
3 | {% include "security/_messages.html" %}
4 | {% block body %}
5 | {{ super() }}
6 |
7 |
8 |
Login
9 |
10 |
18 |
19 |
20 |
21 | {% endblock body %}
22 |
23 |
--------------------------------------------------------------------------------
/astconfman/templates/security/register_user.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/master.html' %}
2 | {% from "security/_macros.html" import render_field_with_errors, render_field %}
3 | {% include "security/_messages.html" %}
4 | {% block body %}
5 | {{ super() }}
6 |
7 |
8 |
Register
9 |
10 |
19 |
Already signed up? Please log in.
20 |
21 |
22 |
23 | {% endblock body %}
--------------------------------------------------------------------------------
/astconfman/utils/validators.py:
--------------------------------------------------------------------------------
1 | from flask_babelex import gettext
2 | from wtforms.validators import ValidationError
3 | from crontab import CronTab, CronItem
4 | from models import Participant
5 |
6 |
7 |
8 | def is_number(form, field):
9 | if field.data and not field.data.isdigit():
10 | raise ValidationError(gettext('Must be a number!'))
11 |
12 |
13 | def is_participant_uniq(form, field):
14 | #
15 | p = Participant.query.filter_by(conference=form.data['conference'],
16 | phone=form.data['phone']).first()
17 | if p:
18 | raise ValidationError(
19 | gettext('Participant with phone number %(num)s already there.',
20 | num=form.data['phone']))
21 |
22 |
23 |
24 | def is_crontab_valid(form, field):
25 | item = CronItem(field.data + ' /bin/echo # Just a test', cron=CronTab())
26 | # May be I will refactor to this:
27 | #item = cron.new(command='/bin/echo', comment='Aaaaa')
28 | #item.hour.every(4)
29 | #item.minute.during(5,50).every(2)
30 | #item.day.on(4,5,6)
31 | #item.dow.on(1)
32 | #item.month.during(1,2)
33 | if not item.is_valid():
34 | raise ValidationError(gettext('%(job)s is not a correct crontab entry.',
35 | job=field.data))
36 |
37 |
--------------------------------------------------------------------------------
/astconfman/forms.py:
--------------------------------------------------------------------------------
1 | # *-* encoding:utf-8 *-*
2 |
3 | from flask_wtf import Form
4 | from flask_admin.form import BaseForm as BaseAdminForm
5 | from flask_wtf.file import FileField, file_required, file_allowed
6 | from wtforms.validators import ValidationError
7 | from flask_babelex import lazy_gettext as _
8 |
9 |
10 | class ContactImportForm(Form):
11 | filename = FileField(_('File'), validators=[
12 | file_required(),
13 | file_allowed(['csv', 'CSV'])])
14 |
15 | def validate_filename(form, field):
16 | data = field.data.readlines()
17 | linenum = 1
18 | for line in data:
19 | if not len(line.split(',')) == 2:
20 | msg = _('CSV file is broken, line %(linenum)s',
21 | linenum=linenum)
22 | raise ValidationError(msg)
23 | elif not line[0].isdigit():
24 | raise ValidationError(_(
25 | 'The first column does not contain phone '
26 | 'number, line %(linenum)s', linenum=linenum))
27 | linenum += 1
28 | field.data.seek(0)
29 |
30 |
31 | class ConferenceForm(BaseAdminForm):
32 |
33 | def validate_is_public(self, field):
34 | profile = self.data.get('public_participant_profile')
35 | if not profile:
36 | raise ValidationError(_(u'You must select a Public Participant'
37 | ' Profile for a Public Conference.'))
38 |
39 |
--------------------------------------------------------------------------------
/astconfman/templates/my_master.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/base.html' %}
2 |
3 | {% block head_css %}
4 | {{ super() }}
5 |
6 | {% endblock %}
7 |
8 | {% block access_control %}
9 |
10 |
11 | {% if current_user.is_authenticated %}
12 |
13 | {{_('Logout')}}
14 |
15 | {% else %}
16 |
17 | {{_('Login')}}
18 |
19 | {% endif %}
20 |
21 | {{_('Language')}}
22 |
23 |
31 |
32 |
33 | {% endblock %}
34 |
35 | {% block tail %}
36 |
37 |
38 |
39 |
40 | {% endblock %}
--------------------------------------------------------------------------------
/astconfman/templates/action_conference.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {% if current_user.has_role('admin') %}
18 |