Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
Gelencsér Szabolcs
/
circlestack
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
0963fa06
authored
Sep 03, 2014
by
Őry Máté
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'custom-connect-command' into 'master'
Custom Connect Command
parents
7a3602eb
09959441
Show whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
939 additions
and
27 deletions
+939
-27
circle/dashboard/admin.py
+6
-2
circle/dashboard/forms.py
+19
-1
circle/dashboard/migrations/0015_auto__add_connectcommand.py
+279
-0
circle/dashboard/migrations/0016_auto__del_field_connectcommand_application__add_field_connectcommand_n.py
+282
-0
circle/dashboard/models.py
+40
-0
circle/dashboard/static/dashboard/dashboard.css
+9
-3
circle/dashboard/static/dashboard/vm-details.js
+10
-9
circle/dashboard/tables.py
+38
-1
circle/dashboard/templates/dashboard/connect-command-create.html
+44
-0
circle/dashboard/templates/dashboard/connect-command-edit.html
+44
-0
circle/dashboard/templates/dashboard/connect-command-list/column-command-actions.html
+7
-0
circle/dashboard/templates/dashboard/profile_form.html
+16
-0
circle/dashboard/templates/dashboard/vm-detail.html
+19
-5
circle/dashboard/urls.py
+11
-0
circle/dashboard/validators.py
+26
-0
circle/dashboard/views.py
+89
-6
No files found.
circle/dashboard/admin.py
View file @
0963fa06
...
@@ -21,18 +21,22 @@ from django import contrib
...
@@ -21,18 +21,22 @@ from django import contrib
from
django.contrib.auth.admin
import
UserAdmin
,
GroupAdmin
from
django.contrib.auth.admin
import
UserAdmin
,
GroupAdmin
from
django.contrib.auth.models
import
User
,
Group
from
django.contrib.auth.models
import
User
,
Group
from
dashboard.models
import
Profile
,
GroupProfile
from
dashboard.models
import
Profile
,
GroupProfile
,
ConnectCommand
class
ProfileInline
(
contrib
.
admin
.
TabularInline
):
class
ProfileInline
(
contrib
.
admin
.
TabularInline
):
model
=
Profile
model
=
Profile
class
CommandInline
(
contrib
.
admin
.
TabularInline
):
model
=
ConnectCommand
class
GroupProfileInline
(
contrib
.
admin
.
TabularInline
):
class
GroupProfileInline
(
contrib
.
admin
.
TabularInline
):
model
=
GroupProfile
model
=
GroupProfile
UserAdmin
.
inlines
=
(
ProfileInline
,
)
UserAdmin
.
inlines
=
(
ProfileInline
,
CommandInline
,
)
GroupAdmin
.
inlines
=
(
GroupProfileInline
,
)
GroupAdmin
.
inlines
=
(
GroupProfileInline
,
)
contrib
.
admin
.
site
.
unregister
(
User
)
contrib
.
admin
.
site
.
unregister
(
User
)
...
...
circle/dashboard/forms.py
View file @
0963fa06
...
@@ -54,7 +54,9 @@ from .models import Profile, GroupProfile
...
@@ -54,7 +54,9 @@ from .models import Profile, GroupProfile
from
circle.settings.base
import
LANGUAGES
,
MAX_NODE_RAM
from
circle.settings.base
import
LANGUAGES
,
MAX_NODE_RAM
from
django.utils.translation
import
string_concat
from
django.utils.translation
import
string_concat
from
.virtvalidator
import
domain_validator
from
.validators
import
domain_validator
from
dashboard.models
import
ConnectCommand
LANGUAGES_WITH_CODE
=
((
l
[
0
],
string_concat
(
l
[
1
],
" ("
,
l
[
0
],
")"
))
LANGUAGES_WITH_CODE
=
((
l
[
0
],
string_concat
(
l
[
1
],
" ("
,
l
[
0
],
")"
))
for
l
in
LANGUAGES
)
for
l
in
LANGUAGES
)
...
@@ -1064,6 +1066,22 @@ class UserKeyForm(forms.ModelForm):
...
@@ -1064,6 +1066,22 @@ class UserKeyForm(forms.ModelForm):
return
super
(
UserKeyForm
,
self
)
.
clean
()
return
super
(
UserKeyForm
,
self
)
.
clean
()
class
ConnectCommandForm
(
forms
.
ModelForm
):
class
Meta
:
fields
=
(
'name'
,
'access_method'
,
'template'
)
model
=
ConnectCommand
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
user
=
kwargs
.
pop
(
"user"
)
super
(
ConnectCommandForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
def
clean
(
self
):
if
self
.
user
:
self
.
instance
.
user
=
self
.
user
return
super
(
ConnectCommandForm
,
self
)
.
clean
()
class
TraitsForm
(
forms
.
ModelForm
):
class
TraitsForm
(
forms
.
ModelForm
):
class
Meta
:
class
Meta
:
...
...
circle/dashboard/migrations/0015_auto__add_connectcommand.py
0 → 100644
View file @
0963fa06
# -*- coding: utf-8 -*-
from
south.utils
import
datetime_utils
as
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
from
django.db
import
models
class
Migration
(
SchemaMigration
):
def
forwards
(
self
,
orm
):
# Adding model 'ConnectCommand'
db
.
create_table
(
u'dashboard_connectcommand'
,
(
(
u'id'
,
self
.
gf
(
'django.db.models.fields.AutoField'
)(
primary_key
=
True
)),
(
'user'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
related_name
=
'command_set'
,
to
=
orm
[
'auth.User'
])),
(
'access_method'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
10
)),
(
'application'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
'128'
)),
(
'template'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
256
,
null
=
True
,
blank
=
True
)),
))
db
.
send_create_signal
(
u'dashboard'
,
[
'ConnectCommand'
])
def
backwards
(
self
,
orm
):
# Deleting model 'ConnectCommand'
db
.
delete_table
(
u'dashboard_connectcommand'
)
models
=
{
u'auth.group'
:
{
'Meta'
:
{
'object_name'
:
'Group'
},
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'80'
}),
'permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
u"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
})
},
u'auth.permission'
:
{
'Meta'
:
{
'ordering'
:
"(u'content_type__app_label', u'content_type__model', u'codename')"
,
'unique_together'
:
"((u'content_type', u'codename'),)"
,
'object_name'
:
'Permission'
},
'codename'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'content_type'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['contenttypes.ContentType']"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
})
},
u'auth.user'
:
{
'Meta'
:
{
'object_name'
:
'User'
},
'date_joined'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'email'
:
(
'django.db.models.fields.EmailField'
,
[],
{
'max_length'
:
'75'
,
'blank'
:
'True'
}),
'first_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'groups'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'symmetrical'
:
'False'
,
'related_name'
:
"u'user_set'"
,
'blank'
:
'True'
,
'to'
:
u"orm['auth.Group']"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'is_staff'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'is_superuser'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'last_login'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'last_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'password'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
}),
'user_permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'symmetrical'
:
'False'
,
'related_name'
:
"u'user_set'"
,
'blank'
:
'True'
,
'to'
:
u"orm['auth.Permission']"
}),
'username'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'30'
})
},
u'contenttypes.contenttype'
:
{
'Meta'
:
{
'ordering'
:
"('name',)"
,
'unique_together'
:
"(('app_label', 'model'),)"
,
'object_name'
:
'ContentType'
,
'db_table'
:
"'django_content_type'"
},
'app_label'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'model'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
})
},
u'dashboard.connectcommand'
:
{
'Meta'
:
{
'object_name'
:
'ConnectCommand'
},
'access_method'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'10'
}),
'application'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
"'128'"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'template'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'256'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'command_set'"
,
'to'
:
u"orm['auth.User']"
})
},
u'dashboard.favourite'
:
{
'Meta'
:
{
'object_name'
:
'Favourite'
},
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'instance'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['vm.Instance']"
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
})
},
u'dashboard.futuremember'
:
{
'Meta'
:
{
'unique_together'
:
"(('org_id', 'group'),)"
,
'object_name'
:
'FutureMember'
},
'group'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.Group']"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'org_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'64'
})
},
u'dashboard.groupprofile'
:
{
'Meta'
:
{
'object_name'
:
'GroupProfile'
},
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'group'
:
(
'django.db.models.fields.related.OneToOneField'
,
[],
{
'to'
:
u"orm['auth.Group']"
,
'unique'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'org_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'64'
,
'unique'
:
'True'
,
'null'
:
'True'
,
'blank'
:
'True'
})
},
u'dashboard.notification'
:
{
'Meta'
:
{
'ordering'
:
"['-created']"
,
'object_name'
:
'Notification'
},
'created'
:
(
'model_utils.fields.AutoCreatedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'message_data'
:
(
'jsonfield.fields.JSONField'
,
[],
{
'null'
:
'True'
}),
'modified'
:
(
'model_utils.fields.AutoLastModifiedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'status'
:
(
'model_utils.fields.StatusField'
,
[],
{
'default'
:
"'new'"
,
'max_length'
:
'100'
,
u'no_check_for_status'
:
'True'
}),
'subject_data'
:
(
'jsonfield.fields.JSONField'
,
[],
{
'null'
:
'True'
}),
'to'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
}),
'valid_until'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'None'
,
'null'
:
'True'
})
},
u'dashboard.profile'
:
{
'Meta'
:
{
'object_name'
:
'Profile'
},
'disk_quota'
:
(
'sizefield.models.FileSizeField'
,
[],
{
'default'
:
'2147483648'
}),
'email_notifications'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'instance_limit'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'5'
}),
'org_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'64'
,
'unique'
:
'True'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'preferred_language'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'en'"
,
'max_length'
:
'32'
}),
'smb_password'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"u'uUmt7R9peX'"
,
'max_length'
:
'20'
}),
'use_gravatar'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'user'
:
(
'django.db.models.fields.related.OneToOneField'
,
[],
{
'to'
:
u"orm['auth.User']"
,
'unique'
:
'True'
})
},
u'firewall.domain'
:
{
'Meta'
:
{
'object_name'
:
'Domain'
},
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
}),
'ttl'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'600'
})
},
u'firewall.group'
:
{
'Meta'
:
{
'object_name'
:
'Group'
},
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'20'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
,
'null'
:
'True'
,
'blank'
:
'True'
})
},
u'firewall.host'
:
{
'Meta'
:
{
'ordering'
:
"('normalized_hostname', 'vlan')"
,
'unique_together'
:
"(('hostname', 'vlan'),)"
,
'object_name'
:
'Host'
},
'comment'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'external_ipv4'
:
(
'firewall.fields.IPAddressField'
,
[],
{
'max_length'
:
'100'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'groups'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'symmetrical'
:
'False'
,
'to'
:
u"orm['firewall.Group']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'hostname'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'ipv4'
:
(
'firewall.fields.IPAddressField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'100'
}),
'ipv6'
:
(
'firewall.fields.IPAddressField'
,
[],
{
'max_length'
:
'100'
,
'unique'
:
'True'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'location'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'mac'
:
(
'firewall.fields.MACAddressField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'17'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'normalized_hostname'
:
(
'common.models.HumanSortField'
,
[],
{
'default'
:
"''"
,
'maximum_number_length'
:
'4'
,
'max_length'
:
'80'
,
'monitor'
:
"'hostname'"
,
'blank'
:
'True'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
}),
'reverse'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'shared_ip'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'vlan'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['firewall.Vlan']"
})
},
u'firewall.vlan'
:
{
'Meta'
:
{
'object_name'
:
'Vlan'
},
'comment'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'dhcp_pool'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'domain'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['firewall.Domain']"
}),
'host_ipv6_prefixlen'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'112'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'ipv6_template'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"'2001:738:2001:4031:
%(b)
d:
%(c)
d:
%(d)
d:0'"
}),
'managed'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'20'
}),
'network4'
:
(
'firewall.fields.IPNetworkField'
,
[],
{
'max_length'
:
'100'
}),
'network6'
:
(
'firewall.fields.IPNetworkField'
,
[],
{
'max_length'
:
'100'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'network_type'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'portforward'"
,
'max_length'
:
'20'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'reverse_domain'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"'
%(d)
d.
%(c)
d.
%(b)
d.
%(a)
d.in-addr.arpa'"
}),
'snat_ip'
:
(
'django.db.models.fields.GenericIPAddressField'
,
[],
{
'max_length'
:
'39'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'snat_to'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'symmetrical'
:
'False'
,
'to'
:
u"orm['firewall.Vlan']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'vid'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'unique'
:
'True'
})
},
u'storage.datastore'
:
{
'Meta'
:
{
'ordering'
:
"[u'name']"
,
'object_name'
:
'DataStore'
},
'hostname'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'40'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'100'
}),
'path'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'200'
})
},
u'storage.disk'
:
{
'Meta'
:
{
'ordering'
:
"[u'name']"
,
'object_name'
:
'Disk'
},
'base'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'blank'
:
'True'
,
'related_name'
:
"u'derivatives'"
,
'null'
:
'True'
,
'to'
:
u"orm['storage.Disk']"
}),
'created'
:
(
'model_utils.fields.AutoCreatedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'datastore'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['storage.DataStore']"
}),
'destroyed'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'None'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'dev_num'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"u'a'"
,
'max_length'
:
'1'
}),
'filename'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'256'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_ready'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'modified'
:
(
'model_utils.fields.AutoLastModifiedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
,
'blank'
:
'True'
}),
'size'
:
(
'sizefield.models.FileSizeField'
,
[],
{
'default'
:
'None'
,
'null'
:
'True'
}),
'type'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'10'
})
},
u'vm.instance'
:
{
'Meta'
:
{
'ordering'
:
"(u'pk',)"
,
'object_name'
:
'Instance'
},
'access_method'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'10'
}),
'active_since'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'arch'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'10'
}),
'boot_menu'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'created'
:
(
'model_utils.fields.AutoCreatedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'destroyed_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'disks'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'related_name'
:
"u'instance_set'"
,
'symmetrical'
:
'False'
,
'to'
:
u"orm['storage.Disk']"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_base'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'lease'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['vm.Lease']"
}),
'max_ram_size'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'modified'
:
(
'model_utils.fields.AutoLastModifiedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
,
'blank'
:
'True'
}),
'node'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'blank'
:
'True'
,
'related_name'
:
"u'instance_set'"
,
'null'
:
'True'
,
'to'
:
u"orm['vm.Node']"
}),
'num_cores'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
}),
'priority'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'pw'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'20'
}),
'ram_size'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'raw_data'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'req_traits'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
u"orm['vm.Trait']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'status'
:
(
'model_utils.fields.StatusField'
,
[],
{
'default'
:
"u'NOSTATE'"
,
'max_length'
:
'100'
,
u'no_check_for_status'
:
'True'
}),
'status_changed'
:
(
'model_utils.fields.MonitorField'
,
[],
{
'default'
:
'datetime.datetime.now'
,
u'monitor'
:
"u'status'"
}),
'system'
:
(
'django.db.models.fields.TextField'
,
[],
{}),
'template'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'blank'
:
'True'
,
'related_name'
:
"u'instance_set'"
,
'null'
:
'True'
,
'on_delete'
:
'models.SET_NULL'
,
'to'
:
u"orm['vm.InstanceTemplate']"
}),
'time_of_delete'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'None'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'time_of_suspend'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'None'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'vnc_port'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'None'
,
'unique'
:
'True'
,
'null'
:
'True'
,
'blank'
:
'True'
})
},
u'vm.instancetemplate'
:
{
'Meta'
:
{
'ordering'
:
"(u'name',)"
,
'object_name'
:
'InstanceTemplate'
},
'access_method'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'10'
}),
'arch'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'10'
}),
'boot_menu'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'created'
:
(
'model_utils.fields.AutoCreatedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'disks'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'related_name'
:
"u'template_set'"
,
'symmetrical'
:
'False'
,
'to'
:
u"orm['storage.Disk']"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'lease'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['vm.Lease']"
}),
'max_ram_size'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'modified'
:
(
'model_utils.fields.AutoLastModifiedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'num_cores'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
}),
'parent'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['vm.InstanceTemplate']"
,
'null'
:
'True'
,
'on_delete'
:
'models.SET_NULL'
,
'blank'
:
'True'
}),
'priority'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'ram_size'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'raw_data'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'req_traits'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
u"orm['vm.Trait']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'system'
:
(
'django.db.models.fields.TextField'
,
[],
{})
},
u'vm.lease'
:
{
'Meta'
:
{
'ordering'
:
"[u'name']"
,
'object_name'
:
'Lease'
},
'delete_interval_seconds'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'100'
}),
'suspend_interval_seconds'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
})
},
u'vm.node'
:
{
'Meta'
:
{
'ordering'
:
"(u'-enabled', u'normalized_name')"
,
'object_name'
:
'Node'
},
'created'
:
(
'model_utils.fields.AutoCreatedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'enabled'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'host'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['firewall.Host']"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified'
:
(
'model_utils.fields.AutoLastModifiedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'50'
}),
'normalized_name'
:
(
'common.models.HumanSortField'
,
[],
{
'default'
:
"''"
,
'maximum_number_length'
:
'4'
,
'max_length'
:
'100'
,
'monitor'
:
"u'name'"
,
'blank'
:
'True'
}),
'overcommit'
:
(
'django.db.models.fields.FloatField'
,
[],
{
'default'
:
'1.0'
}),
'priority'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'traits'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
u"orm['vm.Trait']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
})
},
u'vm.trait'
:
{
'Meta'
:
{
'object_name'
:
'Trait'
},
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
})
}
}
complete_apps
=
[
'dashboard'
]
\ No newline at end of file
circle/dashboard/migrations/0016_auto__del_field_connectcommand_application__add_field_connectcommand_n.py
0 → 100644
View file @
0963fa06
# -*- coding: utf-8 -*-
from
south.utils
import
datetime_utils
as
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
from
django.db
import
models
class
Migration
(
SchemaMigration
):
def
forwards
(
self
,
orm
):
# Deleting field 'ConnectCommand.application'
db
.
delete_column
(
u'dashboard_connectcommand'
,
'application'
)
# Adding field 'ConnectCommand.name'
db
.
add_column
(
u'dashboard_connectcommand'
,
'name'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
default
=
'szia megint'
,
max_length
=
'128'
),
keep_default
=
False
)
def
backwards
(
self
,
orm
):
# Adding field 'ConnectCommand.application'
db
.
add_column
(
u'dashboard_connectcommand'
,
'application'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
default
=
'szia'
,
max_length
=
'128'
),
keep_default
=
False
)
# Deleting field 'ConnectCommand.name'
db
.
delete_column
(
u'dashboard_connectcommand'
,
'name'
)
models
=
{
u'auth.group'
:
{
'Meta'
:
{
'object_name'
:
'Group'
},
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'80'
}),
'permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
u"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
})
},
u'auth.permission'
:
{
'Meta'
:
{
'ordering'
:
"(u'content_type__app_label', u'content_type__model', u'codename')"
,
'unique_together'
:
"((u'content_type', u'codename'),)"
,
'object_name'
:
'Permission'
},
'codename'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'content_type'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['contenttypes.ContentType']"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
})
},
u'auth.user'
:
{
'Meta'
:
{
'object_name'
:
'User'
},
'date_joined'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'email'
:
(
'django.db.models.fields.EmailField'
,
[],
{
'max_length'
:
'75'
,
'blank'
:
'True'
}),
'first_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'groups'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'symmetrical'
:
'False'
,
'related_name'
:
"u'user_set'"
,
'blank'
:
'True'
,
'to'
:
u"orm['auth.Group']"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'is_staff'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'is_superuser'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'last_login'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'last_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'password'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
}),
'user_permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'symmetrical'
:
'False'
,
'related_name'
:
"u'user_set'"
,
'blank'
:
'True'
,
'to'
:
u"orm['auth.Permission']"
}),
'username'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'30'
})
},
u'contenttypes.contenttype'
:
{
'Meta'
:
{
'ordering'
:
"('name',)"
,
'unique_together'
:
"(('app_label', 'model'),)"
,
'object_name'
:
'ContentType'
,
'db_table'
:
"'django_content_type'"
},
'app_label'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'model'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
})
},
u'dashboard.connectcommand'
:
{
'Meta'
:
{
'object_name'
:
'ConnectCommand'
},
'access_method'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'10'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
"'128'"
}),
'template'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'256'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'command_set'"
,
'to'
:
u"orm['auth.User']"
})
},
u'dashboard.favourite'
:
{
'Meta'
:
{
'object_name'
:
'Favourite'
},
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'instance'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['vm.Instance']"
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
})
},
u'dashboard.futuremember'
:
{
'Meta'
:
{
'unique_together'
:
"(('org_id', 'group'),)"
,
'object_name'
:
'FutureMember'
},
'group'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.Group']"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'org_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'64'
})
},
u'dashboard.groupprofile'
:
{
'Meta'
:
{
'object_name'
:
'GroupProfile'
},
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'group'
:
(
'django.db.models.fields.related.OneToOneField'
,
[],
{
'to'
:
u"orm['auth.Group']"
,
'unique'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'org_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'64'
,
'unique'
:
'True'
,
'null'
:
'True'
,
'blank'
:
'True'
})
},
u'dashboard.notification'
:
{
'Meta'
:
{
'ordering'
:
"['-created']"
,
'object_name'
:
'Notification'
},
'created'
:
(
'model_utils.fields.AutoCreatedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'message_data'
:
(
'jsonfield.fields.JSONField'
,
[],
{
'null'
:
'True'
}),
'modified'
:
(
'model_utils.fields.AutoLastModifiedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'status'
:
(
'model_utils.fields.StatusField'
,
[],
{
'default'
:
"'new'"
,
'max_length'
:
'100'
,
u'no_check_for_status'
:
'True'
}),
'subject_data'
:
(
'jsonfield.fields.JSONField'
,
[],
{
'null'
:
'True'
}),
'to'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
}),
'valid_until'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'None'
,
'null'
:
'True'
})
},
u'dashboard.profile'
:
{
'Meta'
:
{
'object_name'
:
'Profile'
},
'disk_quota'
:
(
'sizefield.models.FileSizeField'
,
[],
{
'default'
:
'2147483648'
}),
'email_notifications'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'instance_limit'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'5'
}),
'org_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'64'
,
'unique'
:
'True'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'preferred_language'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'en'"
,
'max_length'
:
'32'
}),
'smb_password'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"u'NRERukxe3Z'"
,
'max_length'
:
'20'
}),
'use_gravatar'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'user'
:
(
'django.db.models.fields.related.OneToOneField'
,
[],
{
'to'
:
u"orm['auth.User']"
,
'unique'
:
'True'
})
},
u'firewall.domain'
:
{
'Meta'
:
{
'object_name'
:
'Domain'
},
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
}),
'ttl'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'600'
})
},
u'firewall.group'
:
{
'Meta'
:
{
'object_name'
:
'Group'
},
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'20'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
,
'null'
:
'True'
,
'blank'
:
'True'
})
},
u'firewall.host'
:
{
'Meta'
:
{
'ordering'
:
"('normalized_hostname', 'vlan')"
,
'unique_together'
:
"(('hostname', 'vlan'),)"
,
'object_name'
:
'Host'
},
'comment'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'external_ipv4'
:
(
'firewall.fields.IPAddressField'
,
[],
{
'max_length'
:
'100'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'groups'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'symmetrical'
:
'False'
,
'to'
:
u"orm['firewall.Group']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'hostname'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'ipv4'
:
(
'firewall.fields.IPAddressField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'100'
}),
'ipv6'
:
(
'firewall.fields.IPAddressField'
,
[],
{
'max_length'
:
'100'
,
'unique'
:
'True'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'location'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'mac'
:
(
'firewall.fields.MACAddressField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'17'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'normalized_hostname'
:
(
'common.models.HumanSortField'
,
[],
{
'default'
:
"''"
,
'maximum_number_length'
:
'4'
,
'max_length'
:
'80'
,
'monitor'
:
"'hostname'"
,
'blank'
:
'True'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
}),
'reverse'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'shared_ip'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'vlan'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['firewall.Vlan']"
})
},
u'firewall.vlan'
:
{
'Meta'
:
{
'object_name'
:
'Vlan'
},
'comment'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'dhcp_pool'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'domain'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['firewall.Domain']"
}),
'host_ipv6_prefixlen'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'112'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'ipv6_template'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"'2001:738:2001:4031:
%(b)
d:
%(c)
d:
%(d)
d:0'"
}),
'managed'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'modified_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'20'
}),
'network4'
:
(
'firewall.fields.IPNetworkField'
,
[],
{
'max_length'
:
'100'
}),
'network6'
:
(
'firewall.fields.IPNetworkField'
,
[],
{
'max_length'
:
'100'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'network_type'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'portforward'"
,
'max_length'
:
'20'
}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'reverse_domain'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"'
%(d)
d.
%(c)
d.
%(b)
d.
%(a)
d.in-addr.arpa'"
}),
'snat_ip'
:
(
'django.db.models.fields.GenericIPAddressField'
,
[],
{
'max_length'
:
'39'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'snat_to'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'symmetrical'
:
'False'
,
'to'
:
u"orm['firewall.Vlan']"
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'vid'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'unique'
:
'True'
})
},
u'storage.datastore'
:
{
'Meta'
:
{
'ordering'
:
"[u'name']"
,
'object_name'
:
'DataStore'
},
'hostname'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'40'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'100'
}),
'path'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'200'
})
},
u'storage.disk'
:
{
'Meta'
:
{
'ordering'
:
"[u'name']"
,
'object_name'
:
'Disk'
},
'base'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'blank'
:
'True'
,
'related_name'
:
"u'derivatives'"
,
'null'
:
'True'
,
'to'
:
u"orm['storage.Disk']"
}),
'created'
:
(
'model_utils.fields.AutoCreatedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'datastore'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['storage.DataStore']"
}),
'destroyed'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'None'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'dev_num'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"u'a'"
,
'max_length'
:
'1'
}),
'filename'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'256'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_ready'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'modified'
:
(
'model_utils.fields.AutoLastModifiedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
,
'blank'
:
'True'
}),
'size'
:
(
'sizefield.models.FileSizeField'
,
[],
{
'default'
:
'None'
,
'null'
:
'True'
}),
'type'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'10'
})
},
u'vm.instance'
:
{
'Meta'
:
{
'ordering'
:
"(u'pk',)"
,
'object_name'
:
'Instance'
},
'access_method'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'10'
}),
'active_since'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'arch'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'10'
}),
'boot_menu'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'created'
:
(
'model_utils.fields.AutoCreatedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'destroyed_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'disks'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'related_name'
:
"u'instance_set'"
,
'symmetrical'
:
'False'
,
'to'
:
u"orm['storage.Disk']"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_base'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'lease'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['vm.Lease']"
}),
'max_ram_size'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'modified'
:
(
'model_utils.fields.AutoLastModifiedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
,
'blank'
:
'True'
}),
'node'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'blank'
:
'True'
,
'related_name'
:
"u'instance_set'"
,
'null'
:
'True'
,
'to'
:
u"orm['vm.Node']"
}),
'num_cores'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
}),
'priority'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'pw'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'20'
}),
'ram_size'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'raw_data'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'req_traits'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
u"orm['vm.Trait']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'status'
:
(
'model_utils.fields.StatusField'
,
[],
{
'default'
:
"u'NOSTATE'"
,
'max_length'
:
'100'
,
u'no_check_for_status'
:
'True'
}),
'status_changed'
:
(
'model_utils.fields.MonitorField'
,
[],
{
'default'
:
'datetime.datetime.now'
,
u'monitor'
:
"u'status'"
}),
'system'
:
(
'django.db.models.fields.TextField'
,
[],
{}),
'template'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'blank'
:
'True'
,
'related_name'
:
"u'instance_set'"
,
'null'
:
'True'
,
'on_delete'
:
'models.SET_NULL'
,
'to'
:
u"orm['vm.InstanceTemplate']"
}),
'time_of_delete'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'None'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'time_of_suspend'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'None'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'vnc_port'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'None'
,
'unique'
:
'True'
,
'null'
:
'True'
,
'blank'
:
'True'
})
},
u'vm.instancetemplate'
:
{
'Meta'
:
{
'ordering'
:
"(u'name',)"
,
'object_name'
:
'InstanceTemplate'
},
'access_method'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'10'
}),
'arch'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'10'
}),
'boot_menu'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'created'
:
(
'model_utils.fields.AutoCreatedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'disks'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'related_name'
:
"u'template_set'"
,
'symmetrical'
:
'False'
,
'to'
:
u"orm['storage.Disk']"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'lease'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['vm.Lease']"
}),
'max_ram_size'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'modified'
:
(
'model_utils.fields.AutoLastModifiedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'num_cores'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'owner'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['auth.User']"
}),
'parent'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['vm.InstanceTemplate']"
,
'null'
:
'True'
,
'on_delete'
:
'models.SET_NULL'
,
'blank'
:
'True'
}),
'priority'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'ram_size'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'raw_data'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'req_traits'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
u"orm['vm.Trait']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'system'
:
(
'django.db.models.fields.TextField'
,
[],
{})
},
u'vm.lease'
:
{
'Meta'
:
{
'ordering'
:
"[u'name']"
,
'object_name'
:
'Lease'
},
'delete_interval_seconds'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'100'
}),
'suspend_interval_seconds'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
})
},
u'vm.node'
:
{
'Meta'
:
{
'ordering'
:
"(u'-enabled', u'normalized_name')"
,
'object_name'
:
'Node'
},
'created'
:
(
'model_utils.fields.AutoCreatedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'enabled'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'host'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
u"orm['firewall.Host']"
}),
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified'
:
(
'model_utils.fields.AutoLastModifiedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'50'
}),
'normalized_name'
:
(
'common.models.HumanSortField'
,
[],
{
'default'
:
"''"
,
'maximum_number_length'
:
'4'
,
'max_length'
:
'100'
,
'monitor'
:
"u'name'"
,
'blank'
:
'True'
}),
'overcommit'
:
(
'django.db.models.fields.FloatField'
,
[],
{
'default'
:
'1.0'
}),
'priority'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'traits'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
u"orm['vm.Trait']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
})
},
u'vm.trait'
:
{
'Meta'
:
{
'object_name'
:
'Trait'
},
u'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
})
}
}
complete_apps
=
[
'dashboard'
]
\ No newline at end of file
circle/dashboard/models.py
View file @
0963fa06
...
@@ -46,8 +46,10 @@ from acl.models import AclBase
...
@@ -46,8 +46,10 @@ from acl.models import AclBase
from
common.models
import
HumanReadableObject
,
create_readable
,
Encoder
from
common.models
import
HumanReadableObject
,
create_readable
,
Encoder
from
vm.tasks.agent_tasks
import
add_keys
,
del_keys
from
vm.tasks.agent_tasks
import
add_keys
,
del_keys
from
vm.models.instance
import
ACCESS_METHODS
from
.store_api
import
Store
,
NoStoreException
,
NotOkException
from
.store_api
import
Store
,
NoStoreException
,
NotOkException
from
.validators
import
connect_command_template_validator
logger
=
getLogger
(
__name__
)
logger
=
getLogger
(
__name__
)
...
@@ -100,6 +102,25 @@ class Notification(TimeStampedModel):
...
@@ -100,6 +102,25 @@ class Notification(TimeStampedModel):
self
.
message_data
=
None
if
value
is
None
else
value
.
to_dict
()
self
.
message_data
=
None
if
value
is
None
else
value
.
to_dict
()
class
ConnectCommand
(
Model
):
user
=
ForeignKey
(
User
,
related_name
=
'command_set'
)
access_method
=
CharField
(
max_length
=
10
,
choices
=
ACCESS_METHODS
,
verbose_name
=
_
(
'access method'
),
help_text
=
_
(
'Type of the remote access method.'
))
name
=
CharField
(
max_length
=
"128"
,
verbose_name
=
_
(
'name'
),
blank
=
False
,
help_text
=
_
(
"Name of your custom command."
))
template
=
CharField
(
blank
=
True
,
null
=
True
,
max_length
=
256
,
verbose_name
=
_
(
'command template'
),
help_text
=
_
(
'Template for connection command string. '
'Available parameters are: '
'username, password, '
'host, port.'
),
validators
=
[
connect_command_template_validator
])
def
__unicode__
(
self
):
return
self
.
template
class
Profile
(
Model
):
class
Profile
(
Model
):
user
=
OneToOneField
(
User
)
user
=
OneToOneField
(
User
)
preferred_language
=
CharField
(
verbose_name
=
_
(
'preferred language'
),
preferred_language
=
CharField
(
verbose_name
=
_
(
'preferred language'
),
...
@@ -129,6 +150,25 @@ class Profile(Model):
...
@@ -129,6 +150,25 @@ class Profile(Model):
default
=
2048
*
1024
*
1024
,
default
=
2048
*
1024
*
1024
,
help_text
=
_
(
'Disk quota in mebibytes.'
))
help_text
=
_
(
'Disk quota in mebibytes.'
))
def
get_connect_commands
(
self
,
instance
,
use_ipv6
=
False
):
""" Generate connection command based on template."""
single_command
=
instance
.
get_connect_command
(
use_ipv6
)
if
single_command
:
# can we even connect to that VM
commands
=
self
.
user
.
command_set
.
filter
(
access_method
=
instance
.
access_method
)
if
commands
.
count
()
<
1
:
return
[
single_command
]
else
:
return
[
command
.
template
%
{
'port'
:
instance
.
get_connect_port
(
use_ipv6
=
use_ipv6
),
'host'
:
instance
.
get_connect_host
(
use_ipv6
=
use_ipv6
),
'password'
:
instance
.
pw
,
'username'
:
'cloud'
,
}
for
command
in
commands
]
else
:
return
[]
def
notify
(
self
,
subject
,
template
,
context
=
None
,
valid_until
=
None
,
def
notify
(
self
,
subject
,
template
,
context
=
None
,
valid_until
=
None
,
**
kwargs
):
**
kwargs
):
if
context
is
not
None
:
if
context
is
not
None
:
...
...
circle/dashboard/static/dashboard/dashboard.css
View file @
0963fa06
...
@@ -654,7 +654,8 @@ textarea[name="list-new-namelist"] {
...
@@ -654,7 +654,8 @@ textarea[name="list-new-namelist"] {
width
:
130px
;
width
:
130px
;
}
}
#vm-details-connection-string-copy
{
.vm-details-connection-string-copy
,
#vm-details-pw-show
{
cursor
:
pointer
;
cursor
:
pointer
;
}
}
...
@@ -681,10 +682,9 @@ textarea[name="list-new-namelist"] {
...
@@ -681,10 +682,9 @@ textarea[name="list-new-namelist"] {
max-width
:
200px
;
max-width
:
200px
;
}
}
#
dashboard-vm-details-connect-command
{
.
dashboard-vm-details-connect-command
{
/* for mobile view */
/* for mobile view */
margin-bottom
:
20px
;
margin-bottom
:
20px
;
}
}
#store-list-list
{
#store-list-list
{
...
@@ -868,6 +868,12 @@ textarea[name="list-new-namelist"] {
...
@@ -868,6 +868,12 @@ textarea[name="list-new-namelist"] {
padding
:
5px
0px
;
padding
:
5px
0px
;
}
}
#profile-key-list-table
td
:last-child
,
#profile-key-list-table
th
:last-child
,
#profile-command-list-table
td
:last-child
,
#profile-command-list-table
th
:last-child
,
#profile-command-list-table
td
:nth-child
(
2
),
#profile-command-list-table
th
:nth-child
(
2
)
{
text-align
:
center
;
vertical-align
:
middle
;
}
#vm-list-table
.migrating-icon
{
#vm-list-table
.migrating-icon
{
-webkit-animation
:
passing
2s
linear
infinite
;
-webkit-animation
:
passing
2s
linear
infinite
;
...
...
circle/dashboard/static/dashboard/vm-details.js
View file @
0963fa06
...
@@ -105,19 +105,20 @@ $(function() {
...
@@ -105,19 +105,20 @@ $(function() {
$
(
"#vm-details-pw-show"
).
click
(
function
()
{
$
(
"#vm-details-pw-show"
).
click
(
function
()
{
var
input
=
$
(
this
).
parent
(
"div"
).
children
(
"input"
);
var
input
=
$
(
this
).
parent
(
"div"
).
children
(
"input"
);
var
eye
=
$
(
this
).
children
(
"#vm-details-pw-eye"
);
var
eye
=
$
(
this
).
children
(
"#vm-details-pw-eye"
);
var
span
=
$
(
this
);
eye
.
tooltip
(
"destroy"
)
span
.
tooltip
(
"destroy"
)
if
(
eye
.
hasClass
(
"fa-eye"
))
{
if
(
eye
.
hasClass
(
"fa-eye"
))
{
eye
.
removeClass
(
"fa-eye"
).
addClass
(
"fa-eye-slash"
);
eye
.
removeClass
(
"fa-eye"
).
addClass
(
"fa-eye-slash"
);
input
.
prop
(
"type"
,
"text"
);
input
.
prop
(
"type"
,
"text"
);
input
.
focus
();
input
.
select
();
eye
.
prop
(
"title"
,
"Hide password"
);
span
.
prop
(
"title"
,
gettext
(
"Hide password"
)
);
}
else
{
}
else
{
eye
.
removeClass
(
"fa-eye-slash"
).
addClass
(
"fa-eye"
);
eye
.
removeClass
(
"fa-eye-slash"
).
addClass
(
"fa-eye"
);
input
.
prop
(
"type"
,
"password"
);
input
.
prop
(
"type"
,
"password"
);
eye
.
prop
(
"title"
,
"Show password"
);
span
.
prop
(
"title"
,
gettext
(
"Show password"
)
);
}
}
eye
.
tooltip
();
span
.
tooltip
();
});
});
/* change password confirmation */
/* change password confirmation */
...
@@ -198,7 +199,7 @@ $(function() {
...
@@ -198,7 +199,7 @@ $(function() {
$
(
"#vm-details-h1-name, .vm-details-rename-button"
).
click
(
function
()
{
$
(
"#vm-details-h1-name, .vm-details-rename-button"
).
click
(
function
()
{
$
(
"#vm-details-h1-name"
).
hide
();
$
(
"#vm-details-h1-name"
).
hide
();
$
(
"#vm-details-rename"
).
css
(
'display'
,
'inline'
);
$
(
"#vm-details-rename"
).
css
(
'display'
,
'inline'
);
$
(
"#vm-details-rename-name"
).
focus
();
$
(
"#vm-details-rename-name"
).
select
();
return
false
;
return
false
;
});
});
...
@@ -206,7 +207,7 @@ $(function() {
...
@@ -206,7 +207,7 @@ $(function() {
$
(
".vm-details-home-edit-name-click"
).
click
(
function
()
{
$
(
".vm-details-home-edit-name-click"
).
click
(
function
()
{
$
(
".vm-details-home-edit-name-click"
).
hide
();
$
(
".vm-details-home-edit-name-click"
).
hide
();
$
(
"#vm-details-home-rename"
).
show
();
$
(
"#vm-details-home-rename"
).
show
();
$
(
"input"
,
$
(
"#vm-details-home-rename"
)).
focus
();
$
(
"input"
,
$
(
"#vm-details-home-rename"
)).
select
();
return
false
;
return
false
;
});
});
...
@@ -306,8 +307,8 @@ $(function() {
...
@@ -306,8 +307,8 @@ $(function() {
});
});
// select connection string
// select connection string
$
(
"
#
vm-details-connection-string-copy"
).
click
(
function
()
{
$
(
"
.
vm-details-connection-string-copy"
).
click
(
function
()
{
$
(
"#vm-details-connection-string"
).
focus
();
$
(
this
).
parent
(
"div"
).
find
(
"input"
).
select
();
});
});
$
(
"a.operation-password_reset"
).
click
(
function
()
{
$
(
"a.operation-password_reset"
).
click
(
function
()
{
...
...
circle/dashboard/tables.py
View file @
0963fa06
...
@@ -25,6 +25,7 @@ from django_tables2.columns import (TemplateColumn, Column, BooleanColumn,
...
@@ -25,6 +25,7 @@ from django_tables2.columns import (TemplateColumn, Column, BooleanColumn,
from
vm.models
import
Node
,
InstanceTemplate
,
Lease
from
vm.models
import
Node
,
InstanceTemplate
,
Lease
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
django_sshkey.models
import
UserKey
from
django_sshkey.models
import
UserKey
from
dashboard.models
import
ConnectCommand
class
NodeListTable
(
Table
):
class
NodeListTable
(
Table
):
...
@@ -248,5 +249,41 @@ class UserKeyListTable(Table):
...
@@ -248,5 +249,41 @@ class UserKeyListTable(Table):
class
Meta
:
class
Meta
:
model
=
UserKey
model
=
UserKey
attrs
=
{
'class'
:
(
'table table-bordered table-striped table-hover'
)}
attrs
=
{
'class'
:
(
'table table-bordered table-striped table-hover'
),
'id'
:
"profile-key-list-table"
}
fields
=
(
'name'
,
'fingerprint'
,
'created'
,
'actions'
)
fields
=
(
'name'
,
'fingerprint'
,
'created'
,
'actions'
)
prefix
=
"key-"
empty_text
=
_
(
"You haven't added any public keys yet."
)
class
ConnectCommandListTable
(
Table
):
name
=
LinkColumn
(
'dashboard.views.connect-command-detail'
,
args
=
[
A
(
'pk'
)],
attrs
=
{
'th'
:
{
'data-sort'
:
"string"
}}
)
access_method
=
Column
(
verbose_name
=
_
(
"Access method"
),
attrs
=
{
'th'
:
{
'data-sort'
:
"string"
}}
)
template
=
Column
(
verbose_name
=
_
(
"Template"
),
attrs
=
{
'th'
:
{
'data-sort'
:
"string"
}}
)
actions
=
TemplateColumn
(
verbose_name
=
_
(
"Actions"
),
template_name
=
(
"dashboard/connect-command-list/column-command"
"-actions.html"
),
orderable
=
False
,
)
class
Meta
:
model
=
ConnectCommand
attrs
=
{
'class'
:
(
'table table-bordered table-striped table-hover'
),
'id'
:
"profile-command-list-table"
}
fields
=
(
'name'
,
'access_method'
,
'template'
,
'actions'
)
prefix
=
"cmd-"
empty_text
=
_
(
"You don't have any custom connection commands yet. You can "
"specify commands to be displayed on VM detail pages instead of "
"the defaults."
)
circle/dashboard/templates/dashboard/connect-command-create.html
0 → 100644
View file @
0963fa06
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block title-page %}{% trans "Create command template" %}{% endblock %}
{% block content %}
<div
class=
"row"
>
<div
class=
"col-md-12"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<a
class=
"pull-right btn btn-default btn-xs"
href=
"{% url "
dashboard
.
views
.
profile-preferences
"
%}"
>
{% trans "Back" %}
</a>
<h3
class=
"no-margin"
><i
class=
"fa fa-code"
></i>
{% trans "Create new command template" %}
</h3>
</div>
<div
class=
"panel-body"
>
<form
method=
"POST"
>
{% csrf_token %}
{{ form.name|as_crispy_field }}
{{ form.access_method|as_crispy_field }}
{{ form.template|as_crispy_field }}
<p
class=
"text-muted"
>
{% trans "Examples" %}
</p>
<p>
<strong>
SSH:
</strong>
<span
class=
"text-muted"
>
sshpass -p %(password)s ssh -o StrictHostKeyChecking=no cloud@%(host)s -p %(port)d
</span>
</p>
<p>
<strong>
RDP:
</strong>
<span
class=
"text-muted"
>
rdesktop %(host)s:%(port)d -u cloud -p %(password)s
</span>
</p>
<input
type=
"submit"
class=
"btn btn-primary"
value=
"{% trans "
Save
"
%}"
>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
circle/dashboard/templates/dashboard/connect-command-edit.html
0 → 100644
View file @
0963fa06
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block title-page %}{% trans "Edit command template" %}{% endblock %}
{% block content %}
<div
class=
"row"
>
<div
class=
"col-md-12"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<a
class=
"pull-right btn btn-default btn-xs"
href=
"{% url "
dashboard
.
views
.
profile-preferences
"
%}"
>
{% trans "Back" %}
</a>
<h3
class=
"no-margin"
><i
class=
"fa fa-code"
></i>
{% trans "Edit command template" %}
</h3>
</div>
<div
class=
"panel-body"
>
<form
method=
"POST"
>
{% csrf_token %}
{{ form.name|as_crispy_field }}
{{ form.access_method|as_crispy_field }}
{{ form.template|as_crispy_field }}
<p
class=
"text-muted"
>
{% trans "Examples" %}
</p>
<p>
<strong>
SSH:
</strong>
<span
class=
"text-muted"
>
sshpass -p %(password)s ssh -o StrictHostKeyChecking=no cloud@%(host)s -p %(port)d
</span>
</p>
<p>
<strong>
RDP:
</strong>
<span
class=
"text-muted"
>
rdesktop %(host)s:%(port)d -u cloud -p %(password)s
</span>
</p>
<input
type=
"submit"
class=
"btn btn-primary"
value=
"{% trans "
Save
"
%}"
>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
circle/dashboard/templates/dashboard/connect-command-list/column-command-actions.html
0 → 100644
View file @
0963fa06
{% load i18n %}
<a
href=
"{% url "
dashboard
.
views
.
connect-command-detail
"
pk=
record.pk%}"
id=
"template-list-edit-button"
class=
"btn btn-default btn-xs"
title=
"{% trans "
Edit
"
%}"
>
<i
class=
"fa fa-edit"
></i>
</a>
<a
data-template-pk=
"{{ record.pk }}"
href=
"{% url "
dashboard
.
views
.
connect-command-delete
"
pk=
record.pk
%}"
class=
"btn btn-danger btn-xs template-delete"
title=
"{% trans "
Delete
"
%}"
>
<i
class=
"fa fa-times"
></i>
</a>
circle/dashboard/templates/dashboard/profile_form.html
View file @
0963fa06
...
@@ -66,4 +66,20 @@
...
@@ -66,4 +66,20 @@
</div>
</div>
</div>
</div>
<div
class=
"row"
>
<div
class=
"col-md-12"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<a
href=
"{% url "
dashboard
.
views
.
connect-command-create
"
%}"
class=
"pull-right btn btn-success btn-xs"
style=
"margin-right: 10px;"
>
<i
class=
"fa fa-plus"
></i>
{% trans "add command template" %}
</a>
<h3
class=
"no-margin"
><i
class=
"fa fa-code"
></i>
{% trans "Command templates" %}
</h3>
</div>
<div
class=
"panel-body"
>
{% render_table connectcommand_table %}
</div>
</div>
</div>
</div>
{% endblock %}
{% endblock %}
circle/dashboard/templates/dashboard/vm-detail.html
View file @
0963fa06
...
@@ -98,8 +98,9 @@
...
@@ -98,8 +98,9 @@
<div
class=
"input-group"
>
<div
class=
"input-group"
>
<input
type=
"text"
id=
"vm-details-pw-input"
class=
"form-control input-sm input-tags"
<input
type=
"text"
id=
"vm-details-pw-input"
class=
"form-control input-sm input-tags"
value=
"{{ instance.pw }}"
spellcheck=
"false"
/>
value=
"{{ instance.pw }}"
spellcheck=
"false"
/>
<span
class=
"input-group-addon input-tags"
id=
"vm-details-pw-show"
>
<span
class=
"input-group-addon input-tags"
id=
"vm-details-pw-show"
<i
class=
"fa fa-eye"
id=
"vm-details-pw-eye"
title=
"Show password"
></i>
title=
"{% trans "
Show
password
"
%}"
data-container=
"body"
>
<i
class=
"fa fa-eye"
id=
"vm-details-pw-eye"
></i>
</span>
</span>
</div>
</div>
</dd>
</dd>
...
@@ -111,16 +112,29 @@
...
@@ -111,16 +112,29 @@
</div>
</div>
</dd>
</dd>
</dl>
</dl>
{% for c in connect_commands %}
<div
class=
"input-group
"
id=
"
dashboard-vm-details-connect-command"
>
<div
class=
"input-group
dashboard-vm-details-connect-command"
>
<span
class=
"input-group-addon input-tags"
>
{% trans "Command" %}
</span>
<span
class=
"input-group-addon input-tags"
>
{% trans "Command" %}
</span>
<input
type=
"text"
spellcheck=
"false"
<input
type=
"text"
spellcheck=
"false"
value=
"{% if instance.get_connect_command %}{{ instance.get_connect_command }}{% else %}{% trans "
Connection
is
not
possible
."
%}{%
endif
%}"
value=
"{{ c }}"
id=
"vm-details-connection-string"
class=
"form-control input-tags"
/>
<span
class=
"input-group-addon input-tags vm-details-connection-string-copy"
title=
"{% trans "
Select
all
"
%}"
data-container=
"body"
>
<i
class=
"fa fa-copy"
></i>
</span>
</div>
{% empty %}
<div
class=
"input-group dashboard-vm-details-connect-command"
>
<span
class=
"input-group-addon input-tags"
>
{% trans "Command" %}
</span>
<input
type=
"text"
spellcheck=
"false"
value=
"{% trans "
Connection
is
not
possible
."
%}"
id=
"vm-details-connection-string"
class=
"form-control input-tags"
/>
id=
"vm-details-connection-string"
class=
"form-control input-tags"
/>
<span
class=
"input-group-addon input-tags"
id=
"vm-details-connection-string-copy"
>
<span
class=
"input-group-addon input-tags"
id=
"vm-details-connection-string-copy"
>
<i
class=
"fa fa-copy"
title=
"{% trans "
Select
all
"
%}"
></i>
<i
class=
"fa fa-copy"
title=
"{% trans "
Select
all
"
%}"
></i>
</span>
</span>
</div>
</div>
{% endfor %}
</div>
</div>
<div
class=
"col-md-8"
id=
"vm-detail-pane"
>
<div
class=
"col-md-8"
id=
"vm-detail-pane"
>
<div
class=
"panel panel-default"
id=
"vm-detail-panel"
>
<div
class=
"panel panel-default"
id=
"vm-detail-panel"
>
...
...
circle/dashboard/urls.py
View file @
0963fa06
...
@@ -39,6 +39,7 @@ from .views import (
...
@@ -39,6 +39,7 @@ from .views import (
get_vm_screenshot
,
get_vm_screenshot
,
ProfileView
,
toggle_use_gravatar
,
UnsubscribeFormView
,
ProfileView
,
toggle_use_gravatar
,
UnsubscribeFormView
,
UserKeyDelete
,
UserKeyDetail
,
UserKeyCreate
,
UserKeyDelete
,
UserKeyDetail
,
UserKeyCreate
,
ConnectCommandDelete
,
ConnectCommandDetail
,
ConnectCommandCreate
,
StoreList
,
store_download
,
store_upload
,
store_get_upload_url
,
StoreRemove
,
StoreList
,
store_download
,
store_upload
,
store_get_upload_url
,
StoreRemove
,
store_new_directory
,
store_refresh_toplist
,
store_new_directory
,
store_refresh_toplist
,
VmTraitsUpdate
,
VmRawDataUpdate
,
VmTraitsUpdate
,
VmRawDataUpdate
,
...
@@ -177,6 +178,16 @@ urlpatterns = patterns(
...
@@ -177,6 +178,16 @@ urlpatterns = patterns(
UserKeyCreate
.
as_view
(),
UserKeyCreate
.
as_view
(),
name
=
"dashboard.views.userkey-create"
),
name
=
"dashboard.views.userkey-create"
),
url
(
r'^conncmd/delete/(?P<pk>\d+)/$'
,
ConnectCommandDelete
.
as_view
(),
name
=
"dashboard.views.connect-command-delete"
),
url
(
r'^conncmd/(?P<pk>\d+)/$'
,
ConnectCommandDetail
.
as_view
(),
name
=
"dashboard.views.connect-command-detail"
),
url
(
r'^conncmd/create/$'
,
ConnectCommandCreate
.
as_view
(),
name
=
"dashboard.views.connect-command-create"
),
url
(
r'^autocomplete/'
,
include
(
'autocomplete_light.urls'
)),
url
(
r'^autocomplete/'
,
include
(
'autocomplete_light.urls'
)),
url
(
r"^store/list/$"
,
StoreList
.
as_view
(),
url
(
r"^store/list/$"
,
StoreList
.
as_view
(),
...
...
circle/dashboard/v
irtvalidator
.py
→
circle/dashboard/v
alidators
.py
View file @
0963fa06
from
django.core.exceptions
import
ValidationError
from
django.core.exceptions
import
ValidationError
from
django.utils.translation
import
ugettext_lazy
as
_
from
lxml
import
etree
as
ET
from
lxml
import
etree
as
ET
import
logging
import
logging
...
@@ -29,3 +31,27 @@ def domain_validator(value):
...
@@ -29,3 +31,27 @@ def domain_validator(value):
relaxng
.
assertValid
(
parsed_xml
)
relaxng
.
assertValid
(
parsed_xml
)
except
Exception
as
e
:
except
Exception
as
e
:
raise
ValidationError
(
e
.
message
)
raise
ValidationError
(
e
.
message
)
def
connect_command_template_validator
(
value
):
"""Validate value as a connect command template.
>>> try: connect_command_template_validator("
%(host)
s")
... except ValidationError as e: print e
...
>>> connect_command_template_validator("
%(host)
s")
>>> try: connect_command_template_validator("
%(host)
s
%
s")
... except ValidationError as e: print e
...
[u'Invalid template string.']
"""
try
:
value
%
{
'username'
:
"uname"
,
'password'
:
"pw"
,
'host'
:
"111.111.111.111"
,
'port'
:
12345
,
}
except
(
KeyError
,
TypeError
,
ValueError
):
raise
ValidationError
(
_
(
"Invalid template string."
))
circle/dashboard/views.py
View file @
0963fa06
...
@@ -70,12 +70,12 @@ from .forms import (
...
@@ -70,12 +70,12 @@ from .forms import (
VmSaveForm
,
UserKeyForm
,
VmRenewForm
,
VmStateChangeForm
,
VmSaveForm
,
UserKeyForm
,
VmRenewForm
,
VmStateChangeForm
,
CirclePasswordChangeForm
,
VmCreateDiskForm
,
VmDownloadDiskForm
,
CirclePasswordChangeForm
,
VmCreateDiskForm
,
VmDownloadDiskForm
,
TraitsForm
,
RawDataForm
,
GroupPermissionForm
,
AclUserAddForm
,
TraitsForm
,
RawDataForm
,
GroupPermissionForm
,
AclUserAddForm
,
VmResourcesForm
,
VmAddInterfaceForm
,
VmListSearchForm
VmResourcesForm
,
VmAddInterfaceForm
,
VmListSearchForm
,
ConnectCommandForm
)
)
from
.tables
import
(
from
.tables
import
(
NodeListTable
,
TemplateListTable
,
LeaseListTable
,
NodeListTable
,
TemplateListTable
,
LeaseListTable
,
GroupListTable
,
UserKeyListTable
GroupListTable
,
UserKeyListTable
,
ConnectCommandListTable
,
)
)
from
common.models
import
(
from
common.models
import
(
HumanReadableObject
,
HumanReadableException
,
fetch_human_exception
,
HumanReadableObject
,
HumanReadableException
,
fetch_human_exception
,
...
@@ -87,7 +87,8 @@ from vm.models import (
...
@@ -87,7 +87,8 @@ from vm.models import (
)
)
from
storage.models
import
Disk
from
storage.models
import
Disk
from
firewall.models
import
Vlan
,
Host
,
Rule
from
firewall.models
import
Vlan
,
Host
,
Rule
from
.models
import
Favourite
,
Profile
,
GroupProfile
,
FutureMember
from
.models
import
(
Favourite
,
Profile
,
GroupProfile
,
FutureMember
,
ConnectCommand
)
from
.store_api
import
Store
,
NoStoreException
,
NotOkException
from
.store_api
import
Store
,
NoStoreException
,
NotOkException
...
@@ -306,6 +307,7 @@ class VmDetailView(CheckedDetailView):
...
@@ -306,6 +307,7 @@ class VmDetailView(CheckedDetailView):
kwargs
=
{
'pk'
:
self
.
object
.
pk
}),
kwargs
=
{
'pk'
:
self
.
object
.
pk
}),
'ops'
:
ops
,
'ops'
:
ops
,
'op'
:
{
i
.
op
:
i
for
i
in
ops
},
'op'
:
{
i
.
op
:
i
for
i
in
ops
},
'connect_commands'
:
user
.
profile
.
get_connect_commands
(
instance
)
})
})
# activity data
# activity data
...
@@ -2907,11 +2909,16 @@ class MyPreferencesView(UpdateView):
...
@@ -2907,11 +2909,16 @@ class MyPreferencesView(UpdateView):
user
=
self
.
request
.
user
),
user
=
self
.
request
.
user
),
'change_language'
:
MyProfileForm
(
instance
=
self
.
get_object
()),
'change_language'
:
MyProfileForm
(
instance
=
self
.
get_object
()),
}
}
table
=
UserKeyListTable
(
key_
table
=
UserKeyListTable
(
UserKey
.
objects
.
filter
(
user
=
self
.
request
.
user
),
UserKey
.
objects
.
filter
(
user
=
self
.
request
.
user
),
request
=
self
.
request
)
request
=
self
.
request
)
table
.
page
=
None
key_table
.
page
=
None
context
[
'userkey_table'
]
=
table
context
[
'userkey_table'
]
=
key_table
cmd_table
=
ConnectCommandListTable
(
self
.
request
.
user
.
command_set
.
all
(),
request
=
self
.
request
)
cmd_table
.
page
=
None
context
[
'connectcommand_table'
]
=
cmd_table
return
context
return
context
def
get_object
(
self
,
queryset
=
None
):
def
get_object
(
self
,
queryset
=
None
):
...
@@ -3316,6 +3323,82 @@ class UserKeyCreate(LoginRequiredMixin, SuccessMessageMixin, CreateView):
...
@@ -3316,6 +3323,82 @@ class UserKeyCreate(LoginRequiredMixin, SuccessMessageMixin, CreateView):
return
kwargs
return
kwargs
class
ConnectCommandDetail
(
LoginRequiredMixin
,
SuccessMessageMixin
,
UpdateView
):
model
=
ConnectCommand
template_name
=
"dashboard/connect-command-edit.html"
form_class
=
ConnectCommandForm
success_message
=
_
(
"Successfully modified command template."
)
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
object
=
self
.
get_object
()
if
object
.
user
!=
request
.
user
:
raise
PermissionDenied
()
return
super
(
ConnectCommandDetail
,
self
)
.
get
(
request
,
*
args
,
**
kwargs
)
def
get_success_url
(
self
):
return
reverse_lazy
(
"dashboard.views.connect-command-detail"
,
kwargs
=
self
.
kwargs
)
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
object
=
self
.
get_object
()
if
object
.
user
!=
request
.
user
:
raise
PermissionDenied
()
return
super
(
ConnectCommandDetail
,
self
)
.
post
(
request
,
args
,
kwargs
)
def
get_form_kwargs
(
self
):
kwargs
=
super
(
ConnectCommandDetail
,
self
)
.
get_form_kwargs
()
kwargs
[
'user'
]
=
self
.
request
.
user
return
kwargs
class
ConnectCommandDelete
(
LoginRequiredMixin
,
DeleteView
):
model
=
ConnectCommand
def
get_success_url
(
self
):
return
reverse
(
"dashboard.views.profile-preferences"
)
def
get_template_names
(
self
):
if
self
.
request
.
is_ajax
():
return
[
'dashboard/confirm/ajax-delete.html'
]
else
:
return
[
'dashboard/confirm/base-delete.html'
]
def
delete
(
self
,
request
,
*
args
,
**
kwargs
):
object
=
self
.
get_object
()
if
object
.
user
!=
request
.
user
:
raise
PermissionDenied
()
object
.
delete
()
success_url
=
self
.
get_success_url
()
success_message
=
_
(
"Command template successfully deleted."
)
if
request
.
is_ajax
():
return
HttpResponse
(
json
.
dumps
({
'message'
:
success_message
}),
content_type
=
"application/json"
,
)
else
:
messages
.
success
(
request
,
success_message
)
return
HttpResponseRedirect
(
success_url
)
class
ConnectCommandCreate
(
LoginRequiredMixin
,
SuccessMessageMixin
,
CreateView
):
model
=
ConnectCommand
form_class
=
ConnectCommandForm
template_name
=
"dashboard/connect-command-create.html"
success_message
=
_
(
"Successfully created a new command template."
)
def
get_success_url
(
self
):
return
reverse_lazy
(
"dashboard.views.profile-preferences"
)
def
get_form_kwargs
(
self
):
kwargs
=
super
(
ConnectCommandCreate
,
self
)
.
get_form_kwargs
()
kwargs
[
'user'
]
=
self
.
request
.
user
return
kwargs
class
HelpView
(
TemplateView
):
class
HelpView
(
TemplateView
):
def
get_context_data
(
self
,
*
args
,
**
kwargs
):
def
get_context_data
(
self
,
*
args
,
**
kwargs
):
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment