├── PROCESS.md ├── README.md ├── __init__.py ├── config ├── __init__.py ├── admin.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto__chg_field_certificate_ca.py │ ├── 0003_auto__del_certificate__add_certficate__add_certificateauthority.py │ ├── 0004_auto__del_certficate__add_certificate__del_field_certificateauthority_.py │ ├── 0005_auto__add_field_certificate_date_created__add_field_certificate_device.py │ ├── 0006_auto__add_field_certificate_md5.py │ ├── 0007_auto__add_field_certificate_serial.py │ └── __init__.py ├── models.py ├── templates │ └── config │ │ ├── generateCA.html │ │ └── index.html ├── tests.py ├── urls.py └── views.py ├── manage.py ├── managedmacadmin ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py ├── mdm ├── NOTES ├── ProfileList.plist ├── __init__.py ├── admin.py ├── commands.py ├── geo.py ├── groups_views.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── cron.py ├── mcx.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto__del_unique_device_serial_number.py │ ├── 0003_auto__add_field_device_model_name.py │ ├── 0004_auto__del_field_command_command__add_field_command_data__chg_field_com.py │ ├── 0005_auto__del_managedpreference__del_unique_managedpreference_application_.py │ ├── 0006_auto__add_inventoryitem.py │ ├── 0007_auto__del_inventoryitem__add_appinventoryitem.py │ ├── 0008_auto__add_lastdevicestatus.py │ ├── 0009_auto__del_lastdevicestatus__add_lastcommandstatus.py │ ├── 0010_auto__del_command__add_devicecommand.py │ ├── 0011_auto__add_devicecheckin.py │ ├── 0012_auto__add_field_devicecheckin_ip.py │ ├── 0013_auto__add_deviceprofile.py │ ├── 0014_auto__add_field_deviceprofile_payload_uuid__add_unique_deviceprofile_d.py │ ├── 0015_auto__del_field_deviceprofile_description__del_field_deviceprofile_rem.py │ ├── 0016_auto__add_profileinventoryitem.py │ ├── 0017_auto__chg_field_profileinventoryitem_uuid.py │ ├── 0018_auto__add_field_devicecommand_attempts__add_field_devicecommand_date_c.py │ ├── 0019_auto__del_field_devicecheckin_date__add_field_devicecheckin_start_date.py │ ├── 0020_auto__del_lastcommandstatus.py │ ├── 0021_auto__add_devicegroup__add_devicegroupmanagedpreference__add_unique_de.py │ ├── 0022_auto__del_field_devicegroupmanagedpreference_frequency__del_unique_dev.py │ ├── 0023_auto__del_field_devicegroupmanagedpreference_application__add_field_de.py │ ├── 0024_auto__del_field_devicemanagedpreference_application__del_field_devicem.py │ ├── 0025_auto__add_field_devicegroup_priority.py │ ├── 0026_auto__add_field_device_last_notification__add_field_device_type__add_f.py │ ├── 0027_auto.py │ ├── 0028_auto.py │ ├── 0029_auto.py │ └── __init__.py ├── models.py ├── plistlib.py ├── preferences_views.py ├── push.py ├── pushcli.py ├── standard_commands.py ├── static │ └── mdm │ │ ├── jquery.plist.css │ │ ├── jquery.plist.js │ │ ├── mdm.js │ │ ├── selectize.bootstrap2.css │ │ ├── selectize.min.js │ │ └── style.css ├── templates │ └── mdm │ │ ├── battery_level.html │ │ ├── detail.html │ │ ├── enroll.html │ │ ├── groups │ │ ├── detail.html │ │ └── index.html │ │ ├── index.html │ │ └── preferences │ │ ├── device.html │ │ └── devicegroup.html ├── templatetags │ ├── __init__.py │ └── mdm_extras.py ├── urls.py └── views.py ├── reports ├── __init__.py ├── admin.py ├── models.py ├── tests.py ├── urls.py └── views.py ├── static ├── admin │ ├── css │ │ ├── base.css │ │ ├── changelists.css │ │ ├── dashboard.css │ │ ├── forms.css │ │ ├── ie.css │ │ ├── login.css │ │ ├── rtl.css │ │ └── widgets.css │ ├── img │ │ ├── changelist-bg.gif │ │ ├── changelist-bg_rtl.gif │ │ ├── chooser-bg.gif │ │ ├── chooser_stacked-bg.gif │ │ ├── default-bg-reverse.gif │ │ ├── default-bg.gif │ │ ├── deleted-overlay.gif │ │ ├── gis │ │ │ ├── move_vertex_off.png │ │ │ └── move_vertex_on.png │ │ ├── icon-no.gif │ │ ├── icon-unknown.gif │ │ ├── icon-yes.gif │ │ ├── icon_addlink.gif │ │ ├── icon_alert.gif │ │ ├── icon_calendar.gif │ │ ├── icon_changelink.gif │ │ ├── icon_clock.gif │ │ ├── icon_deletelink.gif │ │ ├── icon_error.gif │ │ ├── icon_searchbox.png │ │ ├── icon_success.gif │ │ ├── inline-delete-8bit.png │ │ ├── inline-delete.png │ │ ├── inline-restore-8bit.png │ │ ├── inline-restore.png │ │ ├── inline-splitter-bg.gif │ │ ├── nav-bg-grabber.gif │ │ ├── nav-bg-reverse.gif │ │ ├── nav-bg-selected.gif │ │ ├── nav-bg.gif │ │ ├── selector-icons.gif │ │ ├── selector-search.gif │ │ ├── sorting-icons.gif │ │ ├── tool-left.gif │ │ ├── tool-left_over.gif │ │ ├── tool-right.gif │ │ ├── tool-right_over.gif │ │ ├── tooltag-add.gif │ │ ├── tooltag-add_over.gif │ │ ├── tooltag-arrowright.gif │ │ └── tooltag-arrowright_over.gif │ └── js │ │ ├── LICENSE-JQUERY.txt │ │ ├── SelectBox.js │ │ ├── SelectFilter2.js │ │ ├── actions.js │ │ ├── actions.min.js │ │ ├── admin │ │ ├── DateTimeShortcuts.js │ │ └── RelatedObjectLookups.js │ │ ├── calendar.js │ │ ├── collapse.js │ │ ├── collapse.min.js │ │ ├── core.js │ │ ├── inlines.js │ │ ├── inlines.min.js │ │ ├── jquery.init.js │ │ ├── jquery.js │ │ ├── jquery.min.js │ │ ├── prepopulate.js │ │ ├── prepopulate.min.js │ │ ├── timeparse.js │ │ └── urlify.js ├── css │ ├── bootstrap-responsive.min.css │ ├── bootstrap.min.css │ ├── datatables-bootstrap.css │ ├── font-awesome.min.css │ ├── site.css │ ├── smoothness │ │ ├── images │ │ │ ├── animated-overlay.gif │ │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ │ ├── ui-bg_glass_55_fbf9ee_1x400.png │ │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ │ ├── ui-bg_glass_75_dadada_1x400.png │ │ │ ├── ui-bg_glass_75_e6e6e6_1x400.png │ │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ │ ├── ui-bg_highlight-soft_75_cccccc_1x100.png │ │ │ ├── ui-icons_222222_256x240.png │ │ │ ├── ui-icons_2e83ff_256x240.png │ │ │ ├── ui-icons_454545_256x240.png │ │ │ ├── ui-icons_888888_256x240.png │ │ │ └── ui-icons_cd0a0a_256x240.png │ │ └── jquery-ui-1.10.3.custom.min.css │ └── style.css ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ └── fontawesome-webfont.woff ├── img │ ├── arrow-down.gif │ ├── arrow-up.gif │ ├── glyphicons-halflings-white.png │ ├── glyphicons-halflings.png │ └── loading.gif ├── js │ ├── bootstrap.min.js │ ├── datatables-bootstrap.js │ ├── jquery-1.9.1.js │ ├── jquery-ui-1.10.3.custom.min.js │ └── jquery.dataTables.min.js └── mdm │ ├── jquery.plist.css │ ├── jquery.plist.js │ ├── mdm.js │ ├── selectize.bootstrap2.css │ ├── selectize.min.js │ └── style.css └── templates ├── base.html └── registration └── login.html /PROCESS.md: -------------------------------------------------------------------------------- 1 | # Do something to get the data from the pkcs7 wrapper 2 | b = BIO.openfile('test.csr') 3 | p7 = SMIME.PKCS7(m2.pkcs7_read_bio_der(b._ptr()), 1) 4 | s = SMIME.SMIME() 5 | sk = X509.X509_Stack() 6 | s.set_x509_stack(sk) 7 | st = X509.X509_Store() 8 | s.set_x509_store(st) 9 | data = s.verify(p7, None, SMIME.PKCS7_NOVERIFY) 10 | 11 | 12 | # Decrypt the data 13 | b = BIO.MemoryBuffer(pkcs-data-from-above) 14 | p7 = SMIME.PKCS7(m2.pkcs7_read_bio_der(b._ptr()), 1) 15 | k = BIO.MemoryBuffer(cakey) 16 | c = BIO.MemoryBuffer(cacrt) 17 | sender = SMIME.SMIME() 18 | sender.load_key_bio(k, c) 19 | data = sender.decrypt(p7) 20 | 21 | data == CSR to sign 22 | 23 | 24 | 25 | ----- 26 | 27 | CSR in PEM format: 28 | openssl crl2pkcs7 -nocrl -certfile client.pem -outform DER -out client-deg.der 29 | 30 | Now we have the degenerate PKCS7. We need to encrypt it with the 31 | original request certificate. Cipher is des-ede3-cbc (des3). 32 | openssl smime -encrypt -in client-deg.der -des3 -binary -outform DER request.crt >client-enc.der 33 | 34 | Finally, we need to pkcs7 sign the data with our CA cert & key. 35 | openssl smime -sign -in client-enc.der -signer ca.crt -inkey ca.key -outform DER -nodetach >client.p7 36 | 37 | Verify: 38 | openssl smime -verify -in client.p7 -inform DER -CAfile ca.crt 39 | 40 | Send client.p7 back with Content-Type: application/x-pki-message 41 | 42 | 43 | https://github.com/nolanbrown/ios-cert-enrollment/blob/master/lib/ios-cert-enrollment/sign.rb 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Managed Mac Admin is a Python/VirtualEnv based MDM server. 2 | 3 | This was a project I worked on a few years ago. In the end we decided 4 | to move to a 3rd party commercial MDM solution. Not because this one 5 | did not work well but because my position changed and I was no longer 6 | going to be doing programming and wouldn't be able to keep the system 7 | maintained. We ran this system for about 9 months with no functional 8 | problems, so it does work, though it still needs a lot of TLC if you 9 | plan to use it in a production environment. 10 | 11 | 12 | To get up and running (no promises, most of this is from memory): 13 | 14 | Needed manual update to APNSWrapper to support TLS. 15 | in connection.py change PROTOCOL_SSLv3 to PROTOCOL_SSLv23 16 | 17 | From your virtual environment, run: 18 | 19 | python manage.py syncdb 20 | chmod a+w db.sqlite3 (web server needs write access) 21 | python manage.py migrate 22 | python manage.py collectstatic 23 | 24 | You should now be able to login to the MDM server. 25 | Go to the Config section and Configure your Certificate Authority. 26 | 27 | You should now be able to enroll devices. 28 | 29 | To enable Push-Notifications to work, you need to create your push 30 | certificate and call it "pushcert.pem" then place it in the mdm 31 | directory. 32 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/__init__.py -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/config/__init__.py -------------------------------------------------------------------------------- /config/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from models import Certificate, CertificateAuthority 3 | 4 | admin.site.register(Certificate) 5 | admin.site.register(CertificateAuthority) 6 | -------------------------------------------------------------------------------- /config/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'Certificate' 12 | db.create_table(u'config_certificate', ( 13 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('revoked', self.gf('django.db.models.fields.BooleanField')(default=False)), 15 | ('key', self.gf('django.db.models.fields.BinaryField')()), 16 | ('certificate', self.gf('django.db.models.fields.BinaryField')()), 17 | ('ca', self.gf('django.db.models.fields.related.ForeignKey')(related_name='certificates', to=orm['config.Certificate'])), 18 | )) 19 | db.send_create_signal(u'config', ['Certificate']) 20 | 21 | 22 | def backwards(self, orm): 23 | # Deleting model 'Certificate' 24 | db.delete_table(u'config_certificate') 25 | 26 | 27 | models = { 28 | u'config.certificate': { 29 | 'Meta': {'object_name': 'Certificate'}, 30 | 'ca': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'certificates'", 'to': u"orm['config.Certificate']"}), 31 | 'certificate': ('django.db.models.fields.BinaryField', [], {}), 32 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 33 | 'key': ('django.db.models.fields.BinaryField', [], {}), 34 | 'revoked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 35 | } 36 | } 37 | 38 | complete_apps = ['config'] -------------------------------------------------------------------------------- /config/migrations/0002_auto__chg_field_certificate_ca.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | 12 | # Changing field 'Certificate.ca' 13 | db.alter_column(u'config_certificate', 'ca_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, to=orm['config.Certificate'])) 14 | 15 | def backwards(self, orm): 16 | 17 | # User chose to not deal with backwards NULL issues for 'Certificate.ca' 18 | raise RuntimeError("Cannot reverse this migration. 'Certificate.ca' and its values cannot be restored.") 19 | 20 | # The following code is provided here to aid in writing a correct migration 21 | # Changing field 'Certificate.ca' 22 | db.alter_column(u'config_certificate', 'ca_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['config.Certificate'])) 23 | 24 | models = { 25 | u'config.certificate': { 26 | 'Meta': {'object_name': 'Certificate'}, 27 | 'ca': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'certificates'", 'null': 'True', 'to': u"orm['config.Certificate']"}), 28 | 'certificate': ('django.db.models.fields.BinaryField', [], {}), 29 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 30 | 'key': ('django.db.models.fields.BinaryField', [], {}), 31 | 'revoked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 32 | } 33 | } 34 | 35 | complete_apps = ['config'] -------------------------------------------------------------------------------- /config/migrations/0003_auto__del_certificate__add_certficate__add_certificateauthority.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Deleting model 'Certificate' 12 | db.delete_table(u'config_certificate') 13 | 14 | # Adding model 'Certficate' 15 | db.create_table(u'config_certficate', ( 16 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 17 | ('revoked', self.gf('django.db.models.fields.BooleanField')(default=False)), 18 | ('key', self.gf('django.db.models.fields.TextField')()), 19 | ('certificate', self.gf('django.db.models.fields.TextField')()), 20 | ('ca', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['config.CertificateAuthority'])), 21 | )) 22 | db.send_create_signal(u'config', ['Certficate']) 23 | 24 | # Adding model 'CertificateAuthority' 25 | db.create_table(u'config_certificateauthority', ( 26 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 27 | ('revoked', self.gf('django.db.models.fields.BooleanField')(default=False)), 28 | ('key', self.gf('django.db.models.fields.TextField')()), 29 | ('certificate', self.gf('django.db.models.fields.TextField')()), 30 | ('next_serial', self.gf('django.db.models.fields.IntegerField')(default=1)), 31 | )) 32 | db.send_create_signal(u'config', ['CertificateAuthority']) 33 | 34 | 35 | def backwards(self, orm): 36 | # Adding model 'Certificate' 37 | db.create_table(u'config_certificate', ( 38 | ('revoked', self.gf('django.db.models.fields.BooleanField')(default=False)), 39 | ('certificate', self.gf('django.db.models.fields.BinaryField')()), 40 | ('ca', self.gf('django.db.models.fields.related.ForeignKey')(related_name='certificates', null=True, to=orm['config.Certificate'], blank=True)), 41 | ('key', self.gf('django.db.models.fields.BinaryField')()), 42 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 43 | )) 44 | db.send_create_signal(u'config', ['Certificate']) 45 | 46 | # Deleting model 'Certficate' 47 | db.delete_table(u'config_certficate') 48 | 49 | # Deleting model 'CertificateAuthority' 50 | db.delete_table(u'config_certificateauthority') 51 | 52 | 53 | models = { 54 | u'config.certficate': { 55 | 'Meta': {'object_name': 'Certficate'}, 56 | 'ca': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['config.CertificateAuthority']"}), 57 | 'certificate': ('django.db.models.fields.TextField', [], {}), 58 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 59 | 'key': ('django.db.models.fields.TextField', [], {}), 60 | 'revoked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 61 | }, 62 | u'config.certificateauthority': { 63 | 'Meta': {'object_name': 'CertificateAuthority'}, 64 | 'certificate': ('django.db.models.fields.TextField', [], {}), 65 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 66 | 'key': ('django.db.models.fields.TextField', [], {}), 67 | 'next_serial': ('django.db.models.fields.IntegerField', [], {'default': '1'}), 68 | 'revoked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 69 | } 70 | } 71 | 72 | complete_apps = ['config'] -------------------------------------------------------------------------------- /config/migrations/0004_auto__del_certficate__add_certificate__del_field_certificateauthority_.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Deleting model 'Certficate' 12 | db.delete_table(u'config_certficate') 13 | 14 | # Adding model 'Certificate' 15 | db.create_table(u'config_certificate', ( 16 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 17 | ('revoked', self.gf('django.db.models.fields.BooleanField')(default=False)), 18 | ('key', self.gf('django.db.models.fields.TextField')()), 19 | ('crt', self.gf('django.db.models.fields.TextField')()), 20 | ('ca', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['config.CertificateAuthority'])), 21 | )) 22 | db.send_create_signal(u'config', ['Certificate']) 23 | 24 | # Deleting field 'CertificateAuthority.certificate' 25 | db.delete_column(u'config_certificateauthority', 'certificate') 26 | 27 | # Adding field 'CertificateAuthority.crt' 28 | db.add_column(u'config_certificateauthority', 'crt', 29 | self.gf('django.db.models.fields.TextField')(default=''), 30 | keep_default=False) 31 | 32 | 33 | def backwards(self, orm): 34 | # Adding model 'Certficate' 35 | db.create_table(u'config_certficate', ( 36 | ('revoked', self.gf('django.db.models.fields.BooleanField')(default=False)), 37 | ('certificate', self.gf('django.db.models.fields.TextField')()), 38 | ('ca', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['config.CertificateAuthority'])), 39 | ('key', self.gf('django.db.models.fields.TextField')()), 40 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 41 | )) 42 | db.send_create_signal(u'config', ['Certficate']) 43 | 44 | # Deleting model 'Certificate' 45 | db.delete_table(u'config_certificate') 46 | 47 | 48 | # User chose to not deal with backwards NULL issues for 'CertificateAuthority.certificate' 49 | raise RuntimeError("Cannot reverse this migration. 'CertificateAuthority.certificate' and its values cannot be restored.") 50 | 51 | # The following code is provided here to aid in writing a correct migration # Adding field 'CertificateAuthority.certificate' 52 | db.add_column(u'config_certificateauthority', 'certificate', 53 | self.gf('django.db.models.fields.TextField')(), 54 | keep_default=False) 55 | 56 | # Deleting field 'CertificateAuthority.crt' 57 | db.delete_column(u'config_certificateauthority', 'crt') 58 | 59 | 60 | models = { 61 | u'config.certificate': { 62 | 'Meta': {'object_name': 'Certificate'}, 63 | 'ca': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['config.CertificateAuthority']"}), 64 | 'crt': ('django.db.models.fields.TextField', [], {}), 65 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 66 | 'key': ('django.db.models.fields.TextField', [], {}), 67 | 'revoked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 68 | }, 69 | u'config.certificateauthority': { 70 | 'Meta': {'object_name': 'CertificateAuthority'}, 71 | 'crt': ('django.db.models.fields.TextField', [], {}), 72 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 73 | 'key': ('django.db.models.fields.TextField', [], {}), 74 | 'next_serial': ('django.db.models.fields.IntegerField', [], {'default': '1'}), 75 | 'revoked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 76 | } 77 | } 78 | 79 | complete_apps = ['config'] -------------------------------------------------------------------------------- /config/migrations/0005_auto__add_field_certificate_date_created__add_field_certificate_device.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'Certificate.date_created' 12 | db.add_column(u'config_certificate', 'date_created', 13 | self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(2014, 5, 27, 0, 0)), 14 | keep_default=False) 15 | 16 | # Adding field 'Certificate.device' 17 | db.add_column(u'config_certificate', 'device', 18 | self.gf('django.db.models.fields.related.ForeignKey')(to=orm['mdm.Device'], null=True, blank=True), 19 | keep_default=False) 20 | 21 | # Adding field 'CertificateAuthority.date_created' 22 | db.add_column(u'config_certificateauthority', 'date_created', 23 | self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(2014, 5, 27, 0, 0)), 24 | keep_default=False) 25 | 26 | 27 | def backwards(self, orm): 28 | # Deleting field 'Certificate.date_created' 29 | db.delete_column(u'config_certificate', 'date_created') 30 | 31 | # Deleting field 'Certificate.device' 32 | db.delete_column(u'config_certificate', 'device_id') 33 | 34 | # Deleting field 'CertificateAuthority.date_created' 35 | db.delete_column(u'config_certificateauthority', 'date_created') 36 | 37 | 38 | models = { 39 | u'config.certificate': { 40 | 'Meta': {'object_name': 'Certificate'}, 41 | 'ca': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['config.CertificateAuthority']"}), 42 | 'crt': ('django.db.models.fields.TextField', [], {}), 43 | 'date_created': ('django.db.models.fields.DateTimeField', [], {}), 44 | 'device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['mdm.Device']", 'null': 'True', 'blank': 'True'}), 45 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 46 | 'key': ('django.db.models.fields.TextField', [], {}), 47 | 'revoked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 48 | }, 49 | u'config.certificateauthority': { 50 | 'Meta': {'object_name': 'CertificateAuthority'}, 51 | 'crt': ('django.db.models.fields.TextField', [], {}), 52 | 'date_created': ('django.db.models.fields.DateTimeField', [], {}), 53 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 54 | 'key': ('django.db.models.fields.TextField', [], {}), 55 | 'next_serial': ('django.db.models.fields.IntegerField', [], {'default': '1'}), 56 | 'revoked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 57 | }, 58 | u'mdm.device': { 59 | 'Meta': {'object_name': 'Device'}, 60 | 'activation_lock': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 61 | 'available_capacity': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 62 | 'battery_level': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 63 | 'bluetooth_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '17', 'blank': 'True'}), 64 | 'build_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}), 65 | 'capacity': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 66 | 'cloud_backup': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 67 | 'ethernet_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '288', 'blank': 'True'}), 68 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 69 | 'last_checkin': ('django.db.models.fields.DateTimeField', [], {}), 70 | 'last_notification': ('django.db.models.fields.DateTimeField', [], {}), 71 | 'locator_service': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 72 | 'model': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 73 | 'model_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 74 | 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), 75 | 'os': ('django.db.models.fields.IntegerField', [], {'default': '0'}), 76 | 'os_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}), 77 | 'phone_number': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 78 | 'product_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 79 | 'push_magic': ('django.db.models.fields.CharField', [], {'max_length': '48'}), 80 | 'push_token': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 81 | 'push_topic': ('django.db.models.fields.CharField', [], {'max_length': '96'}), 82 | 'serial_number': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), 83 | 'supervised': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 84 | 'type': ('django.db.models.fields.IntegerField', [], {'default': '0'}), 85 | 'udid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36', 'db_index': 'True'}), 86 | 'wifi_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '17', 'blank': 'True'}) 87 | } 88 | } 89 | 90 | complete_apps = ['config'] -------------------------------------------------------------------------------- /config/migrations/0006_auto__add_field_certificate_md5.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'Certificate.md5' 12 | db.add_column(u'config_certificate', 'md5', 13 | self.gf('django.db.models.fields.CharField')(default='', max_length=20), 14 | keep_default=False) 15 | 16 | 17 | def backwards(self, orm): 18 | # Deleting field 'Certificate.md5' 19 | db.delete_column(u'config_certificate', 'md5') 20 | 21 | 22 | models = { 23 | u'config.certificate': { 24 | 'Meta': {'object_name': 'Certificate'}, 25 | 'ca': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['config.CertificateAuthority']"}), 26 | 'crt': ('django.db.models.fields.TextField', [], {}), 27 | 'date_created': ('django.db.models.fields.DateTimeField', [], {}), 28 | 'device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['mdm.Device']", 'null': 'True', 'blank': 'True'}), 29 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 30 | 'key': ('django.db.models.fields.TextField', [], {}), 31 | 'md5': ('django.db.models.fields.CharField', [], {'max_length': '20'}), 32 | 'revoked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 33 | }, 34 | u'config.certificateauthority': { 35 | 'Meta': {'object_name': 'CertificateAuthority'}, 36 | 'crt': ('django.db.models.fields.TextField', [], {}), 37 | 'date_created': ('django.db.models.fields.DateTimeField', [], {}), 38 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 39 | 'key': ('django.db.models.fields.TextField', [], {}), 40 | 'next_serial': ('django.db.models.fields.IntegerField', [], {'default': '1'}), 41 | 'revoked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 42 | }, 43 | u'mdm.device': { 44 | 'Meta': {'object_name': 'Device'}, 45 | 'activation_lock': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 46 | 'available_capacity': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 47 | 'battery_level': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 48 | 'bluetooth_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '17', 'blank': 'True'}), 49 | 'build_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}), 50 | 'capacity': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 51 | 'cloud_backup': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 52 | 'ethernet_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '288', 'blank': 'True'}), 53 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 54 | 'last_checkin': ('django.db.models.fields.DateTimeField', [], {}), 55 | 'last_notification': ('django.db.models.fields.DateTimeField', [], {}), 56 | 'locator_service': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 57 | 'model': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 58 | 'model_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 59 | 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), 60 | 'os': ('django.db.models.fields.IntegerField', [], {'default': '0'}), 61 | 'os_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}), 62 | 'phone_number': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 63 | 'product_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 64 | 'push_magic': ('django.db.models.fields.CharField', [], {'max_length': '48'}), 65 | 'push_token': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 66 | 'push_topic': ('django.db.models.fields.CharField', [], {'max_length': '96'}), 67 | 'serial_number': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), 68 | 'supervised': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 69 | 'type': ('django.db.models.fields.IntegerField', [], {'default': '0'}), 70 | 'udid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36', 'db_index': 'True'}), 71 | 'wifi_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '17', 'blank': 'True'}) 72 | } 73 | } 74 | 75 | complete_apps = ['config'] -------------------------------------------------------------------------------- /config/migrations/0007_auto__add_field_certificate_serial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'Certificate.serial' 12 | db.add_column(u'config_certificate', 'serial', 13 | self.gf('django.db.models.fields.IntegerField')(default=-1), 14 | keep_default=False) 15 | 16 | 17 | def backwards(self, orm): 18 | # Deleting field 'Certificate.serial' 19 | db.delete_column(u'config_certificate', 'serial') 20 | 21 | 22 | models = { 23 | u'config.certificate': { 24 | 'Meta': {'object_name': 'Certificate'}, 25 | 'ca': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['config.CertificateAuthority']"}), 26 | 'crt': ('django.db.models.fields.TextField', [], {}), 27 | 'date_created': ('django.db.models.fields.DateTimeField', [], {}), 28 | 'device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['mdm.Device']", 'null': 'True', 'blank': 'True'}), 29 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 30 | 'key': ('django.db.models.fields.TextField', [], {}), 31 | 'md5': ('django.db.models.fields.CharField', [], {'max_length': '20'}), 32 | 'revoked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 33 | 'serial': ('django.db.models.fields.IntegerField', [], {}) 34 | }, 35 | u'config.certificateauthority': { 36 | 'Meta': {'object_name': 'CertificateAuthority'}, 37 | 'crt': ('django.db.models.fields.TextField', [], {}), 38 | 'date_created': ('django.db.models.fields.DateTimeField', [], {}), 39 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 40 | 'key': ('django.db.models.fields.TextField', [], {}), 41 | 'next_serial': ('django.db.models.fields.IntegerField', [], {'default': '1'}), 42 | 'revoked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 43 | }, 44 | u'mdm.device': { 45 | 'Meta': {'object_name': 'Device'}, 46 | 'activation_lock': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 47 | 'available_capacity': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 48 | 'battery_level': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 49 | 'bluetooth_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '17', 'blank': 'True'}), 50 | 'build_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}), 51 | 'capacity': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 52 | 'cloud_backup': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 53 | 'ethernet_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '288', 'blank': 'True'}), 54 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 55 | 'last_checkin': ('django.db.models.fields.DateTimeField', [], {}), 56 | 'last_notification': ('django.db.models.fields.DateTimeField', [], {}), 57 | 'locator_service': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 58 | 'model': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 59 | 'model_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 60 | 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), 61 | 'os': ('django.db.models.fields.IntegerField', [], {'default': '0'}), 62 | 'os_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}), 63 | 'phone_number': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 64 | 'product_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 65 | 'push_magic': ('django.db.models.fields.CharField', [], {'max_length': '48'}), 66 | 'push_token': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 67 | 'push_topic': ('django.db.models.fields.CharField', [], {'max_length': '96'}), 68 | 'serial_number': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), 69 | 'supervised': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 70 | 'type': ('django.db.models.fields.IntegerField', [], {'default': '0'}), 71 | 'udid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36', 'db_index': 'True'}), 72 | 'wifi_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '17', 'blank': 'True'}) 73 | } 74 | } 75 | 76 | complete_apps = ['config'] -------------------------------------------------------------------------------- /config/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/config/migrations/__init__.py -------------------------------------------------------------------------------- /config/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class CertificateAuthority(models.Model): 5 | revoked = models.BooleanField(default=False) 6 | key = models.TextField() 7 | crt = models.TextField() 8 | next_serial = models.IntegerField(default=1) 9 | date_created = models.DateTimeField() 10 | 11 | class Certificate(models.Model): 12 | revoked = models.BooleanField(default=False) 13 | key = models.TextField() 14 | crt = models.TextField() 15 | ca = models.ForeignKey(CertificateAuthority) 16 | date_created = models.DateTimeField() 17 | md5 = models.CharField(max_length=20) 18 | serial = models.IntegerField() 19 | device = models.ForeignKey("mdm.device", blank=True, null=True) 20 | 21 | -------------------------------------------------------------------------------- /config/templates/config/generateCA.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block extra_style %} 5 | 6 | {% endblock %} 7 | 8 | {% block java_script %} 9 | 10 | 38 | {% endblock %} 39 | 40 | {% block content %} 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 |
Organization
Organizational Unit
Country
State/Province
City
Server Name
70 | 71 | 72 | 73 |
74 | {% endblock %} 75 | -------------------------------------------------------------------------------- /config/templates/config/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block extra_style %} 5 | 6 | {% endblock %} 7 | 8 | {% block java_script %} 9 | 10 | 14 | {% endblock %} 15 | 16 | {% block content %} 17 | 18 | 19 | 20 | 27 | 28 |
Certificate Authority 21 | {% if ca %} 22 | Configured 23 | {% else %} 24 | Configure 25 | {% endif %} 26 |
29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /config/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /config/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from django.views.generic import RedirectView 3 | 4 | urlpatterns = patterns('', 5 | url(r'^$', 'config.views.index', name='config_index'), 6 | 7 | url(r'^generateCA/$', 'config.views.generateCA', name='config_generateCA'), 8 | 9 | # url(r'^monitor/$', 'mdm.views.index'), 10 | # url(r'^monitor/detail/(?P[a-zA-Z0-9\-]+)/$', 'mdm.views.detail'), 11 | # url(r'^monitor/detail/(?P[a-zA-Z0-9\-]+)/api$', 'mdm.views.monitor_detail_api'), 12 | # url(r'^monitor/detail/(?P[a-zA-Z0-9\-]+)/profile/$', 'mdm.views.install_profile'), 13 | # url(r'^monitor/detail/(?P[a-zA-Z0-9\-]+)/push/$', 'mdm.views.send_push'), 14 | ) 15 | -------------------------------------------------------------------------------- /config/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.shortcuts import render 3 | from django.views.decorators.csrf import csrf_exempt 4 | from django.contrib.auth.decorators import login_required 5 | from django.http import HttpResponse, HttpResponseBadRequest, Http404 6 | from datetime import datetime, timedelta 7 | from models import Certificate, CertificateAuthority 8 | from socket import getfqdn 9 | import base64 10 | import mdm.geo 11 | import json 12 | import OpenSSL 13 | 14 | 15 | @login_required 16 | def index(request): 17 | ca = CertificateAuthority.objects.filter(revoked=False).first() 18 | 19 | context = { 20 | 'ca': ca, 21 | } 22 | 23 | return render(request, 'config/index.html', context) 24 | 25 | 26 | @login_required 27 | def generateCA(request): 28 | if request.method == 'GET': 29 | data = request.GET 30 | else: 31 | data = request.POST 32 | 33 | if 'command' in data: 34 | result = { } 35 | if data['command'] == 'generate': 36 | key = OpenSSL.crypto.PKey() 37 | key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) 38 | ca = OpenSSL.crypto.X509() 39 | ca.set_version(2) 40 | ca.set_serial_number(1) 41 | ca.get_subject().C = data['country'] 42 | ca.get_subject().ST = data['state'] 43 | ca.get_subject().L = data['city'] 44 | ca.get_subject().O = data['org'] 45 | ca.get_subject().OU = data['orgunit'] 46 | ca.get_subject().CN = data['name'] 47 | ca.gmtime_adj_notBefore(0) 48 | ca.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60) 49 | ca.set_issuer(ca.get_subject()) 50 | ca.set_pubkey(key) 51 | ca.add_extensions([ 52 | OpenSSL.crypto.X509Extension("basicConstraints", True, "CA:TRUE, pathlen:0"), 53 | OpenSSL.crypto.X509Extension("keyUsage", True, "digitalSignature,keyCertSign"), 54 | # OpenSSL.crypto.X509Extension("subjectKeyIdentifier", False, "hash", subject=ca), 55 | ]) 56 | # ca.add_extensions([ 57 | # OpenSSL.crypto.X509Extension("authorityKeyIdentifier", False, "keyid:always", issuer=ca), 58 | # ]) 59 | ca.sign(key, "sha256") 60 | 61 | cert = CertificateAuthority() 62 | cert.key = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key) 63 | cert.crt = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, ca) 64 | cert.date_created = datetime.now() 65 | cert.save() 66 | 67 | result['status'] = 'OK' 68 | else: 69 | result['status'] = 'ERROR' 70 | result['error'] = 'Invalid command' 71 | 72 | return HttpResponse(json.dumps(result)) 73 | 74 | context = { 75 | 'org': settings.ORGANIZATION, 76 | 'geo': mdm.geo.geocode(''), 77 | } 78 | 79 | return render(request, 'config/generateCA.html', context) 80 | 81 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "managedmacadmin.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /managedmacadmin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/managedmacadmin/__init__.py -------------------------------------------------------------------------------- /managedmacadmin/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for managedmacadmin project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 15 | 16 | # 17 | # Change these settings to reflect your configuration. 18 | # ORGANIZATION - Name of your organization to use for display purposes 19 | # PUSH_TOPIC - Apple assigned, com.apple.mgmt.External. 20 | # MANAGED_PROFILE_UUID - Generate a custom UUID and put it in here 21 | # IDENTIFIER - Prefix to use with profiles (e.g. com.yourorganization) 22 | # DEFAULT_DEVICE_GROUP - UUID of the device group to put all new devices in (create a device group first) 23 | # ENROLL_PIN - The Pin code to use to enroll new devices 24 | # USE_CERTIFICATE - True/False (If True and a certificate was provided by the client, use it. Recommended: False) 25 | # REQUIRE_CERTIFICATE - True/False (If True, require the client to provide a valid certificate. Recommended: False) 26 | 27 | ORGANIZATION = 'Your Organization' 28 | PUSH_TOPIC = 'com.apple.mgmt.External.GUID' 29 | MANAGED_PROFILE_UUID = 'GUID' 30 | IDENTIFIER = 'com.yourorganization' 31 | DEFAULT_DEVICE_GROUP='' 32 | ENROLL_PIN='0000' 33 | USE_CERTIFICATE = False 34 | REQUIRE_CERTIFICATE = False 35 | 36 | LOGIN_URL='/login/' 37 | LOGIN_REDIRECT_URL='/mdm/' 38 | MANAGED_PROFILE_IDENTIFIER = IDENTIFIER + '.' + MANAGED_PROFILE_UUID 39 | 40 | 41 | # Quick-start development settings - unsuitable for production 42 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 43 | 44 | # SECURITY WARNING: keep the secret key used in production secret! 45 | SECRET_KEY = '7wdk^m+rf^zerj3#=d5^e77zf(_ys)=(+p(&$3k(_^e5vm!mzf' 46 | 47 | # SECURITY WARNING: don't run with debug turned on in production! 48 | DEBUG = True 49 | 50 | TEMPLATE_DEBUG = True 51 | 52 | ALLOWED_HOSTS = ['*'] 53 | 54 | TEMPLATE_DIRS = ( 55 | os.path.join(PROJECT_DIR, 'templates'), 56 | ) 57 | 58 | # Application definition 59 | 60 | INSTALLED_APPS = ( 61 | 'django.contrib.admin', 62 | 'django.contrib.auth', 63 | 'django.contrib.contenttypes', 64 | 'django.contrib.sessions', 65 | 'django.contrib.messages', 66 | 'django.contrib.staticfiles', 67 | 'south', 68 | 'reports', 69 | 'mdm', 70 | 'config', 71 | ) 72 | 73 | MIDDLEWARE_CLASSES = ( 74 | 'django.contrib.sessions.middleware.SessionMiddleware', 75 | 'django.middleware.common.CommonMiddleware', 76 | 'django.middleware.csrf.CsrfViewMiddleware', 77 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 78 | 'django.contrib.messages.middleware.MessageMiddleware', 79 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 80 | ) 81 | 82 | ROOT_URLCONF = 'managedmacadmin.urls' 83 | 84 | WSGI_APPLICATION = 'managedmacadmin.wsgi.application' 85 | 86 | 87 | # Database 88 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 89 | 90 | DATABASES = { 91 | 'default': { 92 | 'ENGINE': 'django.db.backends.sqlite3', 93 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 94 | } 95 | # 'default': { 96 | # 'ENGINE': 'django.db.backends.mysql', 97 | # 'NAME': 'django', 98 | # 'USER': 'django', 99 | # 'PASSWORD': 'password', 100 | # 'HOST': 'localhost', 101 | # 'PORT': '3306', 102 | # } 103 | } 104 | 105 | # Internationalization 106 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 107 | 108 | LANGUAGE_CODE = 'en-us' 109 | 110 | TIME_ZONE = 'America/Los_Angeles' 111 | 112 | USE_I18N = True 113 | 114 | USE_L10N = True 115 | 116 | USE_TZ = False 117 | 118 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 121 | 122 | STATIC_ROOT = os.path.join(BASE_DIR, 'static') 123 | STATIC_URL = '/static/' 124 | STATICFILES_DIRS = ( 125 | os.path.join(PROJECT_DIR, 'site_static'), 126 | ) 127 | 128 | LOGGING = { 129 | 'version': 1, 130 | 'disable_existing_loggers': False, 131 | 'handlers': { 132 | 'file': { 133 | 'level': 'DEBUG', 134 | 'class': 'logging.FileHandler', 135 | 'filename': '/tmp/debug.log', 136 | }, 137 | }, 138 | 'loggers': { 139 | 'django.request': { 140 | 'handlers': ['file'], 141 | 'level': 'DEBUG', 142 | 'propagate': True, 143 | }, 144 | 'django': { 145 | 'handlers': ['file'], 146 | 'level': 'DEBUG', 147 | 'propagate': True, 148 | }, 149 | }, 150 | } 151 | 152 | -------------------------------------------------------------------------------- /managedmacadmin/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from django.views.generic import RedirectView 3 | 4 | from django.contrib import admin 5 | admin.autodiscover() 6 | 7 | urlpatterns = patterns('', 8 | url(r'^$', RedirectView.as_view(url='mdm/')), 9 | 10 | url(r'^admin/', include(admin.site.urls)), 11 | url(r'^login/$', 'django.contrib.auth.views.login', name='login'), 12 | url(r'^logout/$', 'django.contrib.auth.views.logout_then_login', name='logout'), 13 | 14 | url(r'^report/', include('reports.urls')), 15 | url(r'^mdm/', include('mdm.urls')), 16 | url(r'^config/', include('config.urls')), 17 | ) 18 | -------------------------------------------------------------------------------- /managedmacadmin/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for abcxx project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | import sys 12 | import site 13 | 14 | site.addsitedir('/var/mdm/env/lib/python2.7/site-packages') 15 | sys.path.append('/var/mdm/env/managedmacadmin') 16 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "managedmacadmin.settings") 17 | 18 | activate_env='/var/mdm/env/bin/activate_this.py' 19 | execfile(activate_env, dict(__file__=activate_env)) 20 | 21 | from django.core.wsgi import get_wsgi_application 22 | application = get_wsgi_application() 23 | -------------------------------------------------------------------------------- /mdm/NOTES: -------------------------------------------------------------------------------- 1 | On the Profiles tab for devices we should also list un-installed profiles. 2 | New column called "Installed" that is true for all profiles found in the 3 | information returned by the device and false for all profiles found in the 4 | database but not found on the device. 5 | -------------------------------------------------------------------------------- /mdm/ProfileList.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CommandUUID 6 | 8911aa66-7029-4ecc-9f6a-9c8536496ba9 7 | ProfileList 8 | 9 | 10 | HasRemovalPasscode 11 | 12 | IsEncrypted 13 | 14 | PayloadContent 15 | 16 | 17 | PayloadDescription 18 | Provides device authentication (certificate or identity). 19 | PayloadDisplayName 20 | ident-mdm.p12 21 | PayloadIdentifier 22 | com.highdesertchurch.mdmprofile.credential 23 | PayloadOrganization 24 | High Desert Church 25 | PayloadType 26 | com.apple.security.pkcs12 27 | PayloadUUID 28 | D3BFAC16-6273-40FC-A993-F899A4F99DE2 29 | PayloadVersion 30 | 1 31 | 32 | 33 | PayloadDescription 34 | Configures Mobile Device Management 35 | PayloadDisplayName 36 | Mobile Device Management 37 | PayloadIdentifier 38 | com.highdesertchurch.mdmprofile.mdm 39 | PayloadOrganization 40 | High Desert Church 41 | PayloadType 42 | com.apple.mdm 43 | PayloadUUID 44 | 52B4D23F-CD6D-4965-BAA7-3443F4B14715 45 | PayloadVersion 46 | 1 47 | 48 | 49 | PayloadDescription 50 | Profile description. 51 | PayloadDisplayName 52 | High Desert Church MDM 53 | PayloadIdentifier 54 | com.highdesertchurch.mdmprofile 55 | PayloadOrganization 56 | High Desert Church 57 | PayloadRemovalDisallowed 58 | 59 | PayloadUUID 60 | BC32FDC6-0EF0-4AE9-BBF3-396E7AA8C3E0 61 | PayloadVersion 62 | 1 63 | SignerCertificates 64 | 65 | 66 | 67 | RequestType 68 | ProfileList 69 | Status 70 | Acknowledged 71 | UDID 72 | 05BE944E-1C4C-54C3-88C0-86ED8D74E69B 73 | 74 | 75 | -------------------------------------------------------------------------------- /mdm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/mdm/__init__.py -------------------------------------------------------------------------------- /mdm/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from mdm.models import Device, DeviceCommand, AppInventoryItem, DeviceCheckin, DeviceProfile, DeviceManagedPreference, ProfileInventoryItem, DeviceGroup, DeviceGroupManagedPreference 3 | 4 | admin.site.register(Device) 5 | admin.site.register(DeviceCommand) 6 | admin.site.register(AppInventoryItem) 7 | admin.site.register(ProfileInventoryItem) 8 | admin.site.register(DeviceCheckin) 9 | admin.site.register(DeviceProfile) 10 | admin.site.register(DeviceManagedPreference) 11 | admin.site.register(DeviceGroup) 12 | admin.site.register(DeviceGroupManagedPreference) 13 | -------------------------------------------------------------------------------- /mdm/commands.py: -------------------------------------------------------------------------------- 1 | from models import DeviceCommand 2 | from datetime import datetime 3 | 4 | 5 | command_list = { } 6 | command_snoops = { } 7 | 8 | 9 | def registerCommandHandler(code, data_handler, result_handler): 10 | command_list[code] = { 'data': data_handler, 'result': result_handler } 11 | 12 | 13 | def registerCommandSnooper(code, result_handler): 14 | list = command_snoops[code] 15 | if list == None: 16 | list = [ ] 17 | 18 | list += result_handler 19 | command_snoops[code] = list 20 | 21 | 22 | def dataForCommand(command): 23 | handler = command_list[command.type] 24 | if handler == None: 25 | return None 26 | 27 | data = { } 28 | data['CommandUUID'] = command.uuid 29 | data['Command'] = handler['data'](command) 30 | 31 | return data 32 | 33 | 34 | def handleCommandResponse(command, response): 35 | if command.type not in command_list: 36 | return False 37 | handler = command_list[command.type] 38 | if handler == None or 'result' not in handler or handler['result'] == None: 39 | return False 40 | 41 | if handler['result'](command, response) != True: 42 | return False 43 | 44 | # Let any snoopers run 45 | if command.type not in command_snoops: 46 | return True 47 | list = command_snoops[command.type] 48 | with snoop in list: 49 | snoop(command, response) 50 | 51 | return True 52 | -------------------------------------------------------------------------------- /mdm/geo.py: -------------------------------------------------------------------------------- 1 | import urllib2 2 | import json 3 | 4 | 5 | # 6 | # Geocode the given IP address. Return a dictionary with the following 7 | # keys: 8 | # latitude [required] 9 | # longitude [required] 10 | # country_code [required] 11 | # region_code [required] 12 | # city [required] 13 | # country_name [optional] 14 | # region_name [optional] 15 | # 16 | # Any required fields that are unavailable should exist but be blank 17 | # strings. 18 | # 19 | #{"ip":"24.182.14.97","country_code":"US","country_name":"United States","region_code":"CA","region_name":"California","city":"Hesperia", 20 | #"zipcode":"","latitude":34.4264,"longitude":-117.3009,"metro_code":"803","area_code":"760"} 21 | def geocode(ip): 22 | socket = None 23 | data = { 24 | 'latitude': 0, 25 | 'longitude': 0, 26 | 'country_code': '', 27 | 'region_code': '', 28 | 'city': '' }; 29 | 30 | try: 31 | socket = urllib2.urlopen("http://freegeoip.net/json/" + ip, None, 2) 32 | result = json.loads(socket.read()) 33 | 34 | data['latitude'] = result['latitude'] 35 | data['longitude'] = result['longitude'] 36 | data['country_code'] = result['country_code'] 37 | data['region_code'] = result['region_code'] 38 | data['city'] = result['city'] 39 | data['country_name'] = result['country_name'] 40 | data['region_name'] = result['region_name'] 41 | except: 42 | if socket == None: 43 | try: 44 | socket.close() 45 | except: 46 | pass 47 | pass 48 | 49 | return data 50 | 51 | -------------------------------------------------------------------------------- /mdm/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/mdm/management/__init__.py -------------------------------------------------------------------------------- /mdm/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/mdm/management/commands/__init__.py -------------------------------------------------------------------------------- /mdm/management/commands/cron.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand, CommandError 2 | from django.db.models import Q 3 | from datetime import datetime, timedelta 4 | from mdm.models import DeviceCommand, Device, DeviceCheckin 5 | import mdm.push 6 | import uuid 7 | 8 | 9 | class Command(BaseCommand): 10 | help = 'Run automated tasks' 11 | 12 | def handle(self, *args, **options): 13 | self.CheckTypeNeeded(6, "com.github.managedmacadmin.DeviceInformation", UpdateDeviceInformation) 14 | self.CheckTypeNeeded(24, "com.github.managedmacadmin.ProfileList", UpdateProfileList) 15 | self.CheckTypeNeeded(24, "com.github.managedmacadmin.InstalledApplicationList", UpdateInstalledApplicationList) 16 | 17 | since = datetime.now() - timedelta(minutes=30) 18 | devices = Device.objects.filter(last_checkin__lt=since, last_notification__lt=since) 19 | RequestDeviceCheckin(devices) 20 | 21 | 22 | def CheckTypeNeeded(self, hours, type, handler): 23 | age = datetime.now() - timedelta(hours=hours) 24 | devices = Device.objects.all() 25 | recent = DeviceCommand.objects.filter(type=type, status=DeviceCommand.SUCCESS, date_completed__gte=age).values('device').distinct() 26 | failed = DeviceCommand.objects.filter(type=type, status=DeviceCommand.FAILED, date_completed__gte=age).values('device').distinct() 27 | pending = DeviceCommand.objects.filter(type=type, status=DeviceCommand.PENDING).values('device').distinct() 28 | devices = Device.objects.exclude(pk__in=recent).exclude(pk__in=pending).exclude(pk__in=failed) 29 | if devices.count() > 0: 30 | handler(type, devices) 31 | 32 | 33 | def UpdateDeviceInformation(type, devices): 34 | for device in devices: 35 | DeviceCommand.NewDeviceInformation(device) 36 | mdm.push.push_notification(device) 37 | 38 | 39 | def UpdateProfileList(type, devices): 40 | for device in devices: 41 | DeviceCommand.NewProfileList(device) 42 | mdm.push.push_notification(device) 43 | 44 | 45 | def UpdateInstalledApplicationList(type, devices): 46 | for device in devices: 47 | DeviceCommand.NewInstalledApplicationList(device) 48 | mdm.push.push_notification(device) 49 | 50 | 51 | def RequestDeviceCheckin(devices): 52 | for device in devices: 53 | mdm.push.push_notification(device) 54 | -------------------------------------------------------------------------------- /mdm/mcx.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from models import DeviceProfile, DeviceManagedPreference, DeviceGroupManagedPreference, Device, DeviceGroup, DeviceCommand 3 | from datetime import datetime 4 | import uuid 5 | import plistlib 6 | import push 7 | 8 | 9 | def build_system_configuration_profile(device, install = False): 10 | # Configuration Profiles are only valid on OS X. 11 | if device.os != Device.OSX: 12 | return 13 | 14 | plist = { } 15 | plist['PayloadDisplayName'] = 'System Device Configuration' 16 | plist['PayloadDescription'] = 'Managed Preferences for Device' 17 | plist['PayloadIdentifier'] = settings.MANAGED_PROFILE_IDENTIFIER + '.ManagedPreferences' 18 | plist['PayloadOrganization'] = settings.ORGANIZATION 19 | plist['PayloadRemovalDisallowed'] = False 20 | plist['PayloadVersion'] = 1 21 | plist['PayloadType'] = 'Configuration' 22 | plist['PayloadScope'] = 'System' 23 | plist['PayloadUUID'] = str(uuid.uuid4()) 24 | 25 | plist['PayloadContent'] = [ ] 26 | apps = build_custom_preference_profile(device) 27 | for app in apps: 28 | payload = { } 29 | payload['PayloadEnabled'] = True 30 | payload['PayloadType'] = 'com.apple.ManagedClient.preferences' 31 | payload['PayloadUUID'] = str(uuid.uuid4()) 32 | payload['PayloadIdentifier'] = plist['PayloadIdentifier'] + '.alacarte.customsettings.' + payload['PayloadUUID'] 33 | payload['PayloadVerison'] = 1 34 | payload['PayloadContent'] = { app: apps[app] } 35 | plist['PayloadContent'].append(payload) 36 | 37 | if DeviceProfile.objects.filter(device=device, identifier=plist['PayloadIdentifier']).count() == 0: 38 | profile = DeviceProfile(device=device) 39 | else: 40 | profile = DeviceProfile.objects.get(device=device, identifier=plist['PayloadIdentifier']) 41 | profile.name = plist['PayloadDisplayName'] 42 | profile.identifier = plist['PayloadIdentifier'] 43 | profile.uuid = plist['PayloadUUID'] 44 | 45 | profile.payload = plistlib.writePlistToString(plist) 46 | 47 | profile.save() 48 | 49 | if install == True: 50 | DeviceCommand.InstallProfile(device, profile) 51 | push.push_notification(device) 52 | 53 | 54 | def build_custom_preference_profile(device): 55 | content = { } 56 | 57 | groups = device.all_groups() 58 | groups.sort(key=lambda group: group.priority) 59 | for group in groups: 60 | prefs = DeviceGroupManagedPreference.objects.filter(group=group) 61 | for pref in prefs: 62 | build_custom_preference_profile_pref(content, pref) 63 | 64 | prefs = DeviceManagedPreference.objects.filter(device=device) 65 | for pref in prefs: 66 | build_custom_preference_profile_pref(content, pref) 67 | 68 | return content 69 | 70 | 71 | def build_custom_preference_profile_pref(content, pref): 72 | plist = plistlib.readPlistFromString(pref.plist) 73 | 74 | for freq in ['Once', 'Often', 'Always']: 75 | if len(plist[freq]) == 0: 76 | continue 77 | 78 | data = dict() 79 | data['mcx_preference_settings'] = plist[freq] 80 | if freq == 'Once': 81 | data['mcx_data_timestamp'] = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') 82 | key = 'Set-Once' 83 | elif freq == 'Often': 84 | key = 'Set-Once' 85 | else: 86 | key = 'Forced' 87 | 88 | if pref.identifier not in content: 89 | content[pref.identifier] = { } 90 | if key not in content[pref.identifier]: 91 | content[pref.identifier][key] = [ ] 92 | 93 | content[pref.identifier][key].append(data) 94 | 95 | -------------------------------------------------------------------------------- /mdm/migrations/0002_auto__del_unique_device_serial_number.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Removing unique constraint on 'Device', fields ['serial_number'] 12 | db.delete_unique(u'mdm_device', ['serial_number']) 13 | 14 | 15 | def backwards(self, orm): 16 | # Adding unique constraint on 'Device', fields ['serial_number'] 17 | db.create_unique(u'mdm_device', ['serial_number']) 18 | 19 | 20 | models = { 21 | u'mdm.command': { 22 | 'Meta': {'object_name': 'Command'}, 23 | 'command': ('django.db.models.fields.CharField', [], {'max_length': '8000'}), 24 | 'date_requested': ('django.db.models.fields.DateTimeField', [], {}), 25 | 'device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['mdm.Device']"}), 26 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 27 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 28 | 'status': ('django.db.models.fields.IntegerField', [], {}), 29 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), 30 | 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '36', 'db_index': 'True'}) 31 | }, 32 | u'mdm.device': { 33 | 'Meta': {'object_name': 'Device'}, 34 | 'activation_lock': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 35 | 'available_capacity': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 36 | 'battery_level': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 37 | 'bluetooth_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '17', 'blank': 'True'}), 38 | 'build_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}), 39 | 'capacity': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 40 | 'cloud_backup': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 41 | 'ethernet_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '288', 'blank': 'True'}), 42 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 43 | 'last_checkin': ('django.db.models.fields.DateTimeField', [], {}), 44 | 'locator_service': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 45 | 'model': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 46 | 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), 47 | 'os_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}), 48 | 'phone_number': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 49 | 'product_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 50 | 'push_magic': ('django.db.models.fields.CharField', [], {'max_length': '48'}), 51 | 'push_token': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 52 | 'push_topic': ('django.db.models.fields.CharField', [], {'max_length': '96'}), 53 | 'serial_number': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), 54 | 'supervised': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 55 | 'udid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36', 'db_index': 'True'}), 56 | 'wifi_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '17', 'blank': 'True'}) 57 | }, 58 | u'mdm.managedpreference': { 59 | 'Meta': {'unique_together': "(('application', 'frequency'),)", 'object_name': 'ManagedPreference'}, 60 | 'application': ('django.db.models.fields.CharField', [], {'max_length': '200'}), 61 | 'device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['mdm.Device']", 'null': 'True', 'blank': 'True'}), 62 | 'frequency': ('django.db.models.fields.IntegerField', [], {}), 63 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 64 | 'plist': ('django.db.models.fields.TextField', [], {}) 65 | } 66 | } 67 | 68 | complete_apps = ['mdm'] -------------------------------------------------------------------------------- /mdm/migrations/0003_auto__add_field_device_model_name.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'Device.model_name' 12 | db.add_column(u'mdm_device', 'model_name', 13 | self.gf('django.db.models.fields.CharField')(default='', max_length=32, blank=True), 14 | keep_default=False) 15 | 16 | 17 | def backwards(self, orm): 18 | # Deleting field 'Device.model_name' 19 | db.delete_column(u'mdm_device', 'model_name') 20 | 21 | 22 | models = { 23 | u'mdm.command': { 24 | 'Meta': {'object_name': 'Command'}, 25 | 'command': ('django.db.models.fields.CharField', [], {'max_length': '8000'}), 26 | 'date_requested': ('django.db.models.fields.DateTimeField', [], {}), 27 | 'device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['mdm.Device']"}), 28 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 29 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 30 | 'status': ('django.db.models.fields.IntegerField', [], {}), 31 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), 32 | 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '36', 'db_index': 'True'}) 33 | }, 34 | u'mdm.device': { 35 | 'Meta': {'object_name': 'Device'}, 36 | 'activation_lock': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 37 | 'available_capacity': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 38 | 'battery_level': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 39 | 'bluetooth_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '17', 'blank': 'True'}), 40 | 'build_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}), 41 | 'capacity': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 42 | 'cloud_backup': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 43 | 'ethernet_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '288', 'blank': 'True'}), 44 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 45 | 'last_checkin': ('django.db.models.fields.DateTimeField', [], {}), 46 | 'locator_service': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 47 | 'model': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 48 | 'model_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 49 | 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), 50 | 'os_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}), 51 | 'phone_number': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 52 | 'product_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 53 | 'push_magic': ('django.db.models.fields.CharField', [], {'max_length': '48'}), 54 | 'push_token': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 55 | 'push_topic': ('django.db.models.fields.CharField', [], {'max_length': '96'}), 56 | 'serial_number': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), 57 | 'supervised': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 58 | 'udid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36', 'db_index': 'True'}), 59 | 'wifi_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '17', 'blank': 'True'}) 60 | }, 61 | u'mdm.managedpreference': { 62 | 'Meta': {'unique_together': "(('application', 'frequency'),)", 'object_name': 'ManagedPreference'}, 63 | 'application': ('django.db.models.fields.CharField', [], {'max_length': '200'}), 64 | 'device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['mdm.Device']", 'null': 'True', 'blank': 'True'}), 65 | 'frequency': ('django.db.models.fields.IntegerField', [], {}), 66 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 67 | 'plist': ('django.db.models.fields.TextField', [], {}) 68 | } 69 | } 70 | 71 | complete_apps = ['mdm'] -------------------------------------------------------------------------------- /mdm/migrations/0004_auto__del_field_command_command__add_field_command_data__chg_field_com.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Deleting field 'Command.command' 12 | db.delete_column(u'mdm_command', 'command') 13 | 14 | # Adding field 'Command.data' 15 | db.add_column(u'mdm_command', 'data', 16 | self.gf('django.db.models.fields.CharField')(default='', max_length=256, blank=True), 17 | keep_default=False) 18 | 19 | 20 | # Changing field 'Command.type' 21 | db.alter_column(u'mdm_command', 'type', self.gf('django.db.models.fields.CharField')(max_length=128)) 22 | 23 | def backwards(self, orm): 24 | 25 | # User chose to not deal with backwards NULL issues for 'Command.command' 26 | raise RuntimeError("Cannot reverse this migration. 'Command.command' and its values cannot be restored.") 27 | 28 | # The following code is provided here to aid in writing a correct migration # Adding field 'Command.command' 29 | db.add_column(u'mdm_command', 'command', 30 | self.gf('django.db.models.fields.CharField')(max_length=8000), 31 | keep_default=False) 32 | 33 | # Deleting field 'Command.data' 34 | db.delete_column(u'mdm_command', 'data') 35 | 36 | 37 | # Changing field 'Command.type' 38 | db.alter_column(u'mdm_command', 'type', self.gf('django.db.models.fields.CharField')(max_length=64)) 39 | 40 | models = { 41 | u'mdm.command': { 42 | 'Meta': {'object_name': 'Command'}, 43 | 'data': ('django.db.models.fields.CharField', [], {'max_length': '256', 'blank': 'True'}), 44 | 'date_requested': ('django.db.models.fields.DateTimeField', [], {}), 45 | 'device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['mdm.Device']"}), 46 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 47 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 48 | 'status': ('django.db.models.fields.IntegerField', [], {}), 49 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 50 | 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '36', 'db_index': 'True'}) 51 | }, 52 | u'mdm.device': { 53 | 'Meta': {'object_name': 'Device'}, 54 | 'activation_lock': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 55 | 'available_capacity': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 56 | 'battery_level': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 57 | 'bluetooth_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '17', 'blank': 'True'}), 58 | 'build_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}), 59 | 'capacity': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 60 | 'cloud_backup': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 61 | 'ethernet_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '288', 'blank': 'True'}), 62 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 63 | 'last_checkin': ('django.db.models.fields.DateTimeField', [], {}), 64 | 'locator_service': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 65 | 'model': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 66 | 'model_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 67 | 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), 68 | 'os_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}), 69 | 'phone_number': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 70 | 'product_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 71 | 'push_magic': ('django.db.models.fields.CharField', [], {'max_length': '48'}), 72 | 'push_token': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 73 | 'push_topic': ('django.db.models.fields.CharField', [], {'max_length': '96'}), 74 | 'serial_number': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), 75 | 'supervised': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 76 | 'udid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36', 'db_index': 'True'}), 77 | 'wifi_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '17', 'blank': 'True'}) 78 | }, 79 | u'mdm.managedpreference': { 80 | 'Meta': {'unique_together': "(('application', 'frequency'),)", 'object_name': 'ManagedPreference'}, 81 | 'application': ('django.db.models.fields.CharField', [], {'max_length': '200'}), 82 | 'device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['mdm.Device']", 'null': 'True', 'blank': 'True'}), 83 | 'frequency': ('django.db.models.fields.IntegerField', [], {}), 84 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 85 | 'plist': ('django.db.models.fields.TextField', [], {}) 86 | } 87 | } 88 | 89 | complete_apps = ['mdm'] -------------------------------------------------------------------------------- /mdm/migrations/0005_auto__del_managedpreference__del_unique_managedpreference_application_.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Removing unique constraint on 'ManagedPreference', fields ['application', 'frequency'] 12 | db.delete_unique(u'mdm_managedpreference', ['application', 'frequency']) 13 | 14 | # Deleting model 'ManagedPreference' 15 | db.delete_table(u'mdm_managedpreference') 16 | 17 | # Adding model 'DeviceManagedPreference' 18 | db.create_table(u'mdm_devicemanagedpreference', ( 19 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 20 | ('device', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['mdm.Device'])), 21 | ('application', self.gf('django.db.models.fields.CharField')(max_length=200)), 22 | ('frequency', self.gf('django.db.models.fields.IntegerField')()), 23 | ('plist', self.gf('django.db.models.fields.TextField')()), 24 | )) 25 | db.send_create_signal(u'mdm', ['DeviceManagedPreference']) 26 | 27 | # Adding unique constraint on 'DeviceManagedPreference', fields ['device', 'application', 'frequency'] 28 | db.create_unique(u'mdm_devicemanagedpreference', ['device_id', 'application', 'frequency']) 29 | 30 | 31 | def backwards(self, orm): 32 | # Removing unique constraint on 'DeviceManagedPreference', fields ['device', 'application', 'frequency'] 33 | db.delete_unique(u'mdm_devicemanagedpreference', ['device_id', 'application', 'frequency']) 34 | 35 | # Adding model 'ManagedPreference' 36 | db.create_table(u'mdm_managedpreference', ( 37 | ('plist', self.gf('django.db.models.fields.TextField')()), 38 | ('application', self.gf('django.db.models.fields.CharField')(max_length=200)), 39 | ('frequency', self.gf('django.db.models.fields.IntegerField')()), 40 | ('device', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['mdm.Device'], null=True, blank=True)), 41 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 42 | )) 43 | db.send_create_signal(u'mdm', ['ManagedPreference']) 44 | 45 | # Adding unique constraint on 'ManagedPreference', fields ['application', 'frequency'] 46 | db.create_unique(u'mdm_managedpreference', ['application', 'frequency']) 47 | 48 | # Deleting model 'DeviceManagedPreference' 49 | db.delete_table(u'mdm_devicemanagedpreference') 50 | 51 | 52 | models = { 53 | u'mdm.command': { 54 | 'Meta': {'object_name': 'Command'}, 55 | 'data': ('django.db.models.fields.CharField', [], {'max_length': '256', 'blank': 'True'}), 56 | 'date_requested': ('django.db.models.fields.DateTimeField', [], {}), 57 | 'device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['mdm.Device']"}), 58 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 59 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 60 | 'status': ('django.db.models.fields.IntegerField', [], {}), 61 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 62 | 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '36', 'db_index': 'True'}) 63 | }, 64 | u'mdm.device': { 65 | 'Meta': {'object_name': 'Device'}, 66 | 'activation_lock': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 67 | 'available_capacity': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 68 | 'battery_level': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 69 | 'bluetooth_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '17', 'blank': 'True'}), 70 | 'build_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}), 71 | 'capacity': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 72 | 'cloud_backup': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 73 | 'ethernet_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '288', 'blank': 'True'}), 74 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 75 | 'last_checkin': ('django.db.models.fields.DateTimeField', [], {}), 76 | 'locator_service': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 77 | 'model': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 78 | 'model_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 79 | 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), 80 | 'os_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}), 81 | 'phone_number': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 82 | 'product_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 83 | 'push_magic': ('django.db.models.fields.CharField', [], {'max_length': '48'}), 84 | 'push_token': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 85 | 'push_topic': ('django.db.models.fields.CharField', [], {'max_length': '96'}), 86 | 'serial_number': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), 87 | 'supervised': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 88 | 'udid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36', 'db_index': 'True'}), 89 | 'wifi_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '17', 'blank': 'True'}) 90 | }, 91 | u'mdm.devicemanagedpreference': { 92 | 'Meta': {'unique_together': "(('device', 'application', 'frequency'),)", 'object_name': 'DeviceManagedPreference'}, 93 | 'application': ('django.db.models.fields.CharField', [], {'max_length': '200'}), 94 | 'device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['mdm.Device']"}), 95 | 'frequency': ('django.db.models.fields.IntegerField', [], {}), 96 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 97 | 'plist': ('django.db.models.fields.TextField', [], {}) 98 | } 99 | } 100 | 101 | complete_apps = ['mdm'] -------------------------------------------------------------------------------- /mdm/migrations/0006_auto__add_inventoryitem.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'InventoryItem' 12 | db.create_table(u'mdm_inventoryitem', ( 13 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('device', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['mdm.Device'])), 15 | ('name', self.gf('django.db.models.fields.CharField')(max_length=256)), 16 | ('identifier', self.gf('django.db.models.fields.CharField')(max_length=256)), 17 | ('version', self.gf('django.db.models.fields.CharField')(max_length=32)), 18 | ('short_version', self.gf('django.db.models.fields.CharField')(max_length=32)), 19 | ('bundle_size', self.gf('django.db.models.fields.IntegerField')()), 20 | ('dynamic_size', self.gf('django.db.models.fields.IntegerField')()), 21 | )) 22 | db.send_create_signal(u'mdm', ['InventoryItem']) 23 | 24 | 25 | def backwards(self, orm): 26 | # Deleting model 'InventoryItem' 27 | db.delete_table(u'mdm_inventoryitem') 28 | 29 | 30 | models = { 31 | u'mdm.command': { 32 | 'Meta': {'object_name': 'Command'}, 33 | 'data': ('django.db.models.fields.CharField', [], {'max_length': '256', 'blank': 'True'}), 34 | 'date_requested': ('django.db.models.fields.DateTimeField', [], {}), 35 | 'device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['mdm.Device']"}), 36 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 37 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 38 | 'status': ('django.db.models.fields.IntegerField', [], {}), 39 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 40 | 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '36', 'db_index': 'True'}) 41 | }, 42 | u'mdm.device': { 43 | 'Meta': {'object_name': 'Device'}, 44 | 'activation_lock': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 45 | 'available_capacity': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 46 | 'battery_level': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 47 | 'bluetooth_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '17', 'blank': 'True'}), 48 | 'build_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}), 49 | 'capacity': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 50 | 'cloud_backup': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 51 | 'ethernet_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '288', 'blank': 'True'}), 52 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 53 | 'last_checkin': ('django.db.models.fields.DateTimeField', [], {}), 54 | 'locator_service': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 55 | 'model': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 56 | 'model_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 57 | 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), 58 | 'os_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}), 59 | 'phone_number': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 60 | 'product_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 61 | 'push_magic': ('django.db.models.fields.CharField', [], {'max_length': '48'}), 62 | 'push_token': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 63 | 'push_topic': ('django.db.models.fields.CharField', [], {'max_length': '96'}), 64 | 'serial_number': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), 65 | 'supervised': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 66 | 'udid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36', 'db_index': 'True'}), 67 | 'wifi_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '17', 'blank': 'True'}) 68 | }, 69 | u'mdm.devicemanagedpreference': { 70 | 'Meta': {'unique_together': "(('device', 'application', 'frequency'),)", 'object_name': 'DeviceManagedPreference'}, 71 | 'application': ('django.db.models.fields.CharField', [], {'max_length': '200'}), 72 | 'device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['mdm.Device']"}), 73 | 'frequency': ('django.db.models.fields.IntegerField', [], {}), 74 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 75 | 'plist': ('django.db.models.fields.TextField', [], {}) 76 | }, 77 | u'mdm.inventoryitem': { 78 | 'Meta': {'ordering': "['name', '-version']", 'object_name': 'InventoryItem'}, 79 | 'bundle_size': ('django.db.models.fields.IntegerField', [], {}), 80 | 'device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['mdm.Device']"}), 81 | 'dynamic_size': ('django.db.models.fields.IntegerField', [], {}), 82 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 83 | 'identifier': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 84 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 85 | 'short_version': ('django.db.models.fields.CharField', [], {'max_length': '32'}), 86 | 'version': ('django.db.models.fields.CharField', [], {'max_length': '32'}) 87 | } 88 | } 89 | 90 | complete_apps = ['mdm'] -------------------------------------------------------------------------------- /mdm/migrations/0008_auto__add_lastdevicestatus.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'LastDeviceStatus' 12 | db.create_table(u'mdm_lastdevicestatus', ( 13 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('device', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['mdm.Device'])), 15 | ('type', self.gf('django.db.models.fields.CharField')(max_length=128)), 16 | ('date', self.gf('django.db.models.fields.DateTimeField')()), 17 | )) 18 | db.send_create_signal(u'mdm', ['LastDeviceStatus']) 19 | 20 | 21 | def backwards(self, orm): 22 | # Deleting model 'LastDeviceStatus' 23 | db.delete_table(u'mdm_lastdevicestatus') 24 | 25 | 26 | models = { 27 | u'mdm.appinventoryitem': { 28 | 'Meta': {'ordering': "['name', '-version']", 'object_name': 'AppInventoryItem'}, 29 | 'bundle_size': ('django.db.models.fields.IntegerField', [], {'default': '0'}), 30 | 'device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['mdm.Device']"}), 31 | 'dynamic_size': ('django.db.models.fields.IntegerField', [], {'default': '0'}), 32 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 33 | 'identifier': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 34 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 35 | 'short_version': ('django.db.models.fields.CharField', [], {'max_length': '32'}), 36 | 'version': ('django.db.models.fields.CharField', [], {'max_length': '32'}) 37 | }, 38 | u'mdm.command': { 39 | 'Meta': {'object_name': 'Command'}, 40 | 'data': ('django.db.models.fields.CharField', [], {'max_length': '256', 'blank': 'True'}), 41 | 'date_requested': ('django.db.models.fields.DateTimeField', [], {}), 42 | 'device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['mdm.Device']"}), 43 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 44 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 45 | 'status': ('django.db.models.fields.IntegerField', [], {}), 46 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 47 | 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '36', 'db_index': 'True'}) 48 | }, 49 | u'mdm.device': { 50 | 'Meta': {'object_name': 'Device'}, 51 | 'activation_lock': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 52 | 'available_capacity': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 53 | 'battery_level': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 54 | 'bluetooth_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '17', 'blank': 'True'}), 55 | 'build_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}), 56 | 'capacity': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 57 | 'cloud_backup': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 58 | 'ethernet_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '288', 'blank': 'True'}), 59 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 60 | 'last_checkin': ('django.db.models.fields.DateTimeField', [], {}), 61 | 'locator_service': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 62 | 'model': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 63 | 'model_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 64 | 'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), 65 | 'os_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '16', 'blank': 'True'}), 66 | 'phone_number': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 67 | 'product_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), 68 | 'push_magic': ('django.db.models.fields.CharField', [], {'max_length': '48'}), 69 | 'push_token': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 70 | 'push_topic': ('django.db.models.fields.CharField', [], {'max_length': '96'}), 71 | 'serial_number': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), 72 | 'supervised': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), 73 | 'udid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36', 'db_index': 'True'}), 74 | 'wifi_mac': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '17', 'blank': 'True'}) 75 | }, 76 | u'mdm.devicemanagedpreference': { 77 | 'Meta': {'unique_together': "(('device', 'application', 'frequency'),)", 'object_name': 'DeviceManagedPreference'}, 78 | 'application': ('django.db.models.fields.CharField', [], {'max_length': '200'}), 79 | 'device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['mdm.Device']"}), 80 | 'frequency': ('django.db.models.fields.IntegerField', [], {}), 81 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 82 | 'plist': ('django.db.models.fields.TextField', [], {}) 83 | }, 84 | u'mdm.lastdevicestatus': { 85 | 'Meta': {'object_name': 'LastDeviceStatus'}, 86 | 'date': ('django.db.models.fields.DateTimeField', [], {}), 87 | 'device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['mdm.Device']"}), 88 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 89 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '128'}) 90 | } 91 | } 92 | 93 | complete_apps = ['mdm'] -------------------------------------------------------------------------------- /mdm/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/mdm/migrations/__init__.py -------------------------------------------------------------------------------- /mdm/preferences_views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core.urlresolvers import reverse 3 | from django.shortcuts import render 4 | from django.views.decorators.csrf import csrf_exempt 5 | from django.http import HttpResponse, HttpResponseBadRequest, Http404 6 | from models import Device, DeviceManagedPreference, DeviceGroup, DeviceGroupManagedPreference 7 | from datetime import datetime, timedelta 8 | import plistlib 9 | import uuid 10 | import mcx 11 | import json 12 | 13 | 14 | @csrf_exempt 15 | def device(request, udid, ident): 16 | try: 17 | device = Device.objects.get(udid=udid) 18 | except: 19 | raise Http404 20 | 21 | if request.method == 'GET': 22 | data = request.GET 23 | else: 24 | data = request.POST 25 | 26 | if 'command' in data: 27 | return device_api(request, data, device, ident) 28 | 29 | context = { 30 | 'device': device, 31 | 'identifier': ident, 32 | } 33 | 34 | return render(request, 'mdm/preferences/device.html', context) 35 | 36 | 37 | def device_api(request, data, device, ident): 38 | command = data['command'] 39 | 40 | response = { } 41 | if command == 'get': 42 | pref = DeviceManagedPreference.objects.get(device=device, identifier=ident) 43 | try: 44 | pref = DeviceManagedPreference.objects.get(device=device, identifier=ident) 45 | plist = pref.plist 46 | except: 47 | plist = 'OnceOftenAlways' 48 | 49 | response['status'] = 'OK' 50 | response['data'] = plist 51 | elif command == 'set': 52 | try: 53 | pref = DeviceManagedPreference.objects.get(device=device, identifier=ident) 54 | except: 55 | pref = DeviceManagedPreference(device=device, identifier=ident) 56 | 57 | pref.plist = data['plist'] 58 | pref.save() 59 | 60 | mcx.build_system_configuration_profile(device, install=True) 61 | 62 | response['status'] = 'OK' 63 | else: 64 | response['status'] = 'ERROR' 65 | response['error'] = 'Unknown command' 66 | 67 | return HttpResponse(json.dumps(response)) 68 | 69 | 70 | @csrf_exempt 71 | def devicegroup(request, id, ident): 72 | try: 73 | group = DeviceGroup.objects.get(uuid=id) 74 | except: 75 | raise Http404 76 | 77 | if request.method == 'GET': 78 | data = request.GET 79 | else: 80 | data = request.POST 81 | 82 | if 'command' in data: 83 | return devicegroup_api(request, data, group, ident) 84 | 85 | context = { 86 | 'group': group, 87 | 'identifier': ident, 88 | } 89 | 90 | return render(request, 'mdm/preferences/devicegroup.html', context) 91 | 92 | 93 | def devicegroup_api(request, data, group, ident): 94 | command = data['command'] 95 | 96 | response = { } 97 | if command == 'get': 98 | try: 99 | pref = DeviceGroupManagedPreference.objects.get(group=group, identifier=ident) 100 | plist = pref.plist 101 | except: 102 | plist = 'OnceOftenAlways' 103 | 104 | response['status'] = 'OK' 105 | response['data'] = plist 106 | elif command == 'set': 107 | try: 108 | pref = DeviceGroupManagedPreference.objects.get(group=group, identifier=ident) 109 | except: 110 | pref = DeviceGroupManagedPreference(group=group, identifier=ident) 111 | 112 | pref.plist = data['plist'] 113 | pref.save() 114 | 115 | for device in group.recursive_devices(): 116 | mcx.build_system_configuration_profile(device, install=True) 117 | 118 | response['status'] = 'OK' 119 | else: 120 | response['status'] = 'ERROR' 121 | response['error'] = 'Unknown command' 122 | 123 | return HttpResponse(json.dumps(response)) 124 | 125 | 126 | -------------------------------------------------------------------------------- /mdm/push.py: -------------------------------------------------------------------------------- 1 | from APNSWrapper import * 2 | import base64 3 | from models import Device 4 | from datetime import datetime 5 | 6 | 7 | def push_notification(dev): 8 | dev.last_notification = datetime.now() 9 | dev.save() 10 | 11 | token = base64.b64decode(dev.push_token) 12 | magic = str(dev.push_magic) 13 | topic = str(dev.push_topic) 14 | 15 | wrapper = APNSNotificationWrapper('/var/mdm/env/managedmacadmin/mdm/pushcert.pem', False) 16 | 17 | message = APNSNotification() 18 | message.token(token) 19 | message.appendProperty(APNSProperty('mdm', magic)) 20 | wrapper.append(message) 21 | 22 | return wrapper.notify() 23 | 24 | -------------------------------------------------------------------------------- /mdm/pushcli.py: -------------------------------------------------------------------------------- 1 | from APNSWrapper import * 2 | import base64 3 | 4 | token = base64.b64decode('tuAjccOSU0aMqd+0TITgzVbUk3m2UjsrcJa3PQCYvOU=') 5 | magic = '58489227-0A45-4F8B-8838-1EE94C848135' 6 | topic = 'com.apple.mgmt.External.89ac8a6a-4780-4825-8b8d-0a148f40c795' 7 | 8 | wrapper = APNSNotificationWrapper('pushcert.pem', False) 9 | 10 | message = APNSNotification() 11 | message.token(token) 12 | message.appendProperty(APNSProperty('mdm', magic)) 13 | wrapper.append(message) 14 | if (wrapper.notify()): 15 | print 'Message sent successfully' 16 | else: 17 | print 'Failed to send push notification' 18 | 19 | -------------------------------------------------------------------------------- /mdm/static/mdm/jquery.plist.css: -------------------------------------------------------------------------------- 1 | table.plist { 2 | width: 100%; 3 | border-spacing: 0px; 4 | border-collapse: separate; 5 | } 6 | 7 | table.plist > tbody > tr > td.plist-key { 8 | min-width: 100px; 9 | white-space: nowrap; 10 | } 11 | 12 | table.plist > tbody > tr > td.plist-type { 13 | padding-left: 10px; 14 | padding-right: 10px; 15 | } 16 | 17 | table.plist > tbody > tr > td.plist-value { 18 | min-width: 350px; 19 | } 20 | 21 | table.plist > tbody > tr > td.plist-key > input { 22 | width: 200px; 23 | margin-top: 2px; 24 | margin-bottom: 2px; 25 | } 26 | 27 | table.plist > tbody > tr > td.plist-type > select { 28 | margin-top: 2px; 29 | margin-bottom: 2px; 30 | } 31 | 32 | table.plist > tbody > tr > td.plist-value > input { 33 | width: 300px; 34 | margin-top: 2px; 35 | margin-bottom: 2px; 36 | } 37 | 38 | table.plist > tbody > tr > td.plist-key > span.plist-indent { 39 | display: inline-block; 40 | margin: 0px; 41 | padding: 0px; 42 | } 43 | 44 | table.plist > tbody > tr > td.plist-value > span.plist-value-icons { 45 | display: inline-block; 46 | float: right; 47 | margin-left: 5px; 48 | margin-right: 5px; 49 | } 50 | 51 | table.plist > tbody > tr > td.plist-value > span.plist-value-icons > i { 52 | margin-left: 5px; 53 | cursor: pointer; 54 | } 55 | 56 | table.plist > tbody > tr > td.plist-key > input { 57 | width: 175px; 58 | } 59 | 60 | table.plist > tbody > tr.plist-indentlevel-0 { background-color: #d9edf7; } 61 | table.plist > tbody > tr.plist-indentlevel-1 { background-color: #edf7d9; } 62 | table.plist > tbody > tr.plist-indentlevel-2 { background-color: #f7d9c2; } 63 | table.plist > tbody > tr.plist-indentlevel-3 { background-color: #d99292; } 64 | table.plist > tbody > tr.plist-indentlevel-4 { background-color: #92d992; } 65 | -------------------------------------------------------------------------------- /mdm/static/mdm/mdm.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | $.fn.editable = function(fnSave, fnValidate, fnFormat) { 3 | var content = $('').text($(this).text()); 4 | var edit = $(''); 5 | 6 | if (typeof(fnSave) === 'undefined') 7 | fnSave = function (object, value, done) { done(true); }; 8 | if (typeof(fnValidate) === 'undefined') 9 | fnValidate = function (object, value) { 10 | if ($(object).data('required') == true && value.length == 0) { 11 | alert('Value is required.'); 12 | return false; 13 | } 14 | return true; 15 | }; 16 | 17 | if ($(content).text().length == 0) 18 | $(this).addClass('empty'); 19 | else 20 | $(this).removeClass('empty'); 21 | 22 | $(this).text(''); 23 | $(this).append(content).append(edit); 24 | 25 | $(edit).click(function () { 26 | var src = $(this).prevAll('.editableContent').first(); 27 | var tb = $(''); 28 | var save = $(''); 29 | var cancel = $(''); 30 | var width = $(src).width(); 31 | 32 | $(tb).val($(src).text()); 33 | $(this).parent().append(tb).append(save).append(cancel); 34 | $(src).hide(); 35 | $(this).css('display', 'none'); 36 | width -= $(save).outerWidth(); 37 | width -= $(cancel).outerWidth(); 38 | if (width < 100) 39 | width = 100; 40 | $(tb).css('width', width + 'px'); 41 | $(tb).select(); 42 | $(tb).focus(); 43 | 44 | $(tb).keyup(function (e) { 45 | if (e.keyCode == 13) { 46 | e.preventDefault(); 47 | $(save).click(); 48 | } 49 | if (e.keyCode == 27) { 50 | e.preventDefault(); 51 | $(cancel).click(); 52 | } 53 | }); 54 | 55 | $(save).click(function () { 56 | var tb = $(this).prevAll('.editing').first(); 57 | var save = $(this); 58 | var cancel = $(this).next('.cancelButton').first(); 59 | var orig = $(this).prevAll('.editableContent').first(); 60 | var container = $(this).parent('.editable').first(); 61 | var icon = $(this).prevAll('.fa-edit'); 62 | var value = $(tb).val(); 63 | 64 | var done = function (ok) { 65 | if (ok === true) { 66 | $(orig).text(value); 67 | $(orig).show(); 68 | $(icon).css('display', ''); 69 | $(tb).remove(); 70 | $(save).remove(); 71 | $(cancel).remove(); 72 | 73 | if (value.length == 0) 74 | $(container).addClass('empty'); 75 | else 76 | $(container).removeClass('empty'); 77 | } 78 | else 79 | $(save).removeClass('active'); 80 | }; 81 | 82 | if (fnValidate($(orig).parent(), value) === false) { 83 | $(tb).focus(); 84 | return; 85 | } 86 | 87 | $(save).addClass('active'); 88 | fnSave($(orig).parent(), value, done); 89 | }); 90 | 91 | $(cancel).click(function () { 92 | var tb = $(this).prevAll('.editing').first(); 93 | var save = $(this).prevAll('.saveButton').first(); 94 | var cancel = $(this); 95 | var orig = $(this).prevAll('.editableContent').first(); 96 | var icon = $(this).prevAll('.fa-edit'); 97 | 98 | $(orig).show(); 99 | $(icon).css('display', ''); 100 | $(tb).remove(); 101 | $(save).remove(); 102 | $(cancel).remove(); 103 | }); 104 | }); 105 | }; 106 | 107 | /* 108 | * Selected elements become fancy two-stage delete buttons. 109 | */ 110 | $.fn.deleteButton = function(fnDelete) { 111 | if ($(this).length > 1) { 112 | $(this).each(function() { $(this).deleteButton(fnDelete); }); 113 | return $(this); 114 | } 115 | 116 | var expandButton = function(e) { 117 | var btn = $(this); 118 | var origContent = $(btn).html(); 119 | var origWidth = $(btn).outerWidth(), origHeight = $(btn).outerHeight(); 120 | var width, height; 121 | 122 | $(btn).unbind("click.ExpandDelete"); 123 | $(btn).bind("click.DoDelete", fnDelete); 124 | $(btn).html(origContent + ' Delete'); 125 | width = $(btn).outerWidth(); 126 | height = $(btn).outerHeight(); 127 | $(btn).html(origContent); 128 | $(btn).css('visibility', 'visible'); 129 | $(btn).css('text-align', 'left'); 130 | $(btn).css('width', origWidth); 131 | $(btn).css('height', origHeight); 132 | $(btn).animate({ width: width, height: height }, 100, function() { 133 | $(btn).html(origContent + ' Delete'); 134 | }); 135 | 136 | e.stopPropagation(); 137 | 138 | $(document).bind("click.ClearDelete", function (e) { 139 | if ($(btn).has(e.target).length > 0 || e.target == $(btn).get(0)) 140 | return; 141 | $(btn).unbind("click.DoDelete"); 142 | $(document).unbind("click.ClearDelete"); 143 | $(btn).html($(btn).html().replace(' Delete', '')); 144 | $(btn).animate({ width: origWidth, height: origHeight }, 100, function() { 145 | $(btn).css('width', '').css('height', ''); 146 | $(btn).css('visibility', ''); 147 | $(this).bind("click.ExpandDelete", expandButton); 148 | }); 149 | }); 150 | }; 151 | 152 | $(this).bind("click.ExpandDelete", expandButton); 153 | }; 154 | })(jQuery); 155 | -------------------------------------------------------------------------------- /mdm/static/mdm/style.css: -------------------------------------------------------------------------------- 1 | .highlight { 2 | padding: 9px 14px; 3 | margin-bottom: 14px; 4 | background-color: #f7f7f9; 5 | border: 1px solid #e1e1e8; 6 | border-radius: 4px; 7 | } 8 | 9 | div.actions a { 10 | margin-bottom: 5px; 11 | } 12 | 13 | .spinner { 14 | display: inline-block; 15 | opacity: 0; 16 | max-width: 0; 17 | 18 | -webkit-transition: opacity 0.25s, max-width 0.45s; 19 | -moz-transition: opacity 0.25s, max-width 0.45s; 20 | -o-transition: opacity 0.25s, max-width 0.45s; 21 | transition: opacity 0.25s, max-width 0.45s; /* Duration fixed since we animate additional hidden width */ 22 | } 23 | 24 | .has-spinner.active .spinner { 25 | opacity: 1; 26 | max-width: 50px; /* More than it will ever come, notice that this affects on animation duration */ 27 | } 28 | 29 | .progress { 30 | margin-bottom: 0px; 31 | } 32 | 33 | .editable > i.fa-edit { opacity: 0; padding-left: 5px; padding-right: 5px; } 34 | .editable.empty > i.fa-edit { opacity: 1; } 35 | .editable:hover > i.fa-edit { opacity: 1; cursor: pointer; } 36 | .editable > input.editing { margin-bottom: 0px; } 37 | .editable > button { margin-left: 5px; } 38 | -------------------------------------------------------------------------------- /mdm/templates/mdm/battery_level.html: -------------------------------------------------------------------------------- 1 | {% load mdm_extras %} 2 | {% if battery_level %} 3 |
4 | {% if battery_level >= 0.5 %} 5 |
{{ battery_level|format_percent }}
6 | {% elif battery_level >= 0.25 %} 7 |
{{ battery_level|format_percent }}
8 | {% else %} 9 |
{{ battery_level|format_percent }}
10 | {% endif %} 11 |
12 | {% else %} 13 | n/a 14 | {% endif %} 15 | -------------------------------------------------------------------------------- /mdm/templates/mdm/enroll.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load mdm_extras %} 3 | {% load static %} 4 | 5 | {% block extra_style %} 6 | 7 | {% endblock %} 8 | 9 | {% block java_script %} 10 | 11 | 15 | {% endblock %} 16 | 17 | {% block content %} 18 | {% if invalid %} 19 |
Invalid PIN
20 | {% endif %} 21 |
22 |
23 | Pin Code:
24 | 25 |
26 |
27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /mdm/templates/mdm/groups/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load mdm_extras %} 3 | {% load static %} 4 | 5 | {% block extra_style %} 6 | 7 | 8 | 12 | {% endblock %} 13 | 14 | {% block java_script %} 15 | 16 | 17 | 89 | {% endblock %} 90 | 91 | {% block content %} 92 |

Device Groups

93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | {% for group in groups %} 104 | 105 | 106 | 107 | 108 | 109 | 110 | {% endfor %} 111 | 112 |
NamePriorityDevicesPreferences
{{ group.name }} {{ group.priority }}{{ group.devices.count }}{{ group.devicegroupmanagedpreference_set.count }}
113 | 114 |
115 | 116 | 122 |
123 | {% endblock %} 124 | -------------------------------------------------------------------------------- /mdm/templates/mdm/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load mdm_extras %} 3 | {% load static %} 4 | 5 | {% block extra_style %} 6 | 7 | {% endblock %} 8 | 9 | {% block java_script %} 10 | 11 | 23 | {% endblock %} 24 | 25 | {% block content %} 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {% for device in devices %} 39 | 40 | 41 | 42 | 45 | 46 | 47 | 48 | 49 | {% endfor %} 50 | 51 |
NameLast CheckinBatteryTypeModelProduct
{{ device }}{{ device.last_checkin }} 43 | {% include "mdm/battery_level.html" with battery_level=device.battery_level %} 44 | {{ device.get_type_display }}{{ device.product_name | format_model:device.model_name }}{{ device.product_name }}
52 | {% endblock %} 53 | -------------------------------------------------------------------------------- /mdm/templates/mdm/preferences/device.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load mdm_extras %} 3 | {% load static %} 4 | 5 | {% block extra_style %} 6 | 7 | 8 | {% endblock %} 9 | 10 | {% block java_script %} 11 | 12 | 46 | {% endblock %} 47 | 48 | {% block content %} 49 |

{{ device }}

50 | 51 |
52 |

{{ identifier }}

53 |
54 |
55 | 56 |
57 | 58 | Cancel 59 |
60 | {% endblock %} 61 | -------------------------------------------------------------------------------- /mdm/templates/mdm/preferences/devicegroup.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load mdm_extras %} 3 | {% load static %} 4 | 5 | {% block extra_style %} 6 | 7 | 8 | {% endblock %} 9 | 10 | {% block java_script %} 11 | 12 | 46 | {% endblock %} 47 | 48 | {% block content %} 49 |

{{ group.name }}

50 | 51 |
52 |

{{identifier}}

53 |
54 |
55 | 56 |
57 | 58 | Cancel 59 |
60 | {% endblock %} 61 | -------------------------------------------------------------------------------- /mdm/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/mdm/templatetags/__init__.py -------------------------------------------------------------------------------- /mdm/templatetags/mdm_extras.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | 4 | register = template.Library() 5 | 6 | 7 | @register.filter 8 | def format_number(value, arg): 9 | if value == None: 10 | return "" 11 | 12 | return arg % value 13 | 14 | 15 | @register.filter 16 | def format_date(value): 17 | if value == None: 18 | return "" 19 | 20 | return value.strftime("%m/%02d/%Y %l:%02M%p") 21 | 22 | 23 | @register.filter 24 | def format_percent(value): 25 | if value == None: 26 | return "0%" 27 | 28 | return "%d%%" % (value * 100) 29 | 30 | 31 | @register.filter 32 | def format_model(value, arg): 33 | models = [ 34 | { 'name': 'iPad', 'ident': ['iPad1,1'] }, 35 | { 'name': 'iPad 2', 'ident': ['iPad2,1', 'iPad2,2', 'iPad2,3', 'iPad2,4'] }, 36 | { 'name': 'iPad Retina', 'ident': ['iPad3,1', 'iPad3,2', 'iPad3,3', 'iPad3,4', 'iPad3,5', 'iPad3,6'] }, 37 | { 'name': 'iPad Mini', 'ident': ['iPad2,5', 'iPad2,6', 'iPad2,7'] }, 38 | { 'name': 'iPad Air', 'ident': ['iPad4,1', 'iPad4,2'] }, 39 | { 'name': 'iPad Mini Retina', 'ident': ['iPad4,4', 'iPad4,5'] }, 40 | ] 41 | 42 | for model in models: 43 | if value in model['ident']: 44 | return model['name'] 45 | 46 | return arg 47 | -------------------------------------------------------------------------------- /mdm/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from django.views.generic import RedirectView 3 | 4 | urlpatterns = patterns('', 5 | url(r'^$', RedirectView.as_view(url='monitor/')), 6 | 7 | url(r'^checkin$', 'mdm.views.checkin'), 8 | url(r'^enroll$', 'mdm.views.enroll'), 9 | 10 | url(r'^monitor/$', 'mdm.views.index'), 11 | url(r'^monitor/detail/(?P[a-zA-Z0-9\-]+)/$', 'mdm.views.detail', name='device_detail'), 12 | url(r'^monitor/detail/(?P[a-zA-Z0-9\-]+)/api$', 'mdm.views.monitor_detail_api'), 13 | url(r'^monitor/detail/(?P[a-zA-Z0-9\-]+)/profile/$', 'mdm.views.install_profile'), 14 | url(r'^monitor/detail/(?P[a-zA-Z0-9\-]+)/push/$', 'mdm.views.send_push'), 15 | url(r'^monitor/detail/(?P[a-zA-Z0-9\-]+)/info/$', 'mdm.views.device_information'), 16 | url(r'^monitor/detail/(?P[a-zA-Z0-9\-]+)/profiles/$', 'mdm.views.profile_list'), 17 | url(r'^monitor/detail/(?P[a-zA-Z0-9\-]+)/apps/$', 'mdm.views.application_list'), 18 | url(r'^monitor/detail/(?P[a-zA-Z0-9\-]+)/preference/(?P[a-zA-Z0-9\-\._]+)/$', 'mdm.preferences_views.device', name='device_preference_edit'), 19 | 20 | url(r'^groups/$', 'mdm.groups_views.index', name='groups_index'), 21 | url(r'^groups/(?P[a-zA-Z0-9\-]+)/$', 'mdm.groups_views.detail', name='devicegroup_detail'), 22 | url(r'^groups/(?P[a-zA-Z0-9\-]+)/api/$', 'mdm.groups_views.api'), 23 | url(r'^groups/(?P[a-zA-Z0-9\-]+)/preference/(?P[a-zA-Z0-9\-\._]+)/$', 'mdm.preferences_views.devicegroup', name='devicegroup_preference_edit'), 24 | ) 25 | -------------------------------------------------------------------------------- /reports/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/reports/__init__.py -------------------------------------------------------------------------------- /reports/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from reports.models import Machine, Fact, HistoricalFact 3 | 4 | admin.site.register(Machine) 5 | admin.site.register(Fact) 6 | admin.site.register(HistoricalFact) 7 | -------------------------------------------------------------------------------- /reports/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils import timezone 3 | from datetime import datetime 4 | 5 | 6 | class Machine(models.Model): 7 | mac = models.CharField(max_length=17, unique=True, db_index=True) 8 | hostname = models.CharField(max_length=64) 9 | last_checkin = models.DateTimeField(default=timezone.make_aware(datetime(1, 1, 1, 0, 0), timezone.get_current_timezone())) 10 | class Meta: 11 | ordering = ['hostname'] 12 | 13 | 14 | class Fact(models.Model): 15 | machine = models.ForeignKey(Machine) 16 | name = models.CharField(max_length=128, db_index=True) 17 | value = models.TextField(default='') 18 | last_update = models.DateTimeField(default=timezone.now()) 19 | class Meta: 20 | ordering = ['name'] 21 | 22 | 23 | class HistoricalFact(models.Model): 24 | machine = models.ForeignKey(Machine) 25 | name = models.CharField(max_length=128, db_index=True) 26 | value = models.TextField(default='') 27 | timestamp = models.DateTimeField(default=timezone.now(), db_index=True) 28 | class Meta: 29 | ordering = ['timestamp', 'name'] 30 | -------------------------------------------------------------------------------- /reports/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /reports/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | 3 | urlpatterns = patterns('reports.views', 4 | url(r'^submit$', 'submit'), 5 | ) 6 | -------------------------------------------------------------------------------- /reports/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.http import HttpResponse, HttpRequest, HttpResponseRedirect 3 | from django.utils import timezone 4 | from django.core.context_processors import csrf 5 | from django.views.decorators.csrf import csrf_exempt 6 | import json 7 | 8 | from models import Machine, Fact, HistoricalFact 9 | 10 | 11 | @csrf_exempt 12 | def submit(request): 13 | if request.method == 'POST': 14 | submit = request.POST 15 | elif request.method == 'GET': 16 | submit = request.GET 17 | else: 18 | raise Http404 19 | 20 | mac = submit.get('mac') 21 | machine = None 22 | if mac: 23 | try: 24 | machine = Machine.objects.get(mac=mac) 25 | except Machine.DoesNotExist: 26 | machine = Machine(mac=mac) 27 | 28 | if machine: 29 | if submit.get('hostname'): 30 | machine.hostname = submit.get('hostname') 31 | machine.last_checkin = timezone.now() 32 | machine.save() 33 | 34 | response = '' 35 | 36 | facts = submit.get('Facts') 37 | if facts is not None: 38 | facts = json.loads(facts) 39 | for key in facts: 40 | try: 41 | fact = Fact.objects.get(machine=machine, name=key) 42 | except: 43 | fact = Fact(machine=machine, name=key) 44 | fact.last_update = timezone.now() 45 | fact.value = facts[key] 46 | 47 | fact.save() 48 | 49 | facts = submit.get('HistoricalFacts') 50 | if facts is not None: 51 | facts = json.loads(facts) 52 | for key in facts: 53 | fact = HistoricalFact(machine=machine, name=key) 54 | fact.timestamp = timezone.now() 55 | fact.value = facts[key] 56 | 57 | fact.save() 58 | 59 | return HttpResponse('Report submitted.\n') 60 | 61 | return HttpResponse('Report not submitted.\n') 62 | -------------------------------------------------------------------------------- /static/admin/css/changelists.css: -------------------------------------------------------------------------------- 1 | /* CHANGELISTS */ 2 | 3 | #changelist { 4 | position: relative; 5 | width: 100%; 6 | } 7 | 8 | #changelist table { 9 | width: 100%; 10 | } 11 | 12 | .change-list .hiddenfields { display:none; } 13 | 14 | .change-list .filtered table { 15 | border-right: 1px solid #ddd; 16 | } 17 | 18 | .change-list .filtered { 19 | min-height: 400px; 20 | } 21 | 22 | .change-list .filtered { 23 | background: white url(../img/changelist-bg.gif) top right repeat-y !important; 24 | } 25 | 26 | .change-list .filtered .results, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { 27 | margin-right: 160px !important; 28 | width: auto !important; 29 | } 30 | 31 | .change-list .filtered table tbody th { 32 | padding-right: 1em; 33 | } 34 | 35 | #changelist-form .results { 36 | overflow-x: auto; 37 | } 38 | 39 | #changelist .toplinks { 40 | border-bottom: 1px solid #ccc !important; 41 | } 42 | 43 | #changelist .paginator { 44 | color: #666; 45 | border-top: 1px solid #eee; 46 | border-bottom: 1px solid #eee; 47 | background: white url(../img/nav-bg.gif) 0 180% repeat-x; 48 | overflow: hidden; 49 | } 50 | 51 | .change-list .filtered .paginator { 52 | border-right: 1px solid #ddd; 53 | } 54 | 55 | /* CHANGELIST TABLES */ 56 | 57 | #changelist table thead th { 58 | padding: 0; 59 | white-space: nowrap; 60 | vertical-align: middle; 61 | } 62 | 63 | #changelist table thead th.action-checkbox-column { 64 | width: 1.5em; 65 | text-align: center; 66 | } 67 | 68 | #changelist table tbody td, #changelist table tbody th { 69 | border-left: 1px solid #ddd; 70 | } 71 | 72 | #changelist table tbody td:first-child, #changelist table tbody th:first-child { 73 | border-left: 0; 74 | border-right: 1px solid #ddd; 75 | } 76 | 77 | #changelist table tbody td.action-checkbox { 78 | text-align:center; 79 | } 80 | 81 | #changelist table tfoot { 82 | color: #666; 83 | } 84 | 85 | /* TOOLBAR */ 86 | 87 | #changelist #toolbar { 88 | padding: 3px; 89 | border-bottom: 1px solid #ddd; 90 | background: #e1e1e1 url(../img/nav-bg.gif) top left repeat-x; 91 | color: #666; 92 | } 93 | 94 | #changelist #toolbar form input { 95 | font-size: 11px; 96 | padding: 1px 2px; 97 | } 98 | 99 | #changelist #toolbar form #searchbar { 100 | padding: 2px; 101 | } 102 | 103 | #changelist #changelist-search img { 104 | vertical-align: middle; 105 | } 106 | 107 | /* FILTER COLUMN */ 108 | 109 | #changelist-filter { 110 | position: absolute; 111 | top: 0; 112 | right: 0; 113 | z-index: 1000; 114 | width: 160px; 115 | border-left: 1px solid #ddd; 116 | background: #efefef; 117 | margin: 0; 118 | } 119 | 120 | #changelist-filter h2 { 121 | font-size: 11px; 122 | padding: 2px 5px; 123 | border-bottom: 1px solid #ddd; 124 | } 125 | 126 | #changelist-filter h3 { 127 | font-size: 12px; 128 | margin-bottom: 0; 129 | } 130 | 131 | #changelist-filter ul { 132 | padding-left: 0; 133 | margin-left: 10px; 134 | } 135 | 136 | #changelist-filter li { 137 | list-style-type: none; 138 | margin-left: 0; 139 | padding-left: 0; 140 | } 141 | 142 | #changelist-filter a { 143 | color: #999; 144 | } 145 | 146 | #changelist-filter a:hover { 147 | color: #036; 148 | } 149 | 150 | #changelist-filter li.selected { 151 | border-left: 5px solid #ccc; 152 | padding-left: 5px; 153 | margin-left: -10px; 154 | } 155 | 156 | #changelist-filter li.selected a { 157 | color: #5b80b2 !important; 158 | } 159 | 160 | /* DATE DRILLDOWN */ 161 | 162 | .change-list ul.toplinks { 163 | display: block; 164 | background: white url(../img/nav-bg-reverse.gif) 0 -10px repeat-x; 165 | border-top: 1px solid white; 166 | float: left; 167 | padding: 0 !important; 168 | margin: 0 !important; 169 | width: 100%; 170 | } 171 | 172 | .change-list ul.toplinks li { 173 | padding: 3px 6px; 174 | font-weight: bold; 175 | list-style-type: none; 176 | display: inline-block; 177 | } 178 | 179 | .change-list ul.toplinks .date-back a { 180 | color: #999; 181 | } 182 | 183 | .change-list ul.toplinks .date-back a:hover { 184 | color: #036; 185 | } 186 | 187 | /* PAGINATOR */ 188 | 189 | .paginator { 190 | font-size: 11px; 191 | padding-top: 10px; 192 | padding-bottom: 10px; 193 | line-height: 22px; 194 | margin: 0; 195 | border-top: 1px solid #ddd; 196 | } 197 | 198 | .paginator a:link, .paginator a:visited { 199 | padding: 2px 6px; 200 | border: solid 1px #ccc; 201 | background: white; 202 | text-decoration: none; 203 | } 204 | 205 | .paginator a.showall { 206 | padding: 0 !important; 207 | border: none !important; 208 | } 209 | 210 | .paginator a.showall:hover { 211 | color: #036 !important; 212 | background: transparent !important; 213 | } 214 | 215 | .paginator .end { 216 | border-width: 2px !important; 217 | margin-right: 6px; 218 | } 219 | 220 | .paginator .this-page { 221 | padding: 2px 6px; 222 | font-weight: bold; 223 | font-size: 13px; 224 | vertical-align: top; 225 | } 226 | 227 | .paginator a:hover { 228 | color: white; 229 | background: #5b80b2; 230 | border-color: #036; 231 | } 232 | 233 | /* ACTIONS */ 234 | 235 | .filtered .actions { 236 | margin-right: 160px !important; 237 | border-right: 1px solid #ddd; 238 | } 239 | 240 | #changelist table input { 241 | margin: 0; 242 | } 243 | 244 | #changelist table tbody tr.selected { 245 | background-color: #FFFFCC; 246 | } 247 | 248 | #changelist .actions { 249 | color: #999; 250 | padding: 3px; 251 | border-top: 1px solid #fff; 252 | border-bottom: 1px solid #ddd; 253 | background: white url(../img/nav-bg-reverse.gif) 0 -10px repeat-x; 254 | } 255 | 256 | #changelist .actions.selected { 257 | background: #fffccf; 258 | border-top: 1px solid #fffee8; 259 | border-bottom: 1px solid #edecd6; 260 | } 261 | 262 | #changelist .actions span.all, 263 | #changelist .actions span.action-counter, 264 | #changelist .actions span.clear, 265 | #changelist .actions span.question { 266 | font-size: 11px; 267 | margin: 0 0.5em; 268 | display: none; 269 | } 270 | 271 | #changelist .actions:last-child { 272 | border-bottom: none; 273 | } 274 | 275 | #changelist .actions select { 276 | border: 1px solid #aaa; 277 | margin-left: 0.5em; 278 | padding: 1px 2px; 279 | } 280 | 281 | #changelist .actions label { 282 | font-size: 11px; 283 | margin-left: 0.5em; 284 | } 285 | 286 | #changelist #action-toggle { 287 | display: none; 288 | } 289 | 290 | #changelist .actions .button { 291 | font-size: 11px; 292 | padding: 1px 2px; 293 | } 294 | -------------------------------------------------------------------------------- /static/admin/css/dashboard.css: -------------------------------------------------------------------------------- 1 | /* DASHBOARD */ 2 | 3 | .dashboard .module table th { 4 | width: 100%; 5 | } 6 | 7 | .dashboard .module table td { 8 | white-space: nowrap; 9 | } 10 | 11 | .dashboard .module table td a { 12 | display: block; 13 | padding-right: .6em; 14 | } 15 | 16 | /* RECENT ACTIONS MODULE */ 17 | 18 | .module ul.actionlist { 19 | margin-left: 0; 20 | } 21 | 22 | ul.actionlist li { 23 | list-style-type: none; 24 | } 25 | 26 | ul.actionlist li { 27 | overflow: hidden; 28 | text-overflow: ellipsis; 29 | -o-text-overflow: ellipsis; 30 | } 31 | -------------------------------------------------------------------------------- /static/admin/css/ie.css: -------------------------------------------------------------------------------- 1 | /* IE 6 & 7 */ 2 | 3 | /* Proper fixed width for dashboard in IE6 */ 4 | 5 | .dashboard #content { 6 | *width: 768px; 7 | } 8 | 9 | .dashboard #content-main { 10 | *width: 535px; 11 | } 12 | 13 | /* IE 6 ONLY */ 14 | 15 | /* Keep header from flowing off the page */ 16 | 17 | #container { 18 | _position: static; 19 | } 20 | 21 | /* Put the right sidebars back on the page */ 22 | 23 | .colMS #content-related { 24 | _margin-right: 0; 25 | _margin-left: 10px; 26 | _position: static; 27 | } 28 | 29 | /* Put the left sidebars back on the page */ 30 | 31 | .colSM #content-related { 32 | _margin-right: 10px; 33 | _margin-left: -115px; 34 | _position: static; 35 | } 36 | 37 | .form-row { 38 | _height: 1%; 39 | } 40 | 41 | /* Fix right margin for changelist filters in IE6 */ 42 | 43 | #changelist-filter ul { 44 | _margin-right: -10px; 45 | } 46 | 47 | /* IE ignores min-height, but treats height as if it were min-height */ 48 | 49 | .change-list .filtered { 50 | _height: 400px; 51 | } 52 | 53 | /* IE doesn't know alpha transparency in PNGs */ 54 | 55 | .inline-deletelink { 56 | background: transparent url(../img/inline-delete-8bit.png) no-repeat; 57 | } 58 | 59 | /* IE7 doesn't support inline-block */ 60 | .change-list ul.toplinks li { 61 | zoom: 1; 62 | *display: inline; 63 | } -------------------------------------------------------------------------------- /static/admin/css/login.css: -------------------------------------------------------------------------------- 1 | /* LOGIN FORM */ 2 | 3 | body.login { 4 | background: #eee; 5 | } 6 | 7 | .login #container { 8 | background: white; 9 | border: 1px solid #ccc; 10 | width: 28em; 11 | min-width: 300px; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-top: 100px; 15 | } 16 | 17 | .login #content-main { 18 | width: 100%; 19 | } 20 | 21 | .login form { 22 | margin-top: 1em; 23 | } 24 | 25 | .login .form-row { 26 | padding: 4px 0; 27 | float: left; 28 | width: 100%; 29 | } 30 | 31 | .login .form-row label { 32 | padding-right: 0.5em; 33 | line-height: 2em; 34 | font-size: 1em; 35 | clear: both; 36 | color: #333; 37 | } 38 | 39 | .login .form-row #id_username, .login .form-row #id_password { 40 | clear: both; 41 | padding: 6px; 42 | width: 100%; 43 | -webkit-box-sizing: border-box; 44 | -moz-box-sizing: border-box; 45 | box-sizing: border-box; 46 | } 47 | 48 | .login span.help { 49 | font-size: 10px; 50 | display: block; 51 | } 52 | 53 | .login .submit-row { 54 | clear: both; 55 | padding: 1em 0 0 9.4em; 56 | } 57 | 58 | .login .password-reset-link { 59 | text-align: center; 60 | } 61 | -------------------------------------------------------------------------------- /static/admin/css/rtl.css: -------------------------------------------------------------------------------- 1 | body { 2 | direction: rtl; 3 | } 4 | 5 | /* LOGIN */ 6 | 7 | .login .form-row { 8 | float: right; 9 | } 10 | 11 | .login .form-row label { 12 | float: right; 13 | padding-left: 0.5em; 14 | padding-right: 0; 15 | text-align: left; 16 | } 17 | 18 | .login .submit-row { 19 | clear: both; 20 | padding: 1em 9.4em 0 0; 21 | } 22 | 23 | /* GLOBAL */ 24 | 25 | th { 26 | text-align: right; 27 | } 28 | 29 | .module h2, .module caption { 30 | text-align: right; 31 | } 32 | 33 | .addlink, .changelink { 34 | padding-left: 0px; 35 | padding-right: 12px; 36 | background-position: 100% 0.2em; 37 | } 38 | 39 | .deletelink { 40 | padding-left: 0px; 41 | padding-right: 12px; 42 | background-position: 100% 0.25em; 43 | } 44 | 45 | .object-tools { 46 | float: left; 47 | } 48 | 49 | thead th:first-child, 50 | tfoot td:first-child { 51 | border-left: 1px solid #ddd !important; 52 | } 53 | 54 | /* LAYOUT */ 55 | 56 | #user-tools { 57 | right: auto; 58 | left: 0; 59 | text-align: left; 60 | } 61 | 62 | div.breadcrumbs { 63 | text-align: right; 64 | } 65 | 66 | #content-main { 67 | float: right; 68 | } 69 | 70 | #content-related { 71 | float: left; 72 | margin-left: -19em; 73 | margin-right: auto; 74 | } 75 | 76 | .colMS { 77 | margin-left: 20em !important; 78 | margin-right: 10px !important; 79 | } 80 | 81 | /* SORTABLE TABLES */ 82 | 83 | table thead th.sorted .sortoptions { 84 | float: left; 85 | } 86 | 87 | thead th.sorted .text { 88 | padding-right: 0; 89 | padding-left: 42px; 90 | } 91 | 92 | /* dashboard styles */ 93 | 94 | .dashboard .module table td a { 95 | padding-left: .6em; 96 | padding-right: 12px; 97 | } 98 | 99 | /* changelists styles */ 100 | 101 | .change-list .filtered { 102 | background: white url(../img/changelist-bg_rtl.gif) top left repeat-y !important; 103 | } 104 | 105 | .change-list .filtered table { 106 | border-left: 1px solid #ddd; 107 | border-right: 0px none; 108 | } 109 | 110 | #changelist-filter { 111 | right: auto; 112 | left: 0; 113 | border-left: 0px none; 114 | border-right: 1px solid #ddd; 115 | } 116 | 117 | .change-list .filtered .results, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { 118 | margin-right: 0px !important; 119 | margin-left: 160px !important; 120 | } 121 | 122 | #changelist-filter li.selected { 123 | border-left: 0px none; 124 | padding-left: 0px; 125 | margin-left: 0; 126 | border-right: 5px solid #ccc; 127 | padding-right: 5px; 128 | margin-right: -10px; 129 | } 130 | 131 | .filtered .actions { 132 | border-left:1px solid #DDDDDD; 133 | margin-left:160px !important; 134 | border-right: 0 none; 135 | margin-right:0 !important; 136 | } 137 | 138 | #changelist table tbody td:first-child, #changelist table tbody th:first-child { 139 | border-right: 0; 140 | border-left: 1px solid #ddd; 141 | } 142 | 143 | /* FORMS */ 144 | 145 | .aligned label { 146 | padding: 0 0 3px 1em; 147 | float: right; 148 | } 149 | 150 | .submit-row { 151 | text-align: left 152 | } 153 | 154 | .submit-row p.deletelink-box { 155 | float: right; 156 | } 157 | 158 | .submit-row .deletelink { 159 | background: url(../img/icon_deletelink.gif) 0 50% no-repeat; 160 | padding-right: 14px; 161 | } 162 | 163 | .vDateField, .vTimeField { 164 | margin-left: 2px; 165 | } 166 | 167 | form ul.inline li { 168 | float: right; 169 | padding-right: 0; 170 | padding-left: 7px; 171 | } 172 | 173 | input[type=submit].default, .submit-row input.default { 174 | float: left; 175 | } 176 | 177 | fieldset .field-box { 178 | float: right; 179 | margin-left: 20px; 180 | margin-right: 0; 181 | } 182 | 183 | .errorlist li { 184 | background-position: 100% .3em; 185 | padding: 4px 25px 4px 5px; 186 | } 187 | 188 | .errornote { 189 | background-position: 100% .3em; 190 | padding: 4px 25px 4px 5px; 191 | } 192 | 193 | /* WIDGETS */ 194 | 195 | .calendarnav-previous { 196 | top: 0; 197 | left: auto; 198 | right: 0; 199 | } 200 | 201 | .calendarnav-next { 202 | top: 0; 203 | right: auto; 204 | left: 0; 205 | } 206 | 207 | .calendar caption, .calendarbox h2 { 208 | text-align: center; 209 | } 210 | 211 | .selector { 212 | float: right; 213 | } 214 | 215 | .selector .selector-filter { 216 | text-align: right; 217 | } 218 | 219 | .inline-deletelink { 220 | float: left; 221 | } 222 | 223 | /* MISC */ 224 | 225 | .inline-related h2, .inline-group h2 { 226 | text-align: right 227 | } 228 | 229 | .inline-related h3 span.delete { 230 | padding-right: 20px; 231 | padding-left: inherit; 232 | left: 10px; 233 | right: inherit; 234 | float:left; 235 | } 236 | 237 | .inline-related h3 span.delete label { 238 | margin-left: inherit; 239 | margin-right: 2px; 240 | } 241 | 242 | /* IE7 specific bug fixes */ 243 | 244 | div.colM { 245 | position: relative; 246 | } 247 | 248 | .submit-row input { 249 | float: left; 250 | } -------------------------------------------------------------------------------- /static/admin/img/changelist-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/changelist-bg.gif -------------------------------------------------------------------------------- /static/admin/img/changelist-bg_rtl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/changelist-bg_rtl.gif -------------------------------------------------------------------------------- /static/admin/img/chooser-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/chooser-bg.gif -------------------------------------------------------------------------------- /static/admin/img/chooser_stacked-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/chooser_stacked-bg.gif -------------------------------------------------------------------------------- /static/admin/img/default-bg-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/default-bg-reverse.gif -------------------------------------------------------------------------------- /static/admin/img/default-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/default-bg.gif -------------------------------------------------------------------------------- /static/admin/img/deleted-overlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/deleted-overlay.gif -------------------------------------------------------------------------------- /static/admin/img/gis/move_vertex_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/gis/move_vertex_off.png -------------------------------------------------------------------------------- /static/admin/img/gis/move_vertex_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/gis/move_vertex_on.png -------------------------------------------------------------------------------- /static/admin/img/icon-no.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/icon-no.gif -------------------------------------------------------------------------------- /static/admin/img/icon-unknown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/icon-unknown.gif -------------------------------------------------------------------------------- /static/admin/img/icon-yes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/icon-yes.gif -------------------------------------------------------------------------------- /static/admin/img/icon_addlink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/icon_addlink.gif -------------------------------------------------------------------------------- /static/admin/img/icon_alert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/icon_alert.gif -------------------------------------------------------------------------------- /static/admin/img/icon_calendar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/icon_calendar.gif -------------------------------------------------------------------------------- /static/admin/img/icon_changelink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/icon_changelink.gif -------------------------------------------------------------------------------- /static/admin/img/icon_clock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/icon_clock.gif -------------------------------------------------------------------------------- /static/admin/img/icon_deletelink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/icon_deletelink.gif -------------------------------------------------------------------------------- /static/admin/img/icon_error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/icon_error.gif -------------------------------------------------------------------------------- /static/admin/img/icon_searchbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/icon_searchbox.png -------------------------------------------------------------------------------- /static/admin/img/icon_success.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/icon_success.gif -------------------------------------------------------------------------------- /static/admin/img/inline-delete-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/inline-delete-8bit.png -------------------------------------------------------------------------------- /static/admin/img/inline-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/inline-delete.png -------------------------------------------------------------------------------- /static/admin/img/inline-restore-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/inline-restore-8bit.png -------------------------------------------------------------------------------- /static/admin/img/inline-restore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/inline-restore.png -------------------------------------------------------------------------------- /static/admin/img/inline-splitter-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/inline-splitter-bg.gif -------------------------------------------------------------------------------- /static/admin/img/nav-bg-grabber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/nav-bg-grabber.gif -------------------------------------------------------------------------------- /static/admin/img/nav-bg-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/nav-bg-reverse.gif -------------------------------------------------------------------------------- /static/admin/img/nav-bg-selected.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/nav-bg-selected.gif -------------------------------------------------------------------------------- /static/admin/img/nav-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/nav-bg.gif -------------------------------------------------------------------------------- /static/admin/img/selector-icons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/selector-icons.gif -------------------------------------------------------------------------------- /static/admin/img/selector-search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/selector-search.gif -------------------------------------------------------------------------------- /static/admin/img/sorting-icons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/sorting-icons.gif -------------------------------------------------------------------------------- /static/admin/img/tool-left.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/tool-left.gif -------------------------------------------------------------------------------- /static/admin/img/tool-left_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/tool-left_over.gif -------------------------------------------------------------------------------- /static/admin/img/tool-right.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/tool-right.gif -------------------------------------------------------------------------------- /static/admin/img/tool-right_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/tool-right_over.gif -------------------------------------------------------------------------------- /static/admin/img/tooltag-add.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/tooltag-add.gif -------------------------------------------------------------------------------- /static/admin/img/tooltag-add_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/tooltag-add_over.gif -------------------------------------------------------------------------------- /static/admin/img/tooltag-arrowright.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/tooltag-arrowright.gif -------------------------------------------------------------------------------- /static/admin/img/tooltag-arrowright_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/admin/img/tooltag-arrowright_over.gif -------------------------------------------------------------------------------- /static/admin/js/LICENSE-JQUERY.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 John Resig, http://jquery.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /static/admin/js/SelectBox.js: -------------------------------------------------------------------------------- 1 | var SelectBox = { 2 | cache: new Object(), 3 | init: function(id) { 4 | var box = document.getElementById(id); 5 | var node; 6 | SelectBox.cache[id] = new Array(); 7 | var cache = SelectBox.cache[id]; 8 | for (var i = 0; (node = box.options[i]); i++) { 9 | cache.push({value: node.value, text: node.text, displayed: 1}); 10 | } 11 | }, 12 | redisplay: function(id) { 13 | // Repopulate HTML select box from cache 14 | var box = document.getElementById(id); 15 | box.options.length = 0; // clear all options 16 | for (var i = 0, j = SelectBox.cache[id].length; i < j; i++) { 17 | var node = SelectBox.cache[id][i]; 18 | if (node.displayed) { 19 | box.options[box.options.length] = new Option(node.text, node.value, false, false); 20 | } 21 | } 22 | }, 23 | filter: function(id, text) { 24 | // Redisplay the HTML select box, displaying only the choices containing ALL 25 | // the words in text. (It's an AND search.) 26 | var tokens = text.toLowerCase().split(/\s+/); 27 | var node, token; 28 | for (var i = 0; (node = SelectBox.cache[id][i]); i++) { 29 | node.displayed = 1; 30 | for (var j = 0; (token = tokens[j]); j++) { 31 | if (node.text.toLowerCase().indexOf(token) == -1) { 32 | node.displayed = 0; 33 | } 34 | } 35 | } 36 | SelectBox.redisplay(id); 37 | }, 38 | delete_from_cache: function(id, value) { 39 | var node, delete_index = null; 40 | for (var i = 0; (node = SelectBox.cache[id][i]); i++) { 41 | if (node.value == value) { 42 | delete_index = i; 43 | break; 44 | } 45 | } 46 | var j = SelectBox.cache[id].length - 1; 47 | for (var i = delete_index; i < j; i++) { 48 | SelectBox.cache[id][i] = SelectBox.cache[id][i+1]; 49 | } 50 | SelectBox.cache[id].length--; 51 | }, 52 | add_to_cache: function(id, option) { 53 | SelectBox.cache[id].push({value: option.value, text: option.text, displayed: 1}); 54 | }, 55 | cache_contains: function(id, value) { 56 | // Check if an item is contained in the cache 57 | var node; 58 | for (var i = 0; (node = SelectBox.cache[id][i]); i++) { 59 | if (node.value == value) { 60 | return true; 61 | } 62 | } 63 | return false; 64 | }, 65 | move: function(from, to) { 66 | var from_box = document.getElementById(from); 67 | var to_box = document.getElementById(to); 68 | var option; 69 | for (var i = 0; (option = from_box.options[i]); i++) { 70 | if (option.selected && SelectBox.cache_contains(from, option.value)) { 71 | SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1}); 72 | SelectBox.delete_from_cache(from, option.value); 73 | } 74 | } 75 | SelectBox.redisplay(from); 76 | SelectBox.redisplay(to); 77 | }, 78 | move_all: function(from, to) { 79 | var from_box = document.getElementById(from); 80 | var to_box = document.getElementById(to); 81 | var option; 82 | for (var i = 0; (option = from_box.options[i]); i++) { 83 | if (SelectBox.cache_contains(from, option.value)) { 84 | SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1}); 85 | SelectBox.delete_from_cache(from, option.value); 86 | } 87 | } 88 | SelectBox.redisplay(from); 89 | SelectBox.redisplay(to); 90 | }, 91 | sort: function(id) { 92 | SelectBox.cache[id].sort( function(a, b) { 93 | a = a.text.toLowerCase(); 94 | b = b.text.toLowerCase(); 95 | try { 96 | if (a > b) return 1; 97 | if (a < b) return -1; 98 | } 99 | catch (e) { 100 | // silently fail on IE 'unknown' exception 101 | } 102 | return 0; 103 | } ); 104 | }, 105 | select_all: function(id) { 106 | var box = document.getElementById(id); 107 | for (var i = 0; i < box.options.length; i++) { 108 | box.options[i].selected = 'selected'; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /static/admin/js/actions.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $.fn.actions = function(opts) { 3 | var options = $.extend({}, $.fn.actions.defaults, opts); 4 | var actionCheckboxes = $(this); 5 | var list_editable_changed = false; 6 | var checker = function(checked) { 7 | if (checked) { 8 | showQuestion(); 9 | } else { 10 | reset(); 11 | } 12 | $(actionCheckboxes).prop("checked", checked) 13 | .parent().parent().toggleClass(options.selectedClass, checked); 14 | }, 15 | updateCounter = function() { 16 | var sel = $(actionCheckboxes).filter(":checked").length; 17 | $(options.counterContainer).html(interpolate( 18 | ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), { 19 | sel: sel, 20 | cnt: _actions_icnt 21 | }, true)); 22 | $(options.allToggle).prop("checked", function() { 23 | if (sel == actionCheckboxes.length) { 24 | value = true; 25 | showQuestion(); 26 | } else { 27 | value = false; 28 | clearAcross(); 29 | } 30 | return value; 31 | }); 32 | }, 33 | showQuestion = function() { 34 | $(options.acrossClears).hide(); 35 | $(options.acrossQuestions).show(); 36 | $(options.allContainer).hide(); 37 | }, 38 | showClear = function() { 39 | $(options.acrossClears).show(); 40 | $(options.acrossQuestions).hide(); 41 | $(options.actionContainer).toggleClass(options.selectedClass); 42 | $(options.allContainer).show(); 43 | $(options.counterContainer).hide(); 44 | }, 45 | reset = function() { 46 | $(options.acrossClears).hide(); 47 | $(options.acrossQuestions).hide(); 48 | $(options.allContainer).hide(); 49 | $(options.counterContainer).show(); 50 | }, 51 | clearAcross = function() { 52 | reset(); 53 | $(options.acrossInput).val(0); 54 | $(options.actionContainer).removeClass(options.selectedClass); 55 | }; 56 | // Show counter by default 57 | $(options.counterContainer).show(); 58 | // Check state of checkboxes and reinit state if needed 59 | $(this).filter(":checked").each(function(i) { 60 | $(this).parent().parent().toggleClass(options.selectedClass); 61 | updateCounter(); 62 | if ($(options.acrossInput).val() == 1) { 63 | showClear(); 64 | } 65 | }); 66 | $(options.allToggle).show().click(function() { 67 | checker($(this).prop("checked")); 68 | updateCounter(); 69 | }); 70 | $("div.actions span.question a").click(function(event) { 71 | event.preventDefault(); 72 | $(options.acrossInput).val(1); 73 | showClear(); 74 | }); 75 | $("div.actions span.clear a").click(function(event) { 76 | event.preventDefault(); 77 | $(options.allToggle).prop("checked", false); 78 | clearAcross(); 79 | checker(0); 80 | updateCounter(); 81 | }); 82 | lastChecked = null; 83 | $(actionCheckboxes).click(function(event) { 84 | if (!event) { event = window.event; } 85 | var target = event.target ? event.target : event.srcElement; 86 | if (lastChecked && $.data(lastChecked) != $.data(target) && event.shiftKey === true) { 87 | var inrange = false; 88 | $(lastChecked).prop("checked", target.checked) 89 | .parent().parent().toggleClass(options.selectedClass, target.checked); 90 | $(actionCheckboxes).each(function() { 91 | if ($.data(this) == $.data(lastChecked) || $.data(this) == $.data(target)) { 92 | inrange = (inrange) ? false : true; 93 | } 94 | if (inrange) { 95 | $(this).prop("checked", target.checked) 96 | .parent().parent().toggleClass(options.selectedClass, target.checked); 97 | } 98 | }); 99 | } 100 | $(target).parent().parent().toggleClass(options.selectedClass, target.checked); 101 | lastChecked = target; 102 | updateCounter(); 103 | }); 104 | $('form#changelist-form table#result_list tr').find('td:gt(0) :input').change(function() { 105 | list_editable_changed = true; 106 | }); 107 | $('form#changelist-form button[name="index"]').click(function(event) { 108 | if (list_editable_changed) { 109 | return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost.")); 110 | } 111 | }); 112 | $('form#changelist-form input[name="_save"]').click(function(event) { 113 | var action_changed = false; 114 | $('div.actions select option:selected').each(function() { 115 | if ($(this).val()) { 116 | action_changed = true; 117 | } 118 | }); 119 | if (action_changed) { 120 | if (list_editable_changed) { 121 | return confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")); 122 | } else { 123 | return confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button.")); 124 | } 125 | } 126 | }); 127 | }; 128 | /* Setup plugin defaults */ 129 | $.fn.actions.defaults = { 130 | actionContainer: "div.actions", 131 | counterContainer: "span.action-counter", 132 | allContainer: "div.actions span.all", 133 | acrossInput: "div.actions input.select-across", 134 | acrossQuestions: "div.actions span.question", 135 | acrossClears: "div.actions span.clear", 136 | allToggle: "#action-toggle", 137 | selectedClass: "selected" 138 | }; 139 | })(django.jQuery); 140 | -------------------------------------------------------------------------------- /static/admin/js/actions.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a.fn.actions=function(n){var b=a.extend({},a.fn.actions.defaults,n),e=a(this),g=false,k=function(c){c?i():j();a(e).prop("checked",c).parent().parent().toggleClass(b.selectedClass,c)},f=function(){var c=a(e).filter(":checked").length;a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:_actions_icnt},true));a(b.allToggle).prop("checked",function(){if(c==e.length){value=true;i()}else{value=false;l()}return value})},i= 2 | function(){a(b.acrossClears).hide();a(b.acrossQuestions).show();a(b.allContainer).hide()},m=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},j=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},l=function(){j();a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)};a(b.counterContainer).show(); 3 | a(this).filter(":checked").each(function(){a(this).parent().parent().toggleClass(b.selectedClass);f();a(b.acrossInput).val()==1&&m()});a(b.allToggle).show().click(function(){k(a(this).prop("checked"));f()});a("div.actions span.question a").click(function(c){c.preventDefault();a(b.acrossInput).val(1);m()});a("div.actions span.clear a").click(function(c){c.preventDefault();a(b.allToggle).prop("checked",false);l();k(0);f()});lastChecked=null;a(e).click(function(c){if(!c)c=window.event;var d=c.target? 4 | c.target:c.srcElement;if(lastChecked&&a.data(lastChecked)!=a.data(d)&&c.shiftKey===true){var h=false;a(lastChecked).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked);a(e).each(function(){if(a.data(this)==a.data(lastChecked)||a.data(this)==a.data(d))h=h?false:true;h&&a(this).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);lastChecked=d;f()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){g= 5 | true});a('form#changelist-form button[name="index"]').click(function(){if(g)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))});a('form#changelist-form input[name="_save"]').click(function(){var c=false;a("div.actions select option:selected").each(function(){if(a(this).val())c=true});if(c)return g?confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")): 6 | confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."))})};a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across",acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"}})(django.jQuery); 7 | -------------------------------------------------------------------------------- /static/admin/js/admin/RelatedObjectLookups.js: -------------------------------------------------------------------------------- 1 | // Handles related-objects functionality: lookup link for raw_id_fields 2 | // and Add Another links. 3 | 4 | function html_unescape(text) { 5 | // Unescape a string that was escaped using django.utils.html.escape. 6 | text = text.replace(/</g, '<'); 7 | text = text.replace(/>/g, '>'); 8 | text = text.replace(/"/g, '"'); 9 | text = text.replace(/'/g, "'"); 10 | text = text.replace(/&/g, '&'); 11 | return text; 12 | } 13 | 14 | // IE doesn't accept periods or dashes in the window name, but the element IDs 15 | // we use to generate popup window names may contain them, therefore we map them 16 | // to allowed characters in a reversible way so that we can locate the correct 17 | // element when the popup window is dismissed. 18 | function id_to_windowname(text) { 19 | text = text.replace(/\./g, '__dot__'); 20 | text = text.replace(/\-/g, '__dash__'); 21 | return text; 22 | } 23 | 24 | function windowname_to_id(text) { 25 | text = text.replace(/__dot__/g, '.'); 26 | text = text.replace(/__dash__/g, '-'); 27 | return text; 28 | } 29 | 30 | function showRelatedObjectLookupPopup(triggeringLink) { 31 | var name = triggeringLink.id.replace(/^lookup_/, ''); 32 | name = id_to_windowname(name); 33 | var href; 34 | if (triggeringLink.href.search(/\?/) >= 0) { 35 | href = triggeringLink.href + '&_popup=1'; 36 | } else { 37 | href = triggeringLink.href + '?_popup=1'; 38 | } 39 | var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); 40 | win.focus(); 41 | return false; 42 | } 43 | 44 | function dismissRelatedLookupPopup(win, chosenId) { 45 | var name = windowname_to_id(win.name); 46 | var elem = document.getElementById(name); 47 | if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) { 48 | elem.value += ',' + chosenId; 49 | } else { 50 | document.getElementById(name).value = chosenId; 51 | } 52 | win.close(); 53 | } 54 | 55 | function showAddAnotherPopup(triggeringLink) { 56 | var name = triggeringLink.id.replace(/^add_/, ''); 57 | name = id_to_windowname(name); 58 | href = triggeringLink.href 59 | if (href.indexOf('?') == -1) { 60 | href += '?_popup=1'; 61 | } else { 62 | href += '&_popup=1'; 63 | } 64 | var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); 65 | win.focus(); 66 | return false; 67 | } 68 | 69 | function dismissAddAnotherPopup(win, newId, newRepr) { 70 | // newId and newRepr are expected to have previously been escaped by 71 | // django.utils.html.escape. 72 | newId = html_unescape(newId); 73 | newRepr = html_unescape(newRepr); 74 | var name = windowname_to_id(win.name); 75 | var elem = document.getElementById(name); 76 | if (elem) { 77 | var elemName = elem.nodeName.toUpperCase(); 78 | if (elemName == 'SELECT') { 79 | var o = new Option(newRepr, newId); 80 | elem.options[elem.options.length] = o; 81 | o.selected = true; 82 | } else if (elemName == 'INPUT') { 83 | if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) { 84 | elem.value += ',' + newId; 85 | } else { 86 | elem.value = newId; 87 | } 88 | } 89 | } else { 90 | var toId = name + "_to"; 91 | elem = document.getElementById(toId); 92 | var o = new Option(newRepr, newId); 93 | SelectBox.add_to_cache(toId, o); 94 | SelectBox.redisplay(toId); 95 | } 96 | win.close(); 97 | } 98 | -------------------------------------------------------------------------------- /static/admin/js/calendar.js: -------------------------------------------------------------------------------- 1 | /* 2 | calendar.js - Calendar functions by Adrian Holovaty 3 | depends on core.js for utility functions like removeChildren or quickElement 4 | */ 5 | 6 | // CalendarNamespace -- Provides a collection of HTML calendar-related helper functions 7 | var CalendarNamespace = { 8 | monthsOfYear: gettext('January February March April May June July August September October November December').split(' '), 9 | daysOfWeek: gettext('S M T W T F S').split(' '), 10 | firstDayOfWeek: parseInt(get_format('FIRST_DAY_OF_WEEK')), 11 | isLeapYear: function(year) { 12 | return (((year % 4)==0) && ((year % 100)!=0) || ((year % 400)==0)); 13 | }, 14 | getDaysInMonth: function(month,year) { 15 | var days; 16 | if (month==1 || month==3 || month==5 || month==7 || month==8 || month==10 || month==12) { 17 | days = 31; 18 | } 19 | else if (month==4 || month==6 || month==9 || month==11) { 20 | days = 30; 21 | } 22 | else if (month==2 && CalendarNamespace.isLeapYear(year)) { 23 | days = 29; 24 | } 25 | else { 26 | days = 28; 27 | } 28 | return days; 29 | }, 30 | draw: function(month, year, div_id, callback) { // month = 1-12, year = 1-9999 31 | var today = new Date(); 32 | var todayDay = today.getDate(); 33 | var todayMonth = today.getMonth()+1; 34 | var todayYear = today.getFullYear(); 35 | var todayClass = ''; 36 | 37 | month = parseInt(month); 38 | year = parseInt(year); 39 | var calDiv = document.getElementById(div_id); 40 | removeChildren(calDiv); 41 | var calTable = document.createElement('table'); 42 | quickElement('caption', calTable, CalendarNamespace.monthsOfYear[month-1] + ' ' + year); 43 | var tableBody = quickElement('tbody', calTable); 44 | 45 | // Draw days-of-week header 46 | var tableRow = quickElement('tr', tableBody); 47 | for (var i = 0; i < 7; i++) { 48 | quickElement('th', tableRow, CalendarNamespace.daysOfWeek[(i + CalendarNamespace.firstDayOfWeek) % 7]); 49 | } 50 | 51 | var startingPos = new Date(year, month-1, 1 - CalendarNamespace.firstDayOfWeek).getDay(); 52 | var days = CalendarNamespace.getDaysInMonth(month, year); 53 | 54 | // Draw blanks before first of month 55 | tableRow = quickElement('tr', tableBody); 56 | for (var i = 0; i < startingPos; i++) { 57 | var _cell = quickElement('td', tableRow, ' '); 58 | _cell.style.backgroundColor = '#f3f3f3'; 59 | } 60 | 61 | // Draw days of month 62 | var currentDay = 1; 63 | for (var i = startingPos; currentDay <= days; i++) { 64 | if (i%7 == 0 && currentDay != 1) { 65 | tableRow = quickElement('tr', tableBody); 66 | } 67 | if ((currentDay==todayDay) && (month==todayMonth) && (year==todayYear)) { 68 | todayClass='today'; 69 | } else { 70 | todayClass=''; 71 | } 72 | var cell = quickElement('td', tableRow, '', 'class', todayClass); 73 | 74 | quickElement('a', cell, currentDay, 'href', 'javascript:void(' + callback + '('+year+','+month+','+currentDay+'));'); 75 | currentDay++; 76 | } 77 | 78 | // Draw blanks after end of month (optional, but makes for valid code) 79 | while (tableRow.childNodes.length < 7) { 80 | var _cell = quickElement('td', tableRow, ' '); 81 | _cell.style.backgroundColor = '#f3f3f3'; 82 | } 83 | 84 | calDiv.appendChild(calTable); 85 | } 86 | } 87 | 88 | // Calendar -- A calendar instance 89 | function Calendar(div_id, callback) { 90 | // div_id (string) is the ID of the element in which the calendar will 91 | // be displayed 92 | // callback (string) is the name of a JavaScript function that will be 93 | // called with the parameters (year, month, day) when a day in the 94 | // calendar is clicked 95 | this.div_id = div_id; 96 | this.callback = callback; 97 | this.today = new Date(); 98 | this.currentMonth = this.today.getMonth() + 1; 99 | this.currentYear = this.today.getFullYear(); 100 | } 101 | Calendar.prototype = { 102 | drawCurrent: function() { 103 | CalendarNamespace.draw(this.currentMonth, this.currentYear, this.div_id, this.callback); 104 | }, 105 | drawDate: function(month, year) { 106 | this.currentMonth = month; 107 | this.currentYear = year; 108 | this.drawCurrent(); 109 | }, 110 | drawPreviousMonth: function() { 111 | if (this.currentMonth == 1) { 112 | this.currentMonth = 12; 113 | this.currentYear--; 114 | } 115 | else { 116 | this.currentMonth--; 117 | } 118 | this.drawCurrent(); 119 | }, 120 | drawNextMonth: function() { 121 | if (this.currentMonth == 12) { 122 | this.currentMonth = 1; 123 | this.currentYear++; 124 | } 125 | else { 126 | this.currentMonth++; 127 | } 128 | this.drawCurrent(); 129 | }, 130 | drawPreviousYear: function() { 131 | this.currentYear--; 132 | this.drawCurrent(); 133 | }, 134 | drawNextYear: function() { 135 | this.currentYear++; 136 | this.drawCurrent(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /static/admin/js/collapse.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $(document).ready(function() { 3 | // Add anchor tag for Show/Hide link 4 | $("fieldset.collapse").each(function(i, elem) { 5 | // Don't hide if fields in this fieldset have errors 6 | if ($(elem).find("div.errors").length == 0) { 7 | $(elem).addClass("collapsed").find("h2").first().append(' (' + gettext("Show") + 9 | ')'); 10 | } 11 | }); 12 | // Add toggle to anchor tag 13 | $("fieldset.collapse a.collapse-toggle").click(function(ev) { 14 | if ($(this).closest("fieldset").hasClass("collapsed")) { 15 | // Show 16 | $(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset", [$(this).attr("id")]); 17 | } else { 18 | // Hide 19 | $(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", [$(this).attr("id")]); 20 | } 21 | return false; 22 | }); 23 | }); 24 | })(django.jQuery); 25 | -------------------------------------------------------------------------------- /static/admin/js/collapse.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a(document).ready(function(){a("fieldset.collapse").each(function(c,b){0==a(b).find("div.errors").length&&a(b).addClass("collapsed").find("h2").first().append(' ('+gettext("Show")+")")});a("fieldset.collapse a.collapse-toggle").click(function(){a(this).closest("fieldset").hasClass("collapsed")?a(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset",[a(this).attr("id")]):a(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", 2 | [a(this).attr("id")]);return!1})})})(django.jQuery); 3 | -------------------------------------------------------------------------------- /static/admin/js/inlines.min.js: -------------------------------------------------------------------------------- 1 | (function(b){b.fn.formset=function(d){var a=b.extend({},b.fn.formset.defaults,d),c=b(this),d=c.parent(),i=function(a,e,g){var d=RegExp("("+e+"-(\\d+|__prefix__))"),e=e+"-"+g;b(a).prop("for")&&b(a).prop("for",b(a).prop("for").replace(d,e));a.id&&(a.id=a.id.replace(d,e));a.name&&(a.name=a.name.replace(d,e))},f=b("#id_"+a.prefix+"-TOTAL_FORMS").prop("autocomplete","off"),g=parseInt(f.val(),10),e=b("#id_"+a.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off"),f=""===e.val()||0'+a.addText+""),h=d.find("tr:last a")):(c.filter(":last").after('"),h=c.filter(":last").next().find("a"));h.click(function(d){d.preventDefault();var f=b("#id_"+a.prefix+"-TOTAL_FORMS"),d=b("#"+a.prefix+ 3 | "-empty"),c=d.clone(true);c.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+g);c.is("tr")?c.children(":last").append('"):c.is("ul")||c.is("ol")?c.append('
  • '+a.deleteText+"
  • "):c.children(":first").append(''+a.deleteText+"");c.find("*").each(function(){i(this, 4 | a.prefix,f.val())});c.insertBefore(b(d));b(f).val(parseInt(f.val(),10)+1);g=g+1;e.val()!==""&&e.val()-f.val()<=0&&h.parent().hide();c.find("a."+a.deleteCssClass).click(function(d){d.preventDefault();d=b(this).parents("."+a.formCssClass);d.remove();g=g-1;a.removed&&a.removed(d);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);(e.val()===""||e.val()-d.length>0)&&h.parent().show();for(var c=0,f=d.length;c 0) { 25 | values.push($(field).val()); 26 | } 27 | }) 28 | field.val(URLify(values.join(' '), maxLength)); 29 | }; 30 | 31 | $(dependencies.join(',')).keyup(populate).change(populate).focus(populate); 32 | }); 33 | }; 34 | })(django.jQuery); 35 | -------------------------------------------------------------------------------- /static/admin/js/prepopulate.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a.fn.prepopulate=function(d,g){return this.each(function(){var b=a(this);b.data("_changed",false);b.change(function(){b.data("_changed",true)});var c=function(){if(b.data("_changed")!=true){var e=[];a.each(d,function(h,f){a(f).val().length>0&&e.push(a(f).val())});b.val(URLify(e.join(" "),g))}};a(d.join(",")).keyup(c).change(c).focus(c)})}})(django.jQuery); 2 | -------------------------------------------------------------------------------- /static/admin/js/timeparse.js: -------------------------------------------------------------------------------- 1 | var timeParsePatterns = [ 2 | // 9 3 | { re: /^\d{1,2}$/i, 4 | handler: function(bits) { 5 | if (bits[0].length == 1) { 6 | return '0' + bits[0] + ':00'; 7 | } else { 8 | return bits[0] + ':00'; 9 | } 10 | } 11 | }, 12 | // 13:00 13 | { re: /^\d{2}[:.]\d{2}$/i, 14 | handler: function(bits) { 15 | return bits[0].replace('.', ':'); 16 | } 17 | }, 18 | // 9:00 19 | { re: /^\d[:.]\d{2}$/i, 20 | handler: function(bits) { 21 | return '0' + bits[0].replace('.', ':'); 22 | } 23 | }, 24 | // 3 am / 3 a.m. / 3am 25 | { re: /^(\d+)\s*([ap])(?:.?m.?)?$/i, 26 | handler: function(bits) { 27 | var hour = parseInt(bits[1]); 28 | if (hour == 12) { 29 | hour = 0; 30 | } 31 | if (bits[2].toLowerCase() == 'p') { 32 | if (hour == 12) { 33 | hour = 0; 34 | } 35 | return (hour + 12) + ':00'; 36 | } else { 37 | if (hour < 10) { 38 | return '0' + hour + ':00'; 39 | } else { 40 | return hour + ':00'; 41 | } 42 | } 43 | } 44 | }, 45 | // 3.30 am / 3:15 a.m. / 3.00am 46 | { re: /^(\d+)[.:](\d{2})\s*([ap]).?m.?$/i, 47 | handler: function(bits) { 48 | var hour = parseInt(bits[1]); 49 | var mins = parseInt(bits[2]); 50 | if (mins < 10) { 51 | mins = '0' + mins; 52 | } 53 | if (hour == 12) { 54 | hour = 0; 55 | } 56 | if (bits[3].toLowerCase() == 'p') { 57 | if (hour == 12) { 58 | hour = 0; 59 | } 60 | return (hour + 12) + ':' + mins; 61 | } else { 62 | if (hour < 10) { 63 | return '0' + hour + ':' + mins; 64 | } else { 65 | return hour + ':' + mins; 66 | } 67 | } 68 | } 69 | }, 70 | // noon 71 | { re: /^no/i, 72 | handler: function(bits) { 73 | return '12:00'; 74 | } 75 | }, 76 | // midnight 77 | { re: /^mid/i, 78 | handler: function(bits) { 79 | return '00:00'; 80 | } 81 | } 82 | ]; 83 | 84 | function parseTimeString(s) { 85 | for (var i = 0; i < timeParsePatterns.length; i++) { 86 | var re = timeParsePatterns[i].re; 87 | var handler = timeParsePatterns[i].handler; 88 | var bits = re.exec(s); 89 | if (bits) { 90 | return handler(bits); 91 | } 92 | } 93 | return s; 94 | } 95 | -------------------------------------------------------------------------------- /static/admin/js/urlify.js: -------------------------------------------------------------------------------- 1 | var LATIN_MAP = { 2 | 'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE', 'Ç': 3 | 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I', 'Î': 'I', 4 | 'Ï': 'I', 'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O', 'Ô': 'O', 'Õ': 'O', 'Ö': 5 | 'O', 'Ő': 'O', 'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U', 'Ü': 'U', 'Ű': 'U', 6 | 'Ý': 'Y', 'Þ': 'TH', 'ß': 'ss', 'à':'a', 'á':'a', 'â': 'a', 'ã': 'a', 'ä': 7 | 'a', 'å': 'a', 'æ': 'ae', 'ç': 'c', 'è': 'e', 'é': 'e', 'ê': 'e', 'ë': 'e', 8 | 'ì': 'i', 'í': 'i', 'î': 'i', 'ï': 'i', 'ð': 'd', 'ñ': 'n', 'ò': 'o', 'ó': 9 | 'o', 'ô': 'o', 'õ': 'o', 'ö': 'o', 'ő': 'o', 'ø': 'o', 'ù': 'u', 'ú': 'u', 10 | 'û': 'u', 'ü': 'u', 'ű': 'u', 'ý': 'y', 'þ': 'th', 'ÿ': 'y' 11 | } 12 | var LATIN_SYMBOLS_MAP = { 13 | '©':'(c)' 14 | } 15 | var GREEK_MAP = { 16 | 'α':'a', 'β':'b', 'γ':'g', 'δ':'d', 'ε':'e', 'ζ':'z', 'η':'h', 'θ':'8', 17 | 'ι':'i', 'κ':'k', 'λ':'l', 'μ':'m', 'ν':'n', 'ξ':'3', 'ο':'o', 'π':'p', 18 | 'ρ':'r', 'σ':'s', 'τ':'t', 'υ':'y', 'φ':'f', 'χ':'x', 'ψ':'ps', 'ω':'w', 19 | 'ά':'a', 'έ':'e', 'ί':'i', 'ό':'o', 'ύ':'y', 'ή':'h', 'ώ':'w', 'ς':'s', 20 | 'ϊ':'i', 'ΰ':'y', 'ϋ':'y', 'ΐ':'i', 21 | 'Α':'A', 'Β':'B', 'Γ':'G', 'Δ':'D', 'Ε':'E', 'Ζ':'Z', 'Η':'H', 'Θ':'8', 22 | 'Ι':'I', 'Κ':'K', 'Λ':'L', 'Μ':'M', 'Ν':'N', 'Ξ':'3', 'Ο':'O', 'Π':'P', 23 | 'Ρ':'R', 'Σ':'S', 'Τ':'T', 'Υ':'Y', 'Φ':'F', 'Χ':'X', 'Ψ':'PS', 'Ω':'W', 24 | 'Ά':'A', 'Έ':'E', 'Ί':'I', 'Ό':'O', 'Ύ':'Y', 'Ή':'H', 'Ώ':'W', 'Ϊ':'I', 25 | 'Ϋ':'Y' 26 | } 27 | var TURKISH_MAP = { 28 | 'ş':'s', 'Ş':'S', 'ı':'i', 'İ':'I', 'ç':'c', 'Ç':'C', 'ü':'u', 'Ü':'U', 29 | 'ö':'o', 'Ö':'O', 'ğ':'g', 'Ğ':'G' 30 | } 31 | var RUSSIAN_MAP = { 32 | 'а':'a', 'б':'b', 'в':'v', 'г':'g', 'д':'d', 'е':'e', 'ё':'yo', 'ж':'zh', 33 | 'з':'z', 'и':'i', 'й':'j', 'к':'k', 'л':'l', 'м':'m', 'н':'n', 'о':'o', 34 | 'п':'p', 'р':'r', 'с':'s', 'т':'t', 'у':'u', 'ф':'f', 'х':'h', 'ц':'c', 35 | 'ч':'ch', 'ш':'sh', 'щ':'sh', 'ъ':'', 'ы':'y', 'ь':'', 'э':'e', 'ю':'yu', 36 | 'я':'ya', 37 | 'А':'A', 'Б':'B', 'В':'V', 'Г':'G', 'Д':'D', 'Е':'E', 'Ё':'Yo', 'Ж':'Zh', 38 | 'З':'Z', 'И':'I', 'Й':'J', 'К':'K', 'Л':'L', 'М':'M', 'Н':'N', 'О':'O', 39 | 'П':'P', 'Р':'R', 'С':'S', 'Т':'T', 'У':'U', 'Ф':'F', 'Х':'H', 'Ц':'C', 40 | 'Ч':'Ch', 'Ш':'Sh', 'Щ':'Sh', 'Ъ':'', 'Ы':'Y', 'Ь':'', 'Э':'E', 'Ю':'Yu', 41 | 'Я':'Ya' 42 | } 43 | var UKRAINIAN_MAP = { 44 | 'Є':'Ye', 'І':'I', 'Ї':'Yi', 'Ґ':'G', 'є':'ye', 'і':'i', 'ї':'yi', 'ґ':'g' 45 | } 46 | var CZECH_MAP = { 47 | 'č':'c', 'ď':'d', 'ě':'e', 'ň': 'n', 'ř':'r', 'š':'s', 'ť':'t', 'ů':'u', 48 | 'ž':'z', 'Č':'C', 'Ď':'D', 'Ě':'E', 'Ň': 'N', 'Ř':'R', 'Š':'S', 'Ť':'T', 49 | 'Ů':'U', 'Ž':'Z' 50 | } 51 | 52 | var POLISH_MAP = { 53 | 'ą':'a', 'ć':'c', 'ę':'e', 'ł':'l', 'ń':'n', 'ó':'o', 'ś':'s', 'ź':'z', 54 | 'ż':'z', 'Ą':'A', 'Ć':'C', 'Ę':'e', 'Ł':'L', 'Ń':'N', 'Ó':'o', 'Ś':'S', 55 | 'Ź':'Z', 'Ż':'Z' 56 | } 57 | 58 | var LATVIAN_MAP = { 59 | 'ā':'a', 'č':'c', 'ē':'e', 'ģ':'g', 'ī':'i', 'ķ':'k', 'ļ':'l', 'ņ':'n', 60 | 'š':'s', 'ū':'u', 'ž':'z', 'Ā':'A', 'Č':'C', 'Ē':'E', 'Ģ':'G', 'Ī':'i', 61 | 'Ķ':'k', 'Ļ':'L', 'Ņ':'N', 'Š':'S', 'Ū':'u', 'Ž':'Z' 62 | } 63 | 64 | var ALL_DOWNCODE_MAPS=new Array() 65 | ALL_DOWNCODE_MAPS[0]=LATIN_MAP 66 | ALL_DOWNCODE_MAPS[1]=LATIN_SYMBOLS_MAP 67 | ALL_DOWNCODE_MAPS[2]=GREEK_MAP 68 | ALL_DOWNCODE_MAPS[3]=TURKISH_MAP 69 | ALL_DOWNCODE_MAPS[4]=RUSSIAN_MAP 70 | ALL_DOWNCODE_MAPS[5]=UKRAINIAN_MAP 71 | ALL_DOWNCODE_MAPS[6]=CZECH_MAP 72 | ALL_DOWNCODE_MAPS[7]=POLISH_MAP 73 | ALL_DOWNCODE_MAPS[8]=LATVIAN_MAP 74 | 75 | var Downcoder = new Object(); 76 | Downcoder.Initialize = function() 77 | { 78 | if (Downcoder.map) // already made 79 | return ; 80 | Downcoder.map ={} 81 | Downcoder.chars = '' ; 82 | for(var i in ALL_DOWNCODE_MAPS) 83 | { 84 | var lookup = ALL_DOWNCODE_MAPS[i] 85 | for (var c in lookup) 86 | { 87 | Downcoder.map[c] = lookup[c] ; 88 | Downcoder.chars += c ; 89 | } 90 | } 91 | Downcoder.regex = new RegExp('[' + Downcoder.chars + ']|[^' + Downcoder.chars + ']+','g') ; 92 | } 93 | 94 | downcode= function( slug ) 95 | { 96 | Downcoder.Initialize() ; 97 | var downcoded ="" 98 | var pieces = slug.match(Downcoder.regex); 99 | if(pieces) 100 | { 101 | for (var i = 0 ; i < pieces.length ; i++) 102 | { 103 | if (pieces[i].length == 1) 104 | { 105 | var mapped = Downcoder.map[pieces[i]] ; 106 | if (mapped != null) 107 | { 108 | downcoded+=mapped; 109 | continue ; 110 | } 111 | } 112 | downcoded+=pieces[i]; 113 | } 114 | } 115 | else 116 | { 117 | downcoded = slug; 118 | } 119 | return downcoded; 120 | } 121 | 122 | 123 | function URLify(s, num_chars) { 124 | // changes, e.g., "Petty theft" to "petty_theft" 125 | // remove all these words from the string before urlifying 126 | s = downcode(s); 127 | removelist = ["a", "an", "as", "at", "before", "but", "by", "for", "from", 128 | "is", "in", "into", "like", "of", "off", "on", "onto", "per", 129 | "since", "than", "the", "this", "that", "to", "up", "via", 130 | "with"]; 131 | r = new RegExp('\\b(' + removelist.join('|') + ')\\b', 'gi'); 132 | s = s.replace(r, ''); 133 | // if downcode doesn't hit, the char will be stripped here 134 | s = s.replace(/[^-\w\s]/g, ''); // remove unneeded chars 135 | s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces 136 | s = s.replace(/[-\s]+/g, '-'); // convert spaces to hyphens 137 | s = s.toLowerCase(); // convert to lowercase 138 | return s.substring(0, num_chars);// trim to first num_chars chars 139 | } 140 | 141 | -------------------------------------------------------------------------------- /static/css/datatables-bootstrap.css: -------------------------------------------------------------------------------- 1 | 2 | div.dataTables_length label { 3 | float: left; 4 | text-align: left; 5 | } 6 | 7 | div.dataTables_length select { 8 | width: 75px; 9 | } 10 | 11 | div.dataTables_filter label { 12 | float: right; 13 | } 14 | 15 | div.dataTables_info { 16 | padding-top: 8px; 17 | } 18 | 19 | div.dataTables_paginate { 20 | float: right; 21 | margin: 0; 22 | } 23 | 24 | table.table { 25 | clear: both; 26 | margin-bottom: 6px !important; 27 | } 28 | 29 | table.table thead .sorting, 30 | table.table thead .sorting_asc, 31 | table.table thead .sorting_desc, 32 | table.table thead .sorting_asc_disabled, 33 | table.table thead .sorting_desc_disabled { 34 | cursor: pointer; 35 | *cursor: hand; 36 | } 37 | 38 | table.table thead .sorting { background: none center right; } 39 | table.table thead .sorting_asc { background: url('../img/arrow-up.gif') no-repeat center right; } 40 | table.table thead .sorting_desc { background: url('../img/arrow-down.gif') no-repeat center right; } 41 | 42 | table.table thead .sorting_asc_disabled { background: none no-repeat center right; } 43 | table.table thead .sorting_desc_disabled { background: none no-repeat center right; } 44 | 45 | table.dataTable th:active { 46 | outline: none; 47 | } -------------------------------------------------------------------------------- /static/css/site.css: -------------------------------------------------------------------------------- 1 | .error, .error a { 2 | color: red; 3 | font-weight: bold; 4 | text-decoration: none; 5 | } 6 | .warning, .warning a { 7 | color: #c70; 8 | font-weight: bold; 9 | text-decoration: none; 10 | } 11 | .error a:hover, .warning a:hover { 12 | text-decoration: underline; 13 | } 14 | -------------------------------------------------------------------------------- /static/css/smoothness/images/animated-overlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/css/smoothness/images/animated-overlay.gif -------------------------------------------------------------------------------- /static/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /static/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /static/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png -------------------------------------------------------------------------------- /static/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /static/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /static/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png -------------------------------------------------------------------------------- /static/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /static/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png -------------------------------------------------------------------------------- /static/css/smoothness/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/css/smoothness/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /static/css/smoothness/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/css/smoothness/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /static/css/smoothness/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/css/smoothness/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /static/css/smoothness/images/ui-icons_888888_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/css/smoothness/images/ui-icons_888888_256x240.png -------------------------------------------------------------------------------- /static/css/smoothness/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/css/smoothness/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /static/img/arrow-down.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/img/arrow-down.gif -------------------------------------------------------------------------------- /static/img/arrow-up.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/img/arrow-up.gif -------------------------------------------------------------------------------- /static/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /static/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /static/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cabal95/managedmacadmin/e0cd4afc717926c1c2d78e9cfe960840829e72a7/static/img/loading.gif -------------------------------------------------------------------------------- /static/js/datatables-bootstrap.js: -------------------------------------------------------------------------------- 1 | /* Default class modification */ 2 | $.extend( $.fn.dataTableExt.oStdClasses, { 3 | "sWrapper": "dataTables_wrapper form-inline" 4 | } ); 5 | 6 | /* API method to get paging information */ 7 | $.fn.dataTableExt.oApi.fnPagingInfo = function ( oSettings ) 8 | { 9 | return { 10 | "iStart": oSettings._iDisplayStart, 11 | "iEnd": oSettings.fnDisplayEnd(), 12 | "iLength": oSettings._iDisplayLength, 13 | "iTotal": oSettings.fnRecordsTotal(), 14 | "iFilteredTotal": oSettings.fnRecordsDisplay(), 15 | "iPage": Math.ceil( oSettings._iDisplayStart / oSettings._iDisplayLength ), 16 | "iTotalPages": Math.ceil( oSettings.fnRecordsDisplay() / oSettings._iDisplayLength ) 17 | }; 18 | } 19 | 20 | /* Bootstrap style pagination control */ 21 | $.extend( $.fn.dataTableExt.oPagination, { 22 | "bootstrap": { 23 | "fnInit": function( oSettings, nPaging, fnDraw ) { 24 | var oLang = oSettings.oLanguage.oPaginate; 25 | var fnClickHandler = function ( e ) { 26 | e.preventDefault(); 27 | if ( oSettings.oApi._fnPageChange(oSettings, e.data.action) ) { 28 | fnDraw( oSettings ); 29 | } 30 | }; 31 | 32 | $(nPaging).addClass('pagination').append( 33 | '' 37 | ); 38 | var els = $('a', nPaging); 39 | $(els[0]).bind( 'click.DT', { action: "previous" }, fnClickHandler ); 40 | $(els[1]).bind( 'click.DT', { action: "next" }, fnClickHandler ); 41 | }, 42 | 43 | "fnUpdate": function ( oSettings, fnDraw ) { 44 | var iListLength = 5; 45 | var oPaging = oSettings.oInstance.fnPagingInfo(); 46 | var an = oSettings.aanFeatures.p; 47 | var i, j, sClass, iStart, iEnd, iHalf=Math.floor(iListLength/2); 48 | 49 | if ( oPaging.iTotalPages < iListLength) { 50 | iStart = 1; 51 | iEnd = oPaging.iTotalPages; 52 | } 53 | else if ( oPaging.iPage <= iHalf ) { 54 | iStart = 1; 55 | iEnd = iListLength; 56 | } else if ( oPaging.iPage >= (oPaging.iTotalPages-iHalf) ) { 57 | iStart = oPaging.iTotalPages - iListLength + 1; 58 | iEnd = oPaging.iTotalPages; 59 | } else { 60 | iStart = oPaging.iPage - iHalf + 1; 61 | iEnd = iStart + iListLength - 1; 62 | } 63 | 64 | for ( i=0, iLen=an.length ; i'+j+'') 72 | .insertBefore( $('li:last', an[i])[0] ) 73 | .bind('click', function (e) { 74 | e.preventDefault(); 75 | oSettings._iDisplayStart = (parseInt($('a', this).text(),10)-1) * oPaging.iLength; 76 | fnDraw( oSettings ); 77 | } ); 78 | } 79 | 80 | // Add / remove disabled classes from the static elements 81 | if ( oPaging.iPage === 0 ) { 82 | $('li:first', an[i]).addClass('disabled'); 83 | } else { 84 | $('li:first', an[i]).removeClass('disabled'); 85 | } 86 | 87 | if ( oPaging.iPage === oPaging.iTotalPages-1 || oPaging.iTotalPages === 0 ) { 88 | $('li:last', an[i]).addClass('disabled'); 89 | } else { 90 | $('li:last', an[i]).removeClass('disabled'); 91 | } 92 | } 93 | } 94 | } 95 | } ); -------------------------------------------------------------------------------- /static/mdm/jquery.plist.css: -------------------------------------------------------------------------------- 1 | table.plist { 2 | width: 100%; 3 | border-spacing: 0px; 4 | border-collapse: separate; 5 | } 6 | 7 | table.plist > tbody > tr > td.plist-key { 8 | min-width: 100px; 9 | white-space: nowrap; 10 | } 11 | 12 | table.plist > tbody > tr > td.plist-type { 13 | padding-left: 10px; 14 | padding-right: 10px; 15 | } 16 | 17 | table.plist > tbody > tr > td.plist-value { 18 | min-width: 350px; 19 | } 20 | 21 | table.plist > tbody > tr > td.plist-key > input { 22 | width: 200px; 23 | margin-top: 2px; 24 | margin-bottom: 2px; 25 | } 26 | 27 | table.plist > tbody > tr > td.plist-type > select { 28 | margin-top: 2px; 29 | margin-bottom: 2px; 30 | } 31 | 32 | table.plist > tbody > tr > td.plist-value > input { 33 | width: 300px; 34 | margin-top: 2px; 35 | margin-bottom: 2px; 36 | } 37 | 38 | table.plist > tbody > tr > td.plist-key > span.plist-indent { 39 | display: inline-block; 40 | margin: 0px; 41 | padding: 0px; 42 | } 43 | 44 | table.plist > tbody > tr > td.plist-value > span.plist-value-icons { 45 | display: inline-block; 46 | float: right; 47 | margin-left: 5px; 48 | margin-right: 5px; 49 | } 50 | 51 | table.plist > tbody > tr > td.plist-value > span.plist-value-icons > i { 52 | margin-left: 5px; 53 | cursor: pointer; 54 | } 55 | 56 | table.plist > tbody > tr > td.plist-key > input { 57 | width: 175px; 58 | } 59 | 60 | table.plist > tbody > tr.plist-indentlevel-0 { background-color: #d9edf7; } 61 | table.plist > tbody > tr.plist-indentlevel-1 { background-color: #edf7d9; } 62 | table.plist > tbody > tr.plist-indentlevel-2 { background-color: #f7d9c2; } 63 | table.plist > tbody > tr.plist-indentlevel-3 { background-color: #d99292; } 64 | table.plist > tbody > tr.plist-indentlevel-4 { background-color: #92d992; } 65 | -------------------------------------------------------------------------------- /static/mdm/mdm.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | $.fn.editable = function(fnSave, fnValidate, fnFormat) { 3 | var content = $('').text($(this).text()); 4 | var edit = $(''); 5 | 6 | if (typeof(fnSave) === 'undefined') 7 | fnSave = function (object, value, done) { done(true); }; 8 | if (typeof(fnValidate) === 'undefined') 9 | fnValidate = function (object, value) { 10 | if ($(object).data('required') == true && value.length == 0) { 11 | alert('Value is required.'); 12 | return false; 13 | } 14 | return true; 15 | }; 16 | 17 | if ($(content).text().length == 0) 18 | $(this).addClass('empty'); 19 | else 20 | $(this).removeClass('empty'); 21 | 22 | $(this).text(''); 23 | $(this).append(content).append(edit); 24 | 25 | $(edit).click(function () { 26 | var src = $(this).prevAll('.editableContent').first(); 27 | var tb = $(''); 28 | var save = $(''); 29 | var cancel = $(''); 30 | var width = $(src).width(); 31 | 32 | $(tb).val($(src).text()); 33 | $(this).parent().append(tb).append(save).append(cancel); 34 | $(src).hide(); 35 | $(this).css('display', 'none'); 36 | width -= $(save).outerWidth(); 37 | width -= $(cancel).outerWidth(); 38 | if (width < 100) 39 | width = 100; 40 | $(tb).css('width', width + 'px'); 41 | $(tb).select(); 42 | $(tb).focus(); 43 | 44 | $(tb).keyup(function (e) { 45 | if (e.keyCode == 13) { 46 | e.preventDefault(); 47 | $(save).click(); 48 | } 49 | if (e.keyCode == 27) { 50 | e.preventDefault(); 51 | $(cancel).click(); 52 | } 53 | }); 54 | 55 | $(save).click(function () { 56 | var tb = $(this).prevAll('.editing').first(); 57 | var save = $(this); 58 | var cancel = $(this).next('.cancelButton').first(); 59 | var orig = $(this).prevAll('.editableContent').first(); 60 | var container = $(this).parent('.editable').first(); 61 | var icon = $(this).prevAll('.fa-edit'); 62 | var value = $(tb).val(); 63 | 64 | var done = function (ok) { 65 | if (ok === true) { 66 | $(orig).text(value); 67 | $(orig).show(); 68 | $(icon).css('display', ''); 69 | $(tb).remove(); 70 | $(save).remove(); 71 | $(cancel).remove(); 72 | 73 | if (value.length == 0) 74 | $(container).addClass('empty'); 75 | else 76 | $(container).removeClass('empty'); 77 | } 78 | else 79 | $(save).removeClass('active'); 80 | }; 81 | 82 | if (fnValidate($(orig).parent(), value) === false) { 83 | $(tb).focus(); 84 | return; 85 | } 86 | 87 | $(save).addClass('active'); 88 | fnSave($(orig).parent(), value, done); 89 | }); 90 | 91 | $(cancel).click(function () { 92 | var tb = $(this).prevAll('.editing').first(); 93 | var save = $(this).prevAll('.saveButton').first(); 94 | var cancel = $(this); 95 | var orig = $(this).prevAll('.editableContent').first(); 96 | var icon = $(this).prevAll('.fa-edit'); 97 | 98 | $(orig).show(); 99 | $(icon).css('display', ''); 100 | $(tb).remove(); 101 | $(save).remove(); 102 | $(cancel).remove(); 103 | }); 104 | }); 105 | }; 106 | 107 | /* 108 | * Selected elements become fancy two-stage delete buttons. 109 | */ 110 | $.fn.deleteButton = function(fnDelete) { 111 | if ($(this).length > 1) { 112 | $(this).each(function() { $(this).deleteButton(fnDelete); }); 113 | return $(this); 114 | } 115 | 116 | var expandButton = function(e) { 117 | var btn = $(this); 118 | var origContent = $(btn).html(); 119 | var origWidth = $(btn).outerWidth(), origHeight = $(btn).outerHeight(); 120 | var width, height; 121 | 122 | $(btn).unbind("click.ExpandDelete"); 123 | $(btn).bind("click.DoDelete", fnDelete); 124 | $(btn).html(origContent + ' Delete'); 125 | width = $(btn).outerWidth(); 126 | height = $(btn).outerHeight(); 127 | $(btn).html(origContent); 128 | $(btn).css('visibility', 'visible'); 129 | $(btn).css('text-align', 'left'); 130 | $(btn).css('width', origWidth); 131 | $(btn).css('height', origHeight); 132 | $(btn).animate({ width: width, height: height }, 100, function() { 133 | $(btn).html(origContent + ' Delete'); 134 | }); 135 | 136 | e.stopPropagation(); 137 | 138 | $(document).bind("click.ClearDelete", function (e) { 139 | if ($(btn).has(e.target).length > 0 || e.target == $(btn).get(0)) 140 | return; 141 | $(btn).unbind("click.DoDelete"); 142 | $(document).unbind("click.ClearDelete"); 143 | $(btn).html($(btn).html().replace(' Delete', '')); 144 | $(btn).animate({ width: origWidth, height: origHeight }, 100, function() { 145 | $(btn).css('width', '').css('height', ''); 146 | $(btn).css('visibility', ''); 147 | $(this).bind("click.ExpandDelete", expandButton); 148 | }); 149 | }); 150 | }; 151 | 152 | $(this).bind("click.ExpandDelete", expandButton); 153 | }; 154 | })(jQuery); 155 | -------------------------------------------------------------------------------- /static/mdm/style.css: -------------------------------------------------------------------------------- 1 | .highlight { 2 | padding: 9px 14px; 3 | margin-bottom: 14px; 4 | background-color: #f7f7f9; 5 | border: 1px solid #e1e1e8; 6 | border-radius: 4px; 7 | } 8 | 9 | div.actions a { 10 | margin-bottom: 5px; 11 | } 12 | 13 | .spinner { 14 | display: inline-block; 15 | opacity: 0; 16 | max-width: 0; 17 | 18 | -webkit-transition: opacity 0.25s, max-width 0.45s; 19 | -moz-transition: opacity 0.25s, max-width 0.45s; 20 | -o-transition: opacity 0.25s, max-width 0.45s; 21 | transition: opacity 0.25s, max-width 0.45s; /* Duration fixed since we animate additional hidden width */ 22 | } 23 | 24 | .has-spinner.active .spinner { 25 | opacity: 1; 26 | max-width: 50px; /* More than it will ever come, notice that this affects on animation duration */ 27 | } 28 | 29 | .progress { 30 | margin-bottom: 0px; 31 | } 32 | 33 | .editable > i.fa-edit { opacity: 0; padding-left: 5px; padding-right: 5px; } 34 | .editable.empty > i.fa-edit { opacity: 1; } 35 | .editable:hover > i.fa-edit { opacity: 1; cursor: pointer; } 36 | .editable > input.editing { margin-bottom: 0px; } 37 | .editable > button { margin-left: 5px; } 38 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% load static %} 7 | {% load url from future %} 8 | 11 | 14 | 19 | 22 | 25 | 28 | 29 | {% block extra_style %}{% endblock %} 30 | 31 | 34 | 37 | 40 | 43 | 46 | 47 | {% block java_script %}{% endblock %} 48 | Managed Mac Admin 49 | 50 | {% block body_tag %}{% endblock %} 51 | 52 | {% block main_menu %} 53 | 98 | {% endblock %} 99 | 100 |
    101 |
    102 | {% block content %}{% endblock %} 103 |
    104 |
    105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load url from future %} 3 | 4 | {% block content %} 5 | {% if user.is_authenticated %} 6 |

    Welcome, {{ user.username }}.

    7 |

    8 | {% else %} 9 | {% if form.errors %} 10 |

    Your username and password didn't match. Please try again.

    11 | {% else %} 12 |

    Please log in.

    13 | {% endif %} 14 | 15 |
    16 | {% csrf_token %} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
    {{ form.username.label_tag }}{{ form.username }}
    {{ form.password.label_tag }}{{ form.password }}
    27 | 28 | 29 | 30 |
    31 | {% endif %} 32 | {% endblock %} 33 | 34 | --------------------------------------------------------------------------------