56 Commits
v1.03 ... cmdb

Author SHA1 Message Date
RobbieHan
fc5a19bdbf domainname 2019-04-12 14:06:12 +08:00
RobbieHan
9430fa0781 network_asset 2019-04-08 16:17:54 +08:00
RobbieHan
0e7bb2678f natrule 2019-04-02 18:34:39 +08:00
RobbieHan
667b4bc29c network asset 2019-04-01 10:26:16 +08:00
RobbieHan
24a3f4c107 modify cmdb models 2019-03-31 18:18:18 +08:00
RobbieHan
9216755a5b Merge branch '2-cmdb' 2019-02-26 12:52:22 +08:00
RobbieHan
892ec0b2de README.md 2019-02-26 12:51:45 +08:00
RobbieHan
05b1cc6665 Merge branch '2-cmdb' 2019-02-26 12:44:07 +08:00
RobbieHan
8c00fdd315 add README.md&config 2019-02-26 12:43:05 +08:00
RobbieHan
3182032540 Merge branch '2-cmdb' 2019-02-23 20:44:35 +08:00
RobbieHan
2646254e52 upload&auto updatee 2019-02-21 20:28:46 +08:00
RobbieHan
e3490b3af7 tags&filters 2019-02-17 22:40:06 +08:00
RobbieHan
b58be33aac change_compare 2019-02-12 21:21:00 +08:00
RobbieHan
f1dde180fb device2connection 2019-02-12 16:37:24 +08:00
RobbieHan
6765fa8b66 deviceinfo 2019-01-31 00:57:11 +08:00
RobbieHan
6b413bc114 upgrade django 2019-01-30 14:55:11 +08:00
RobbieHan
bca383fbb4 Merge branch '2-cmdb' 2019-01-30 14:49:04 +08:00
RobbieHan
ad74ed1802 update pyyaml 2019-01-30 14:46:36 +08:00
RobbieHan
e6272ccaab master 2-cmdb 2019-01-25 14:43:52 +08:00
RobbieHan
a36f8d74f6 cabinet 2019-01-25 14:34:42 +08:00
RobbieHan
cea6fa7cba celery&flower&supervisor 2019-01-18 16:50:05 +08:00
RobbieHan
eb10ffe9be device scan 2019-01-14 02:12:08 +08:00
RobbieHan
9c204933e8 scan&login execution 2019-01-11 17:53:32 +08:00
RobbieHan
371b1ebbe3 signals 2019-01-04 19:54:50 +08:00
RobbieHan
9d0bd95b69 device models 2019-01-03 20:54:17 +08:00
RobbieHan
4fbdc88743 scan config 2018-12-29 20:19:34 +08:00
RobbieHan
860ae14d4c code management 2018-12-19 21:41:50 +08:00
RobbieHan
00f7112b67 logging 2018-12-15 15:34:27 +08:00
RobbieHan
ba936d7f9e create cmdb app 2018-12-10 20:32:45 +08:00
RobbieHan
c399b77703 remove .idea 2018-11-27 12:11:16 +08:00
RobbieHan
894fe29e15 change to mysql 2018-11-27 01:05:11 +08:00
RobbieHan
602edea8f8 deployment setting 2018-11-26 21:10:43 +08:00
RobbieHan
cbe0c54472 merge 1-rbac 2018-11-17 14:07:10 +08:00
RobbieHan
9d149a6882 system config 2018-11-17 12:12:05 +08:00
RobbieHan
0b47673759 system config 2018-11-16 23:03:09 +08:00
RobbieHan
e2edef0af2 rbac config 2018-11-16 20:15:05 +08:00
RobbieHan
1f94ffa857 role2menu 2018-11-14 23:40:06 +08:00
RobbieHan
134ea4426f role2user 2018-11-14 19:56:01 +08:00
RobbieHan
04d01aa273 role2uesr 2018-11-14 18:49:00 +08:00
RobbieHan
4570252f6d role create&update&list 2018-11-14 13:44:27 +08:00
RobbieHan
0f6bd53883 menu update 2018-11-12 22:05:32 +08:00
RobbieHan
8c3158b18e menu list 2018-11-10 19:36:12 +08:00
RobbieHan
cd879f38d7 menu create 2018-11-09 23:29:43 +08:00
RobbieHan
1526767e87 menu create 2018-11-09 23:22:13 +08:00
RobbieHan
6dfb9197e6 user management 2018-11-06 12:52:03 +08:00
RobbieHan
64a8fab611 user management 2018-11-06 12:42:31 +08:00
RobbieHan
9bffe64477 idea 2018-10-23 21:31:49 +08:00
RobbieHan
45630bd451 structure2user 2018-10-23 19:10:55 +08:00
RobbieHan
a328e88122 workspace 2018-10-21 16:54:15 +08:00
RobbieHan
2511a3c066 workspace 2018-10-21 16:53:08 +08:00
RobbieHan
b85d89642d structure update & delete 2018-10-21 16:46:38 +08:00
RobbieHan
832c55ab34 structure list 2018-10-20 14:32:30 +08:00
RobbieHan
f72efee2e1 structure create 2018-10-19 19:22:54 +08:00
RobbieHan
2bba133771 SystemIndex 2018-10-17 23:27:14 +08:00
RobbieHan
2261bc6b3a workspace 2018-10-17 15:23:04 +08:00
RobbieHan
005b62a01e workspace 2018-10-17 15:22:21 +08:00
125 changed files with 14007 additions and 474 deletions

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
*.js linguist-language=python
*.css linguist-language=python
*.html linguist-language=python

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.idea

View File

@@ -1,3 +0,0 @@
<component name="ProjectDictionaryState">
<dictionary name="RobbieHan" />
</component>

4
.idea/misc.xml generated
View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6 (sandboxMP)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/sandboxMP.iml" filepath="$PROJECT_DIR$/.idea/sandboxMP.iml" />
</modules>
</component>
</project>

32
.idea/sandboxMP.iml generated
View File

@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$" />
<option name="settingsModule" value="sandboxMP/settings.py" />
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="doNotUseTestRunner" value="false" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/apps" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/../sandboxMP\templates" />
</list>
</option>
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
</component>
</module>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

661
.idea/workspace.xml generated
View File

@@ -1,103 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="80da5b45-7eca-459a-bbe3-5443bc141768" name="Default" comment="">
<change beforePath="" afterPath="$PROJECT_DIR$/apps/system/__init__.py" />
<change beforePath="" afterPath="$PROJECT_DIR$/apps/system/admin.py" />
<change beforePath="" afterPath="$PROJECT_DIR$/apps/system/apps.py" />
<change beforePath="" afterPath="$PROJECT_DIR$/apps/system/migrations/0001_initial.py" />
<change beforePath="" afterPath="$PROJECT_DIR$/apps/system/migrations/__init__.py" />
<change beforePath="" afterPath="$PROJECT_DIR$/apps/system/models.py" />
<change beforePath="" afterPath="$PROJECT_DIR$/apps/system/tests.py" />
<change beforePath="" afterPath="$PROJECT_DIR$/apps/system/views.py" />
<change beforePath="" afterPath="$PROJECT_DIR$/apps/system/views_user.py" />
<change beforePath="$PROJECT_DIR$/.idea/sandboxMP.iml" afterPath="$PROJECT_DIR$/.idea/sandboxMP.iml" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" afterPath="$PROJECT_DIR$/.idea/workspace.xml" />
<change beforePath="$PROJECT_DIR$/db.sqlite3" afterPath="$PROJECT_DIR$/db.sqlite3" />
<change beforePath="$PROJECT_DIR$/sandboxMP/settings.py" afterPath="$PROJECT_DIR$/sandboxMP/settings.py" />
<change beforePath="$PROJECT_DIR$/sandboxMP/urls.py" afterPath="$PROJECT_DIR$/sandboxMP/urls.py" />
<list default="true" id="6b2c11e3-c0ac-49bf-9d4d-fae9ba173955" name="Default Changelist" comment="">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/apps/cmdb/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/apps/cmdb/forms.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/apps/cmdb/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/apps/cmdb/models.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/apps/cmdb/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/apps/cmdb/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/apps/cmdb/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/apps/cmdb/views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/apps/cmdb/views_eam.py" beforeDir="false" afterPath="$PROJECT_DIR$/apps/cmdb/views_eam.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/cmdb/cmdb_index.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/cmdb/cmdb_index.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/cmdb/domainname.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/cmdb/domainname.html" afterDir="false" />
</list>
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="TRACKING_ENABLED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="CoverageDataManager">
<SUITE FILE_PATH="coverage/sandboxMP$views_eam.coverage" NAME="views_eam Coverage Results" MODIFIED="1554950533334" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/apps/cmdb" />
</component>
<component name="DjangoConsoleOptions" custom-start-script="import sys; print('Python %s on %s' % (sys.version, sys.platform))&#10;import django; print('Django %s' % django.get_version())&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;if 'setup' in dir(django): django.setup()&#10;import django_manage_shell; django_manage_shell.run(PROJECT_ROOT)">
<option name="myCustomStartScript" value="import sys; print('Python %s on %s' % (sys.version, sys.platform))&#10;import django; print('Django %s' % django.get_version())&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;if 'setup' in dir(django): django.setup()&#10;import django_manage_shell; django_manage_shell.run(PROJECT_ROOT)" />
</component>
<component name="FileEditorManager">
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
<file leaf-file-name="settings.py" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/sandboxMP/settings.py">
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/apps/cmdb/models.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="584">
<caret line="131" column="44" lean-forward="false" selection-start-line="131" selection-start-column="44" selection-end-line="131" selection-end-column="44" />
<state relative-caret-position="2567">
<caret line="156" column="4" selection-start-line="156" selection-start-column="4" selection-end-line="156" selection-end-column="9" />
<folding>
<element signature="e#310#319#0" expanded="true" />
<element signature="e#0#29#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
<file leaf-file-name="views_user.py" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/apps/system/views_user.py">
<file pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/templates/cmdb/cmdb_index.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="221">
<caret line="13" column="0" lean-forward="true" selection-start-line="13" selection-start-column="0" selection-end-line="13" selection-end-column="0" />
<state relative-caret-position="374">
<caret line="50" column="97" selection-start-line="50" selection-start-column="97" selection-end-line="50" selection-end-column="97" />
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/apps/cmdb/views_eam.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="7514">
<caret line="460" selection-start-line="460" selection-end-line="460" />
<folding>
<element signature="e#78#113#0" expanded="true" />
<element signature="e#0#9#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
<file leaf-file-name="urls.py" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/sandboxMP/urls.py">
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/apps/custom.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="340">
<caret line="20" column="0" lean-forward="true" selection-start-line="20" selection-start-column="0" selection-end-line="20" selection-end-column="0" />
<folding />
<state relative-caret-position="1649">
<caret line="107" column="26" selection-start-line="107" selection-start-column="26" selection-end-line="107" selection-end-column="26" />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="mixin.py" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/apps/system/mixin.py">
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/apps/cmdb/forms.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="187">
<caret line="11" column="35" lean-forward="false" selection-start-line="11" selection-start-column="35" selection-end-line="11" selection-end-column="35" />
<folding />
<state relative-caret-position="2788">
<caret line="169" column="45" selection-start-line="169" selection-start-column="45" selection-end-line="169" selection-end-column="45" />
<folding>
<element signature="e#73#82#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
<file leaf-file-name="forms.py" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/apps/system/forms.py">
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/apps/cmdb/urls.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="153">
<caret line="9" column="31" lean-forward="false" selection-start-line="9" selection-start-column="31" selection-end-line="9" selection-end-column="31" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="views.py" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/apps/system/views.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="models.py" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/apps/system/models.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="549">
<caret line="72" column="24" lean-forward="true" selection-start-line="72" selection-start-column="24" selection-end-line="72" selection-end-column="24" />
<state relative-caret-position="1037">
<caret line="64" column="95" selection-start-line="64" selection-start-column="95" selection-end-line="64" selection-end-column="95" />
<folding>
<element signature="e#0#28#0" expanded="true" />
</folding>
@@ -105,77 +91,80 @@
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/templates/cmdb/domainname.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="4505">
<caret line="265" column="38" lean-forward="true" selection-start-line="265" selection-start-column="38" selection-end-line="265" selection-end-column="38" />
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Python Script" />
<option value="JavaScript File" />
</list>
</option>
</component>
<component name="FindInProjectRecents">
<findStrings>
<find>all_cabinet</find>
<find>disk</find>
<find>passwor</find>
<find>个人中心</find>
<find>Monthly Recap Report</find>
<find>访问来源</find>
<find>Latest Orders</find>
<find>Browser Usage</find>
<find>get_context_data</find>
</findStrings>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="IdeDocumentHistory">
<option name="CHANGED_PATHS">
<list>
<option value="$PROJECT_DIR$/templates/base-left.html" />
<option value="$PROJECT_DIR$/templates/head-footer.html" />
<option value="$PROJECT_DIR$/apps/system/models.py" />
<option value="$PROJECT_DIR$/apps/system/forms.py" />
<option value="$PROJECT_DIR$/apps/system/mixin.py" />
<option value="$PROJECT_DIR$/apps/system/views_user.py" />
<option value="$PROJECT_DIR$/templates/cmdb/natrule.html" />
<option value="$PROJECT_DIR$/templates/cmdb/natrule_form.html" />
<option value="$PROJECT_DIR$/templates/cmdb/networkasset_form.html" />
<option value="$PROJECT_DIR$/static/dist/css/AdminLTE.css" />
<option value="$PROJECT_DIR$/templates/cmdb/network_asset.html" />
<option value="$PROJECT_DIR$/templates/system/users/user.html" />
<option value="$PROJECT_DIR$/sandboxMP/settings.py" />
<option value="$PROJECT_DIR$/sandboxMP/urls.py" />
<option value="$PROJECT_DIR$/apps/system/urls.py" />
<option value="$PROJECT_DIR$/apps/system/views_user.py" />
<option value="$PROJECT_DIR$/templates/head-footer.html" />
<option value="$PROJECT_DIR$/templates/system/users/personal_passwd_change.html" />
<option value="$PROJECT_DIR$/templates/base-static.html" />
<option value="$PROJECT_DIR$/static/plugins/echarts/echarts.min.js" />
<option value="$PROJECT_DIR$/static/plugins/highcharts/highcharts.js" />
<option value="$PROJECT_DIR$/templates/index2.html" />
<option value="$PROJECT_DIR$/apps/cmdb/views.py" />
<option value="$PROJECT_DIR$/apps/cmdb/forms.py" />
<option value="$PROJECT_DIR$/apps/cmdb/models.py" />
<option value="$PROJECT_DIR$/apps/cmdb/views_eam.py" />
<option value="$PROJECT_DIR$/apps/cmdb/urls.py" />
<option value="$PROJECT_DIR$/templates/cmdb/domainname_form.html" />
<option value="$PROJECT_DIR$/templates/cmdb/domainname.html" />
<option value="$PROJECT_DIR$/templates/cmdb/domainname2natrule.html" />
<option value="$PROJECT_DIR$/templates/cmdb/cmdb_index.html" />
</list>
</option>
</component>
<component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsGulpfileManager">
<detection-done>true</detection-done>
<sorting>DEFINITION_ORDER</sorting>
<component name="ProjectFrameBounds">
<option name="y" value="23" />
<option name="width" value="1680" />
<option name="height" value="973" />
</component>
<component name="ProjectFrameBounds" extendedState="6">
<option name="x" value="-8" />
<option name="y" value="-8" />
<option name="width" value="1936" />
<option name="height" value="1056" />
</component>
<component name="ProjectInspectionProfilesVisibleTreeState">
<entry key="Project Default">
<profile-state>
<expanded-state>
<State>
<id />
</State>
</expanded-state>
<selected-state>
<State>
<id>AngularJS</id>
</State>
</selected-state>
</profile-state>
</entry>
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectView">
<navigator currentView="ProjectPane" proportions="" version="1">
<flattenPackages />
<showMembers />
<showModules />
<showLibraryContents />
<hideEmptyPackages />
<abbreviatePackageNames />
<autoscrollToSource />
<autoscrollFromSource />
<sortByType />
<manualOrder />
<navigator proportions="" version="1">
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="Scratches" />
<pane id="Scope" />
<pane id="ProjectPane">
<subPane>
@@ -193,12 +182,23 @@
<item name="sandboxMP" type="b2602c69:ProjectViewProjectNode" />
<item name="sandboxMP" type="462c0819:PsiDirectoryNode" />
<item name="apps" type="462c0819:PsiDirectoryNode" />
<item name="system" type="462c0819:PsiDirectoryNode" />
<item name="cmdb" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="sandboxMP" type="b2602c69:ProjectViewProjectNode" />
<item name="sandboxMP" type="462c0819:PsiDirectoryNode" />
<item name="config" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="sandboxMP" type="b2602c69:ProjectViewProjectNode" />
<item name="sandboxMP" type="462c0819:PsiDirectoryNode" />
<item name="templates" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="sandboxMP" type="b2602c69:ProjectViewProjectNode" />
<item name="sandboxMP" type="462c0819:PsiDirectoryNode" />
<item name="templates" type="462c0819:PsiDirectoryNode" />
<item name="cmdb" type="462c0819:PsiDirectoryNode" />
</path>
</expand>
<select />
@@ -207,26 +207,24 @@
</panes>
</component>
<component name="PropertiesComponent">
<property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
<property name="WebServerToolWindowFactoryState" value="true" />
<property name="WebServerToolWindowPanel.toolwindow.highlight.mappings" value="true" />
<property name="WebServerToolWindowPanel.toolwindow.highlight.symlinks" value="true" />
<property name="WebServerToolWindowPanel.toolwindow.show.date" value="false" />
<property name="WebServerToolWindowPanel.toolwindow.show.permissions" value="false" />
<property name="WebServerToolWindowPanel.toolwindow.show.size" value="false" />
<property name="settings.editor.selected.configurable" value="com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
<property name="WebServerToolWindowFactoryState" value="false" />
</component>
<component name="PyConsoleOptionsProvider">
<option name="myPythonConsoleState">
<console-settings is-module-sdk="true">
<option name="myUseModuleSdk" value="true" />
</console-settings>
</option>
</component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="D:\ProjectFile\sandboxMP\apps" />
</key>
<key name="CopyFile.RECENT_KEYS">
<recent name="D:\ProjectFile\sandboxMP\media" />
<recent name="D:\ProjectFile\sandboxMP\static" />
<recent name="D:\ProjectFile\sandboxMP\templates" />
<recent name="$PROJECT_DIR$/templates/cmdb" />
<recent name="$PROJECT_DIR$/static/plugins/echarts" />
<recent name="$PROJECT_DIR$/templates" />
<recent name="$PROJECT_DIR$/templates/system/users" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/static/plugins/echarts" />
<recent name="$PROJECT_DIR$/templates/cmdb" />
</key>
</component>
<component name="RunDashboard">
@@ -241,224 +239,369 @@
</list>
</option>
</component>
<component name="RunManager">
<configuration name="sandboxMP" type="Python.DjangoServer" factoryName="Django server">
<component name="RunManager" selected="Django server.cmdb">
<configuration name="views_eam" type="PythonConfigurationType" factoryName="Python" temporary="true">
<module name="sandboxMP" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/apps/cmdb" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/apps/cmdb/views_eam.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="cmdb" type="Python.DjangoServer" factoryName="Django server">
<module name="sandboxMP" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="DJANGO_SETTINGS_MODULE" value="sandboxMP.settings" />
</envs>
<option name="SDK_HOME" value="" />
<option name="SDK_HOME" value="sftp://root@49.4.52.5:6622/root/.virtualenvs/sandboxMP/bin/python" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<module name="sandboxMP" />
<option name="launchJavascriptDebuger" value="false" />
<option name="port" value="8000" />
<option name="host" value="" />
<option name="host" value="192.168.0.242" />
<option name="additionalOptions" value="" />
<option name="browserUrl" value="" />
<option name="runTestServer" value="false" />
<option name="runNoReload" value="false" />
<option name="useCustomRunCommand" value="false" />
<option name="customRunCommand" value="" />
<method v="2" />
</configuration>
</component>
<component name="ShelveChangesManager" show_recycled="false">
<option name="remove_strategy" value="false" />
<recent_temporary>
<list>
<item itemvalue="Python.views_eam" />
</list>
</recent_temporary>
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="80da5b45-7eca-459a-bbe3-5443bc141768" name="Default" comment="" />
<created>1539589405377</created>
<changelist id="6b2c11e3-c0ac-49bf-9d4d-fae9ba173955" name="Default Changelist" comment="" />
<created>1554181508462</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1539589405377</updated>
<updated>1554181508462</updated>
<workItem from="1554181511110" duration="8527000" />
<workItem from="1554199194064" duration="7844000" />
<workItem from="1554255904237" duration="12068000" />
<workItem from="1554305728868" duration="446000" />
<workItem from="1554533797978" duration="10677000" />
<workItem from="1554604330772" duration="12685000" />
<workItem from="1554711227466" duration="4577000" />
<workItem from="1554786790020" duration="3729000" />
<workItem from="1554871617441" duration="1241000" />
<workItem from="1554897056464" duration="7503000" />
<workItem from="1554949204301" duration="20322000" />
<workItem from="1554999607936" duration="4491000" />
<workItem from="1555037177006" duration="1560000" />
</task>
<servers />
</component>
<component name="TimeTrackingManager">
<option name="totallyTimeSpent" value="95670000" />
</component>
<component name="ToolWindowManager">
<frame x="-8" y="-8" width="1936" height="1056" extended-state="6" />
<frame x="0" y="23" width="1680" height="973" extended-state="0" />
<editor active="true" />
<layout>
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
<window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="true" content_ui="tabs" />
<window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.21762785" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
<window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
<window_info id="Python Console" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.3297062" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
<window_info id="manage.py@sandboxMP" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.28073993" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Mongo Explorer" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.3297062" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
<window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.17590618" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
<window_info id="Docker" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="false" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
<window_info id="Database" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="SciView" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" />
<window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
<window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
<window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
<window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info content_ui="combo" id="Project" order="0" visible="true" weight="0.18192919" />
<window_info id="Structure" order="1" side_tool="true" weight="0.25" />
<window_info id="Favorites" order="2" side_tool="true" />
<window_info anchor="bottom" id="Message" order="0" />
<window_info anchor="bottom" id="Find" order="1" />
<window_info active="true" anchor="bottom" id="Run" order="2" visible="true" weight="0.2644722" />
<window_info anchor="bottom" id="Debug" order="3" weight="0.24063565" />
<window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
<window_info anchor="bottom" id="TODO" order="6" />
<window_info anchor="bottom" id="Docker" order="7" show_stripe_button="false" />
<window_info anchor="bottom" id="Version Control" order="8" />
<window_info anchor="bottom" id="File Transfer" order="9" weight="0.17366628" />
<window_info anchor="bottom" id="Database Changes" order="10" />
<window_info anchor="bottom" id="Terminal" order="11" />
<window_info anchor="bottom" id="Event Log" order="12" side_tool="true" />
<window_info anchor="bottom" id="Python Console" order="13" />
<window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
<window_info anchor="right" id="Remote Host" order="3" visible="true" weight="0.1898657" />
<window_info anchor="right" id="SciView" order="4" />
<window_info anchor="right" id="Database" order="5" />
</layout>
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" />
</component>
<component name="VcsContentAnnotationSettings">
<option name="myLimit" value="2678400000" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager />
<watches-manager />
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/templates/cmdb/natrule.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="295">
<caret line="63" column="82" selection-start-line="63" selection-start-column="82" selection-end-line="63" selection-end-column="82" />
</state>
</provider>
</entry>
<entry file="file://$USER_HOME$/Library/Caches/PyCharm2018.3/remote_sources/-1817203215/-2003647482/django/forms/forms.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="671">
<caret line="408" selection-start-line="408" selection-end-line="408" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/templates/cmdb/scan_config.html">
<provider selected="true" editor-type-id="text-editor" />
</entry>
<entry file="file://$PROJECT_DIR$/templates/cmdb/networkasset_form.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="315">
<caret line="39" column="66" lean-forward="true" selection-start-line="39" selection-start-column="66" selection-end-line="39" selection-end-column="66" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/static/dist/css/alt/AdminLTE-without-plugins.min.css">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="102">
<caret line="6" column="26560" selection-start-line="6" selection-start-column="26560" selection-end-line="6" selection-end-column="26560" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/static/dist/css/AdminLTE.css">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="352">
<caret line="1361" column="1" selection-start-line="1361" selection-start-column="1" selection-end-line="1361" selection-end-column="1" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/templates/system/users/passwd_change.html">
<provider selected="true" editor-type-id="text-editor" />
</entry>
<entry file="file://$PROJECT_DIR$/sandboxMP/settings.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding>
<element signature="e#310#319#0" expanded="true" />
</folding>
<state relative-caret-position="445">
<caret line="162" column="31" selection-start-line="162" selection-start-column="31" selection-end-line="162" selection-end-column="31" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/templates/system/users/login.html">
<entry file="file://$PROJECT_DIR$/templates/system/users/personal_passwd_change.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
<state relative-caret-position="496">
<caret line="69" lean-forward="true" selection-start-line="69" selection-end-line="69" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/static/bootstrap/js/npm.js">
<entry file="file://$PROJECT_DIR$/templates/cmdb/natrule_form.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="204">
<caret line="12" column="28" lean-forward="true" selection-start-line="12" selection-start-column="28" selection-end-line="12" selection-end-column="28" />
<state relative-caret-position="200">
<caret line="107" column="14" selection-start-line="107" selection-start-column="14" selection-end-line="107" selection-end-column="14" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/templates/index.html">
<entry file="file://$PROJECT_DIR$/templates/base-static.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="238">
<caret line="14" column="14" lean-forward="true" selection-start-line="14" selection-start-column="14" selection-end-line="14" selection-end-column="14" />
<state relative-caret-position="2142">
<caret line="126" column="7" lean-forward="true" selection-start-line="126" selection-start-column="7" selection-end-line="126" selection-end-column="7" />
</state>
</provider>
</entry>
<entry file="file://$USER_HOME$/Envs/sandboxMP/Lib/site-packages/django/views/generic/base.py">
<entry file="file://$PROJECT_DIR$/templates/system/users/user.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="171">
<caret line="143" column="6" lean-forward="false" selection-start-line="143" selection-start-column="6" selection-end-line="143" selection-end-column="6" />
<folding />
<state relative-caret-position="200">
<caret line="98" column="30" selection-start-line="98" selection-start-column="8" selection-end-line="98" selection-end-column="30" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/templates/head-footer.html">
<entry file="file://$PROJECT_DIR$/static/plugins/echarts/echarts.min.js" />
<entry file="file://$PROJECT_DIR$/apps/system/urls.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1420">
<caret line="94" column="22" lean-forward="true" selection-start-line="94" selection-start-column="22" selection-end-line="94" selection-end-column="22" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/templates/base-layer.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="476">
<caret line="28" column="22" lean-forward="true" selection-start-line="28" selection-start-column="22" selection-end-line="28" selection-end-column="22" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/templates/base-left.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="483">
<caret line="36" column="13" lean-forward="true" selection-start-line="36" selection-start-column="13" selection-end-line="36" selection-end-column="13" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/apps/system/views.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/apps/system/models.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="549">
<caret line="72" column="24" lean-forward="true" selection-start-line="72" selection-start-column="24" selection-end-line="72" selection-end-column="24" />
<state relative-caret-position="567">
<caret line="40" selection-start-line="40" selection-end-line="40" />
<folding>
<element signature="e#0#28#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/apps/system/forms.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="153">
<caret line="9" column="31" lean-forward="false" selection-start-line="9" selection-start-column="31" selection-end-line="9" selection-end-column="31" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/apps/system/mixin.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="187">
<caret line="11" column="35" lean-forward="false" selection-start-line="11" selection-start-column="35" selection-end-line="11" selection-end-column="35" />
<folding />
</state>
</provider>
<entry file="file://$PROJECT_DIR$/static/plugins/highcharts/highcharts.js" />
<entry file="file://$PROJECT_DIR$/static/plugins/echarts/echarts.js">
<provider selected="true" editor-type-id="text-editor" />
</entry>
<entry file="file://$PROJECT_DIR$/apps/system/views_user.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="221">
<caret line="13" column="0" lean-forward="true" selection-start-line="13" selection-start-column="0" selection-end-line="13" selection-end-column="0" />
<state relative-caret-position="-331">
<caret line="27" column="45" lean-forward="true" selection-start-line="27" selection-start-column="45" selection-end-line="27" selection-end-column="45" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/templates/index2.html" />
<entry file="file://$PROJECT_DIR$/apps/system/forms.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-1082" />
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/static/bootstrap/css/bootstrap.css">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="216">
<caret line="250" column="3" selection-start-line="250" selection-start-column="3" selection-end-line="250" selection-end-column="3" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/templates/head-footer.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="462">
<caret line="114" column="40" selection-start-line="114" selection-start-column="14" selection-end-line="114" selection-end-column="40" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/templates/cmdb/deviceinfo_detail.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-2183" />
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/apps/system/views_structure.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-382">
<caret line="17" selection-start-line="17" selection-end-line="17" />
<folding>
<element signature="e#78#113#0" expanded="true" />
<element signature="e#83#94#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/sandboxMP/settings.py">
<entry file="file://$PROJECT_DIR$/apps/cmdb/views.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="584">
<caret line="131" column="44" lean-forward="false" selection-start-line="131" selection-start-column="44" selection-end-line="131" selection-end-column="44" />
<state relative-caret-position="736">
<caret line="49" column="38" selection-start-line="49" selection-start-column="38" selection-end-line="49" selection-end-column="38" />
<folding>
<element signature="e#310#319#0" expanded="true" />
<element signature="e#0#9#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/sandboxMP/urls.py">
<entry file="file://$PROJECT_DIR$/templates/cmdb/deviceinfo_form.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="340">
<caret line="20" column="0" lean-forward="true" selection-start-line="20" selection-start-column="0" selection-end-line="20" selection-end-column="0" />
<folding />
<state relative-caret-position="87">
<caret line="77" selection-start-line="77" selection-end-line="88" selection-end-column="22" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/apps/utils/sandbox_utils.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="42">
<caret line="77" lean-forward="true" selection-start-line="77" selection-end-line="77" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/templates/cmdb/deviceinfo.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="3134">
<caret line="354" selection-start-line="354" selection-end-line="356" selection-end-column="5" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/templates/index.html">
<provider selected="true" editor-type-id="text-editor" />
</entry>
<entry file="file://$PROJECT_DIR$/templates/cmdb/network_asset.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="634">
<caret line="161" column="51" selection-start-line="161" selection-start-column="51" selection-end-line="161" selection-end-column="51" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/templates/cmdb/domainname2natrule.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="425">
<caret line="25" column="17" selection-start-line="4" selection-start-column="4" selection-end-line="29" selection-end-column="12" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/apps/cmdb/models.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="2567">
<caret line="156" column="4" selection-start-line="156" selection-start-column="4" selection-end-line="156" selection-end-column="9" />
<folding>
<element signature="e#0#29#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/apps/cmdb/views_eam.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="7514">
<caret line="460" selection-start-line="460" selection-end-line="460" />
<folding>
<element signature="e#0#9#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/apps/custom.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1649">
<caret line="107" column="26" selection-start-line="107" selection-start-column="26" selection-end-line="107" selection-end-column="26" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/apps/cmdb/forms.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="2788">
<caret line="169" column="45" selection-start-line="169" selection-start-column="45" selection-end-line="169" selection-end-column="45" />
<folding>
<element signature="e#73#82#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/apps/cmdb/urls.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1037">
<caret line="64" column="95" selection-start-line="64" selection-start-column="95" selection-end-line="64" selection-end-column="95" />
<folding>
<element signature="e#0#28#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/templates/cmdb/domainname_form.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="293">
<caret line="35" column="22" selection-start-line="35" selection-start-column="22" selection-end-line="35" selection-end-column="22" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/templates/cmdb/domainname.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="4505">
<caret line="265" column="38" lean-forward="true" selection-start-line="265" selection-start-column="38" selection-end-line="265" selection-end-column="38" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/templates/cmdb/cmdb_index.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="374">
<caret line="50" column="97" selection-start-line="50" selection-start-column="97" selection-end-line="50" selection-end-column="97" />
</state>
</provider>
</entry>
</component>
<component name="masterDetails">
<states>
<state key="ScopeChooserConfigurable.UI">
<settings>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
</states>
</component>
</project>

777
README.md Normal file
View File

@@ -0,0 +1,777 @@
> 项目说明本项目是根据客户需求开发的一套自动化运维项目在征得客户同意的情况下将部分功能开源。本项目配套有完整开发文档详细记录了项目整个实现过程可作为学习Django的参考文档。文档以项目为主线逐步介绍了Django基本类视图、通用类视图和自定义类视图涵盖了Django核心组件和扩展模块的使用包括logging 、signals、simple-history等同时扩展了celery和channel来实现分布式任务队列和websocket功能等利用ansible进行集中管理和自动化任务执行。
# 1 运行环境配置
## 1.1 项目运行环境
- 系统版本: Centos7(CentOS Linux release 7.6.1810)
- Python版本Python 3.6.6 (default, Jan 30 2019, 21:53:32)
- Django版本django 2.1.5
- 数据库: mysql5.7/mongo3.4/redis3.2
- 进程管理工具supervisorctl 3.1.4
- 扫描工具Nmap version 6.40
- 其他依赖sandboxMP/requirements/pro.txt
## 1.2 Python虚拟环境配置
准备一台centos7的系统作为项目运行的服务器系统完成基本网络设置、防火墙设置和系统基本优化设置。以下内容都是在Centos7系统命令行中进行操作
1、基础配置使用ssh远程工具我用的是secureCRT连接准备好的Centos7系统完成以下设置
```
# 1.修改系统登陆后的提示信息。
# 将motd内容改为sandboxMP 172.16.3.200 (地址设置成你登陆的系统地址,上下各空一行);
# 这样在登陆系统是的时候就可以看到上面的提示信息,防止登错系统。
[root@template ~]$ vim /etc/motd
sandboxMP 192.168.31.200
# 2.修改主机名exit退出系统按回车重新连接系统就可以看到新的提示信息和主机名
[root@template ~]$ hostnamectl set-hostname sandboxmp
[root@template ~]$ exit
```
2、安装python3.6系统中默认带有python2.7项目中使用的是python3.6.6
```
# 1.可以去官网下载python3.6.6(Source release Gzip)也可以在linux下使用wget下载
[root@sandboxmp ~]$ yum -y install wget
[root@sandboxmp ~]$ wget https://www.python.org/ftp/python/3.6.6/Python-3.6.6.tgz
# 2.安装pip和环境依赖包
[root@sandboxmp ~]$ yum -y install epel-release
[root@sandboxmp ~]$ yum -y install python-pip
[root@sandboxmp ~]$ pip install --upgrade pip
[root@sandboxmp ~]$ yum install -y zlib zlib-devel --setopt=protected_multilib=false
[root@sandboxmp ~]$ yum install -y bzip2 bzip2-devel openssl openssl-devel ncurses ncurses-devel sqlite sqlite-devel readline readline-devel gcc make python-devel
# 3.安装python3.6(编译安装完成执行 echo $? 返回0安装成功否则安装出错)
[root@sandboxmp ~]$ tar -zvxf Python-3.6.6.tgz
[root@sandboxmp ~]$ cd Python-3.6.6
[root@sandboxmp Python-3.6.6 ~]$ ./configure
[root@sandboxmp Python-3.6.6 ~]$ make && make install
[root@sandboxmp Python-3.6.6 ~]$ echo $?
0
# 4.安装成功后就可以使用python3环境
[root@sandboxmp Python-3.6.6 ~]$ cd ~
[root@sandboxmp ~]$ python3
Python 3.6.6 (default, Nov 26 2018, 16:19:34)
''''''
>>> exit() # 退出python环境
```
3、安装python虚拟环境在项目中还是使用虚拟环境项目环境互不影响
```
# 1.安装 virtualenv virtualenvwrapper
[root@sandboxmp ~]$ pip install virtualenv virtualenvwrapper
# 2.设置环境变量使用vim编辑.bashrc在最后面添加最后两行内容
[root@sandboxmp ~]$ vim ~/.bashrc
# .bashrc
# User specific aliases and functions
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
# 下面两行是新增加内容
export WORKON_HOME=$HOME/.virtualenvs
source /usr/bin/virtualenvwrapper.sh
# 3.保存修改退出vim运行命令让变量生效
[root@sandboxmp ~]$ source ~/.bashrc
# 4.现在可以在bash窗口使用mkvirtualnev来创建虚拟环境了
[root@sandboxmp ~]$ mkvirtualenv -p /usr/local/bin/python3.6 sandboxMP
Running virtualenv with interpreter /usr/local/bin/python3.6
Using base prefix '/usr/local'
New python executable in /root/.virtualenvs/sandboxMP/bin/python3.6
Also creating executable in /root/.virtualenvs/sandboxMP/bin/python
Installing setuptools, pip, wheel...
done.
......
# 5.创建完成系统自动进入了虚拟环境sandboxMP
(sandboxMP) [root@sandboxmp ~]$
# 6.离开虚拟环境
(sandboxMP) [root@sandboxmp ~]$ deactivate
[root@sandboxmp ~]$
# 7.查看虚拟环境
[root@sandboxmp ~]$ workon
sandboxMP
[root@sandboxmp ~]$
# 8.激活虚拟环境
[root@sandboxmp ~]$ workon sandboxMP
(sandboxMP) [root@sandboxmp ~]$
```
通过上面配置指定使用python3.6来创建一个虚拟环境虚拟环境名称为sandboxMP虚拟环境存放的目录在/root/.virtualenvs目录下这个是由上一步环境变量设置的。
## 1.3 安装数据库
在准备好的Centos7系统中安装mysql/redis/mongodb当然数据库也可以和应用分开单独安装。
### 1.3.1 安装Mysql
在Centos7系统中更新yum源文件安装myslq创建项目数据库
```
# 1.安装Mysql
[root@sandboxmp ~]$ wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm
[root@sandboxmp ~]$ rpm -ivh mysql-community-release-el7-5.noarch.rpm
[root@sandboxmp ~]$ yum -y update
[root@sandboxmp ~]$ yum install -y mysql-server
# 2.修改配置文/etc/my.cnf在[mysql]标签下添加后面三行内容保存退出vim
[root@sandboxmp ~]$ vim /etc/my.cnf
[mysqld]
collation-server = utf8_unicode_ci
character_set_server=utf8
init_connect='SET NAMES utf8'
# 3.启动mysql设置开机启动为root用户设置密码
[root@sandboxmp ~]$ systemctl start mysqld
[root@sandboxmp ~]$ systemctl enable mysqld
[root@sandboxmp ~]$ mysql
mysql> set password for 'root'@'localhost'=password('1234@abcd.com');
mysql> exit
# 4.再次连接Mysql会提示要使用密码连接方式如下回车根据提示输入密码
[root@sandboxmp ~]$ mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
''''''
mysql>
# 5.创建数据库,添加用户和访问授权
mysql> CREATE DATABASE sandboxMP;
mysql> GRANT ALL PRIVILEGES ON sandboxMP.* TO 'ddadmin'@'%' IDENTIFIED BY '1234@abcd.com';
mysql> GRANT ALL PRIVILEGES ON sandboxMP.* TO 'ddadmin'@'localhost' IDENTIFIED BY '1234@abcd.com';
```
### 1.3.2 安装Redis
项目中会使用celery来做分布式任务队列用来处理比较耗时的操作例如发送邮件资产扫描等操作。我们使用redis来做中间人用来存储任务队列和接受返回值。
在服务器中执行下面命令安装redis:
```
# 1.安装扩展源前面我们已经安装过扩展源epel-release和redise
[root@sandboxmp ~]$ yum install epel-release
[root@sandboxmp ~]$ yum install redis
# 2.修改redis配置文件找到bind去掉前面的注释符号ip改为0.0.0.0,保存退出
[root@sandboxmp ~]$ vim /etc/redis.conf
bind 0.0.0.0
# 3.启动redise和设置开机启动
[root@sandboxmp ~]$ systemctl start redis
[root@sandboxmp ~]$ systemctl enable redis
```
### 1.3.3 安装Mongodb
项目中mongodb用来存储日志信息安装方法如下
```
# 1.配置yum源文件添加mongo安装源保存退出
[root@sandboxmp ~]$ vim /etc/yum.repos.d/mongo.repo
[mongodb-org-3.4]
name=MongoDB 3.4 Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/3.4/x86_64/
gpgcheck=0
enabled=1
# 2.安装mongodb 修改bindIp(把地址改成0.0.0.0后,保存退出)
[root@sandboxmp ~]$ yum install -y mongodb-org
[root@sandboxmp ~]$ vim /etc/mongod.conf
bindIp 0.0.0.0 # 修改bindIp
# 3.启动mongodb设置开始启动
[root@sandboxmp ~]$ systemctl start mongod
[root@sandboxmp ~]$ systemctl enable mongod
```
## 1.4 安装项目依赖包
项目运行需要安装必要的依赖包所有依赖包都记录在项目文件中sandboxMP/requirements/pro.txt 。
### 1.4.1 克隆项目到本地
登陆服务器系统使用git命令克隆项目到本地如果没有git命令需要先安装gityum -y install git:
```
# 1.创建/opt/app目录用来存放项目文件
[root@sandboxmp ~]$ mkdir /opt/app
[root@sandboxmp ~]$ cd /opt/app
# 2.克隆项目到本地
[root@sandboxmp app]$ git clone https://github.com/RobbieHan/sandboxMP.git
Cloning into 'sandboxMP'...
remote: Enumerating objects: 134, done.
remote: Counting objects: 100% (134/134), done.
remote: Compressing objects: 100% (57/57), done.
remote: Total 2041 (delta 96), reused 81 (delta 76), pack-reused 1907
Receiving objects: 100% (2041/2041), 7.67 MiB | 16.00 KiB/s, done.
Resolving deltas: 100% (663/663), done.
```
==**对于无法访问github的朋友可以从码云上克隆项目**==
```
# 进入到/opt/app目录执行下面命令从码云克隆项目到本地
[root@sandboxmp app]$ git clone https://gitee.com/RobbieHan/sandboxMP.git
```
### 1.4.2 安装项目依赖包
项目克隆到本地后进入python虚拟环境使用pip安装项目依赖包
```
# 1.进入python虚拟环境
[root@sandboxmp ~]$ workon sandboxMP
# 2.防止安装mysqlclient报错EnvironmentError: mysql_config not found先安装下面两个包
(sandboxMP) [root@sandboxmp ~]$ yum -y install mysql-devel libmysqlclient-dev
# 3.安装 /opt/app/sandboxMP/requirements/pro.txt文件中所有依赖包
(sandboxMP) [root@sandboxmp ~]$ pip install -r /opt/app/sandboxMP/requirements/pro.txt
```
## 1.5 创建数据表&导入初始数据
项目做了自定义的权限管理,更具用户角色权限来动态生成导航数据,所以在运行像目前,需要生成数据表并导入初始数据。
```
# 1. 从模型生成数据表(以下命令在服务器虚拟环境中执行)
(sandboxMP) [root@sandboxmp ~]$cd /opt/app/sandboxMP # 进入项目目录
(sandboxMP) [root@sandboxmp sandboxMP]$ python manage.py makemigrations
Migrations for 'cmdb':
apps/cmdb/migrations/0001_initial.py
- Create model Cabinet
- Create model Code
- Create model ConnectionInfo
- Create model DeviceFile
- Create model DeviceInfo
- Create model DeviceScanInfo
- Create model HistoricalDeviceInfo
- Add field device to devicefile
(sandboxMP) [root@sandboxmp sandboxMP]$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, cmdb, contenttypes, sessions, system
Running migrations:
Applying contenttypes.0001_initial... OK
'''输出信息省略'''
# 2.导入基础数据内容更具提示信息输入密码默认1234@abcd.com
(sandboxMP) [root@sandboxmp sandboxMP]$ mysql -uroot -p sandboxMP < config/basic_data_20190225.sql
Enter password:
(sandboxMP) [root@sandboxmp sandboxMP]$
```
**注意:** sandboxMP/sandboxMP/settings.py中数据库连接设置的是本地地址127.0.0.1,请根据实际配置进行调整。
**环境部署到这里可以在命令行使用manage.py工具临时运行项目。**
==**测试项目运行:**==
```
# 在虚拟环境下进入到项目目录使用manage.py临时运行项目
(sandboxMP) [root@sandboxmp sandboxMP]$ python manage.py runserver 0.0.0.0:80
Performing system checks...
System check identified no issues (0 silenced).
February 25, 2019 - 19:58:19
Django version 2.1.5, using settings 'sandboxMP.settings'
Starting development server at http://0.0.0.0:80/
Quit the server with CONTROL-C.
```
使用你的服务器地址访问项目确认系统防火墙有没有限制80端口
```
http://172.16.3.200 # 导入的基础数据包含一个默认管理员admin 密码:!qaz@wsx
```
登录系统后可以点击导航菜单,访问对应功能。确认项目环境和项目运行没有问题后在服务器中按 CTRL + C 终止运行。
# 2 功能环境配置
项目中使用了一些工具例如Nmap异步任务使用的Celery进程管理使用的Supervisor扫描执行和集中管理使用的密钥认证登录等配置。
## 2.1 安装Nmap
登录服务器使用yum安装nmap工具nmap的基本使用【Django实战2-自动化运维之配置管理-08资产扫描工具的使用】
```
[root@sandboxmp ~]$ yum -y install nmap
```
## 2.2 密钥认证配置
本项目提供的扫描执行和集中管理功能支持密钥认证方式,如果要使用密钥认证来登录管理远程主机,需要设置密钥认证。
**1、在项目部署的服务器上创建密钥文件**
```
# 使用 ssh-keygen创建密钥文件执行命令后一路回车。生成的密钥文件默认存放在/root/.ssh目录中
[root@sandboxmp ~]# ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:dn0G8JVMN6Qby6JhZhFaMzOQ19ko5y518FIGsds5Pbg root@sandboxmp
The key's randomart image is:
+---[RSA 2048]----+
| .oBoo*o++.|
| .oo*O.=+..|
| ...+.Bo |
| .=++B |
| S=+o+Ooo |
| .+oo..oo .|
| .. E |
| |
| |
+----[SHA256]-----+
```
**2、在需要管理的远程终端中配置允许密钥登录**
```
# 1.修改sshd_config配置文件内容去掉下面内容的注释保存退出
[root@server1 ~]$ vim /etc/ssh/sshd_config
RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys # 认证授权默认存放位置
[root@server1 ~]$ systemctl restart sshd
# 2.生成公钥私钥文件,执行下面命令然后一路回车
[root@server1 ~]$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Created directory '/root/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
be:02:d5:03:c3:bc:64:22:90:a1:12:1f:64:16:76:4e root@server1
The key's randomart image is:
+--[ RSA 2048]----+
|+=B.Eo |
|o*.=. B |
|o ...+ = |
|. o o |
| . S. |
| . . |
| . . |
| . . |
| .. |
+-----------------+
# 3.这时候已经在/root/.ssh目录下生成了密钥文件
[root@server1 ~]$ ls -l .ssh
total 8
-rw------- 1 root root 1679 Dec 2 19:59 id_rsa
-rw-r--r-- 1 root root 394 Dec 2 19:59 id_rsa.pub
# 4. 创建认证文件设置访问权限authorized_keys是sshd_config中配置的认证授权默认存放位置
[root@server1 .ssh]$ touch authorized_keys
[root@server1 .ssh]$ chmod 600 authorized_keys
[root@server1 .ssh]$ ls -l
total 12
-rw------- 1 root root 396 Dec 2 20:05 authorized_keys
-rw------- 1 root root 1679 Dec 2 19:59 id_rsa
-rw-r--r-- 1 root root 394 Dec 2 19:59 id_rsa.pub
[root@server1 .ssh]$
```
**3、将项目部署服务器的公钥写入远程终端的认证文件authorized_keys**
```
[root@sandboxmp ~]$ cat ~/.ssh/id_rsa.pub | ssh root@172.16.3.101 'cat >> .ssh/authorized_keys'
The authenticity of host '172.16.3.101 (172.16.3.101)' can't be established.
ECDSA key fingerprint is SHA256:6veR9N0x60mE73zxt+N7sesJrlAEatHK9/UkXHeAd3Y.
ECDSA key fingerprint is MD5:17:89:88:88:25:6e:ca:c9:f8:19:45:3e:c4:2f:d7:bf.
Are you sure you want to continue connecting (yes/no)? yes # 首次登录需要输入yes确认
Warning: Permanently added '172.16.3.101' (ECDSA) to the list of known hosts.
root@172.16.3.101's password: # 输入远程主机的密码
[root@sandboxmp ~]$
```
代码中172.16.3.101是服务器需要管理的远程终端的IP地址首次SSH连接远程主机需要输入yes确认连接根据提示输入密码后成功写入公钥到远程终端。
在此通过项目服务器SSH连接到远程终端已经不需要再输入密码了可以直接通过密钥完成认证
```
[root@sandboxmp ~]$ ssh root@172.16.3.101
Last login: Wed Feb 20 17:53:16 2019 from 172.16.3.200
server01 172.16.3.101
[root@server01 ~]# # 成功登陆到远程终端命令行提示符显示的是server01
[root@server01 ~]# exit # 退出远程终端连接
logout
Connection to 172.16.3.101 closed.
[root@sandboxmp ~]#
```
## 2.3 使用Supervisor管理进程
设备扫描功能使用了celery做的任务队列进行异步扫描同时又使用到Flower来监控任务队列。为了方便管理在项目中使用了Supervisor来管理进程数据。
项目文件中已经做好了celery的配置和任务队列配置所以只需要使用Supervisor将进程管理起来即可。
### 2.3.1 安装Supervisor
安装Supervisor可以直接使用pip install supervisor来安装不支持python3.6也可以使用yum install 来安装。
```
# 在项目部署的服务器上安装supervisor
[root@sandboxmp ~]$ yum -y install supervisor
```
项目中使用yum方式来安装supervisor安装后会自带配置文件可以通过systemctl 来管理supervisor进程。
### 2.3.2 创建进程管理文件
**1、查看项目部署服务器上的Supervisord配置文件**
```
[root@sandboxmp /]$ cat /etc/supervisord.conf
'''以上内容省略'''
[include]
files = supervisord.d/*.ini
```
可以看到配置文件中导入了supervisord.d目录下的所有.ini格式文件所以我们可以把进程管理的配置文件放到这个目录。
**2、创建子进程管理配置文件**
在项目部署的服务器中创建子进程管理文件来管理celer任务进程和flower进程
```
# 编辑进程管理配置文件
[root@sandboxmp ~]$ touch /etc/supervisord.d/celery_worker.ini
[root@sandboxmp ~]$ vim /etc/supervisord.d/celery_worker.ini
# 将以下内容写入配置文件保存并退出
[program:celery-worker]
command=/root/.virtualenvs/sandboxMP/bin/celery worker -A sandboxMP -l INFO
directory=/opt/app/sandboxMP
environment=PATH="/root/.virtualenvs/sandboxMP/bin/"
stdout_logfile=/opt/app/sandboxMP/slogs/celery_worker.log
stderr_logfile=/opt/app/sandboxMP/slogs/celery_worker.log
autostart=true
autorestart=true
priority=901
[program:celery-flower]
command=/root/.virtualenvs/sandboxMP/bin/celery flower --broker=redis://localhost:6379/0
directory=/opt/app/sandboxMP
environment=PATH="/root/.virtualenvs/sandboxMP/bin/"
stdout_logfile=/opt/app/sandboxMP/slogs/celery_flower.log
stderr_logfile=/opt/app/sandboxMP/slogs/celery_flower.log
autostart=true
autorestart=true
priority=900
```
**3、启动Supervisord**
```
[root@sandboxmp ~]$ systemctl start supervisord # 启动supervisord
[root@sandboxmp ~]$ systemctl enable supervisord # 加到开机启动
```
**4、使用supervisorctl管理工具**
启动supervisord服务后supervisor会读取配置文件中的子进程配置并启动celery-worker和celery-flower进程。Supervisor为我们提供了一个子进程管理工具supervisorctl来管理这些进程数据
```
[root@sandboxmp /]$ supervisorctl # 启用子进程管理工具,系统会打印当前子进程状态
celery-flower RUNNING pid 4007, uptime 0:05:13
celery-worker RUNNING pid 4008, uptime 0:05:13
supervisor> status # 查看子进程状态
celery-flower RUNNING pid 4007, uptime 0:05:17
celery-worker RUNNING pid 4008, uptime 0:05:17
supervisor> stop celery-flower # 停止子进程stop all 停止所有
celery-flower: stopped
supervisor> status
celery-flower STOPPED Jan 18 03:27 PM
celery-worker RUNNING pid 4008, uptime 0:05:32
supervisor> start celery-flower # 启动子进程
celery-flower: started
supervisor> help # 查看帮助
default commands (type help <topic>):
=====================================
add clear fg open quit remove restart start stop update
avail exit maintail pid reload reread shutdown status tail version
```
为了使用Celery任务队列功能需要确保celery-flower和celery-worker进程状态都是RUNNING。如果有问题请查看对应日志文件在配置子进程的时候指定了日志存储路径
> 完成功能环境配置后,可以在项目服务器中临时运行项目,然后登陆系统,测试设备扫描功能,查看任务队列是否可以正常执行。
# 3 将项目部署上线
项目部署使用uwsgi来做Web服务Nginx做代理并提供静态资源访问和简单缓存功能。一般项部署项目上线我会分步骤进行这样在遇到问题也清楚是哪一个环节出的问题可以有针对性的进行排错。部署过程如下图所示
![image](https://raw.githubusercontent.com/RobbieHan/sandboxMP/05b1cc666595bf41a24de5d6a4d56e48d7089c18/document/images/stepww.png)
## 3.1 使用uwsgi运行项目
经过前面的部署已经准备好了系统环境、项目以来环并确认项目可以正常运行接下来使用uwsgi来运行项目。
**1、登陆服务器系统进入虚拟环境安装uwsgi**
```
[root@sandboxmp ~]# workon sandboxMP
(sandboxMP) [root@sandboxmp ~]$ pip install uwsgi
```
**2、设置Uwsgi配置文件**
```
(sandboxMP) [root@sandboxmp ~]$ vim /etc/smp_uwsgi.ini
[uwsgi]
http = 172.16.3.200:9000
#socket = 127.0.0.1:9000
chdir = /opt/app/sandboxMP
module = sandboxMP.wsgi
static-map=/static=/opt/app/sandboxMP/static
#daemonize =/var/log/uwsgi.log
master = Ture
vacuum = True
processes = 4
threads = 2
buffer-size=32768
```
配置说明:
- chdir: 指定项目目录,请设置项目所在目录
- static-map静态文件映射测试uwsgi配置时为了能够访问到静态资源所以加上这个配置。在使用nginx时需要注销掉这个配置改用nginx来代理静态资源访问。
注意配置文件中设置http是为了方便使用Uswgi启动项目后进行访问和功能测试。
**3、使用配置文件启动Uwsgi**
```
# 注意uwsgi是安装在虚拟环境的要使用uwsgi命令需要先进入虚拟环境
(sandboxMP) [root@sandboxmp ~]$ uwsgi /etc/smp_uwsgi.ini
[uWSGI] getting INI configuration from /etc/smp_uwsgi.ini
[uwsgi-static] added mapping for /static => /opt/app/sandboxMP/static
*** Starting uWSGI 2.0.18 (64bit) on [Mon Feb 25 23:21:01 2019] ***
compiled with version: 4.8.5 20150623 (Red Hat 4.8.5-36) on 25 February 2019 14:10:29
'''中间启动内容省略'''
spawned uWSGI master process (pid: 17054)
spawned uWSGI worker 1 (pid: 17058, cores: 2)
spawned uWSGI worker 2 (pid: 17060, cores: 2)
spawned uWSGI worker 3 (pid: 17061, cores: 2)
spawned uWSGI worker 4 (pid: 17063, cores: 2)
spawned uWSGI http 1 (pid: 17064)
```
项目运行成功通过服务器地址http://172.16.3.200:9000就可以访问项目了使用默认用户名: admin密码!qaz@wsx 登陆系统,测试系统功能。
至此确认完Uwsgi配置没有问题可以正常启动项目然后终止Uwsgi运行在命令行使用CTRL + C
**4、修改Uwsgi配置文件**
上面的配置是为了方便测试Uwsgi运行项目线上部署项目采用socket模式使用nginx来处理静态文件访问
```
[uwsgi]
#http = 172.16.3.200:9000
socket = 127.0.0.1:9000
chdir = /opt/app/sandboxMP
module = sandboxMP.wsgi
#static-map=/static=/opt/app/sandboxMP/static
#daemonize =/var/log/uwsgi.log
master = Ture
vacuum = True
processes = 4
threads = 2
buffer-size=32768
```
配置中注销了http和static-map配置同时启用socket配置。这时你也可以在命令行使用新的配置文件启动下uwsgi看看运行状态不过这时候外面是无法通过uwsgi来直接访问系统了还需要配置nginx代理。
**5、使用supervisor来管理uwsgi进程**
在前面已经使用supervisor来管理celery任务进程和flower进程同样也可以使用supervisor来管理uwsgi进程
```
# 1.新建一个进程文件sandboxmp_uwsgi写入下面配置内容
(sandboxMP) [root@sandboxmp ~]$ vim /etc/supervisord.d/sandboxmp_uwsgi.ini
[program:sandboxmp-uwsgi]
command=/root/.virtualenvs/sandboxMP/bin/uwsgi /etc/smp_uwsgi.ini
stdout_logfile=/var/log/uwsgi/smp_uwsgi.log
stderr_logfile=/var/log/uwsgi/smp_uwsgi.log
stdout_logfile_maxbytes = 20MB
autostart=true
autorestart=true
priority=905
# 2.创建一个目录用来存放uwsgi日志
(sandboxMP) [root@sandboxmp ~]$ mkdir /var/log/uwsgi
# 3.启动sandboxmp_uwsgi进程
(sandboxMP) [root@sandboxmp ~]# supervisorctl reload
Restarted supervisord
# 4. 稍微等待一会,然后查看进程状态:
(sandboxMP) [root@sandboxmp ~]# supervisorctl status
celery-flower RUNNING pid 17231, uptime 0:00:05
celery-worker RUNNING pid 17232, uptime 0:00:05
sandboxmp-uwsgi RUNNING pid 17233, uptime 0:00:05
# 5.查看服务状态
(sandboxMP) [root@sandboxmp ~]# netstat -tnpl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN 17233/uwsgi
```
通过上面的配置已经成功使用supervisor管理了sandboxmp-uwsgi进程。
## 3.2 使用Nginx做代理访问
**1、安装nginx**
```
(sandboxMP) [root@sandboxmp ~]$ yum -y install nginx
```
**2、修改nginx配置文件**
```
(sandboxMP) [root@sandboxmp ~]$ echo "" > /etc/nginx/nginx.conf
(sandboxMP) [root@sandboxmp ~]$ vim /etc/nginx/nginx.conf
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
server_tokens off;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
log_format nginxlog '$http_host '
'$remote_addr [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time '
'$upstream_response_time';
access_log /var/log/nginx/access.log nginxlog;
keepalive_timeout 60;
client_header_timeout 10;
client_body_timeout 15;
client_max_body_size 100M;
client_body_buffer_size 1024k;
gzip on;
gzip_min_length 1;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 3;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png app lication/vnd.ms-fontobject application/x-font-ttf image/svg+xml;
gzip_vary on;
upstream sandboxmp {
server 127.0.0.1:9000;
}
server {
listen 80;
server_name 0.0.0.0;
charset utf-8;
client_max_body_size 75M;
location /static {
alias /opt/app/sandboxMP/static;
}
location /media {
alias /opt/app/sandboxMP/media;
}
location / {
uwsgi_pass sandboxmp;
include /etc/nginx/uwsgi_params;
}
}
}
```
nginx配置说明<br>
在nginx中配置了日志格式应用代理和静态文件的代理访问。
**3、启动nginx服务设置开机启动**
```
(sandboxMP) [root@sandboxmp ~]# systemctl start nginx
(sandboxMP) [root@sandboxmp ~]# systemctl enable nginx
Created symlink from /etc/systemd/system/multi-user.target.wants/nginx.service to /usr/lib/systemd/system/nginx.service.
```
## 3.3 项目优化设置
项目在开发的时候是启用了Debug模式的现在部署上线了可以关闭Debug。
```
# 1.修改项目配置文件将DEBUG内容改成False
(sandboxMP) [root@sandboxmp ~]$ vim /opt/app/sandboxMP/sandboxMP/settings.py
'''配置文件中内容省略,主要修改下面两个内容'''
DEBUG = False
ALLOWED_HOSTS = ['*']
# 2.保存修改后,重启项目
(sandboxMP) [root@sandboxmp ~]$ supervisorctl restart sandboxmp-uwsgi
sandboxmp-uwsgi: stopped
sandboxmp-uwsgi: started
(sandboxMP) [root@sandboxmp ~]$ systemctl restart nginx
(sandboxMP) [root@sandboxmp ~]$
```
> 到这里项目已经部署上线了可以访问系统地址http://172.16.3.200 用户名admin 密码:!qaz@wsx 使用项目功能。本文档中配置文件已经包含在项目master版本文件中sandboxMP/config。
<br>
<br>
> 更多实战类文档,请关注我的知识星球: https://t.zsxq.com/a6IqBMr (微信中打开链接)<br>
轻量级办公管理系统项目开源地址https://github.com/RobbieHan/gistandard <br>

Binary file not shown.

1
apps/cmdb/__init__.py Normal file
View File

@@ -0,0 +1 @@
default_app_config = 'cmdb.apps.CmdbConfig'

3
apps/cmdb/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

8
apps/cmdb/apps.py Normal file
View File

@@ -0,0 +1,8 @@
from django.apps import AppConfig
class CmdbConfig(AppConfig):
name = 'cmdb'
def ready(self):
from .signals import auto_delete_file

171
apps/cmdb/forms.py Normal file
View File

@@ -0,0 +1,171 @@
# @Time : 2018/12/19 16:13
# @Author : RobbieHan
# @File : forms.py
import re
from django import forms
from .models import (Code, DeviceInfo, ConnectionInfo, DeviceFile,
NetworkAsset, NatRule, DomainName)
class CodeCreateForm(forms.ModelForm):
class Meta:
model = Code
fields = '__all__'
error_messages = {
'key': {'required': 'key不能为空'},
'value': {'required': 'value不能为空'}
}
def clean(self):
cleaned_data = super(CodeCreateForm, self).clean()
key = cleaned_data.get('key')
value = cleaned_data.get('value')
if Code.objects.filter(key=key).count():
raise forms.ValidationError('key{}已存在'.format(key))
if Code.objects.filter(value=value).count():
raise forms.ValidationError('value: {}已存在'.format(value))
class CodeUpdateForm(CodeCreateForm):
def clean(self):
cleaned_data = self.cleaned_data
key = cleaned_data.get('key')
value = cleaned_data.get('value')
if self.instance:
matching_code = Code.objects.exclude(pk=self.instance.pk)
if matching_code.filter(key=key).exists():
msg = 'key{} 已经存在'.format(key)
raise forms.ValidationError(msg)
if matching_code.filter(value=value).exists():
msg = 'value{} 已经存在'.format(value)
raise forms.ValidationError(msg)
class DeviceCreateForm(forms.ModelForm):
class Meta:
model = DeviceInfo
exclude = ['dev_connection']
error_messages = {
'hostname': {'required': '请填写设备地址'},
}
def clean(self):
cleaned_data = super(DeviceCreateForm, self).clean()
hostname = cleaned_data.get('hostname')
if DeviceInfo.objects.filter(hostname=hostname).count():
raise forms.ValidationError('设备地址:{}已存在'.format(hostname))
class DeviceUpdateForm(DeviceCreateForm):
def clean(self):
cleaned_data = self.cleaned_data
hostname = cleaned_data.get('hostname')
if self.instance:
matching_device = DeviceInfo.objects.exclude(pk=self.instance.pk)
if matching_device.filter(hostname=hostname).exists():
raise forms.ValidationError('设备地址:{}已存在'.format(hostname))
class ConnectionInfoForm(forms.ModelForm):
class Meta:
model = ConnectionInfo
fields = '__all__'
error_messages = {
'port': {'required': '端口不能为空'},
}
def clean(self):
cleaned_data = self.cleaned_data
username = cleaned_data.get('username')
password = cleaned_data.get('password')
private_key = cleaned_data.get('private_key')
auth_type = cleaned_data.get('auth_type')
if len(username) == 0:
raise forms.ValidationError('用户名不能为空!')
if auth_type == 'password' and len(password) == 0:
raise forms.ValidationError('认证类型为[密码]时,必须设置密码信息!')
if auth_type == 'private_key' and len(private_key) == 0:
raise forms.ValidationError('认证类型为[密钥]时,必须设置密钥信息!')
class DeviceFileUploadForm(forms.ModelForm):
class Meta:
model = DeviceFile
fields = '__all__'
class NetworkAssetForm(forms.ModelForm):
class Meta:
model = NetworkAsset
fields = '__all__'
error_messages = {
'name': {'required': '请填写网络资产名称'},
}
def clean(self):
cleaned_data = super(NetworkAssetForm, self).clean()
memory = cleaned_data.get('memory')
disk = cleaned_data.get('disk')
show_on_top = cleaned_data.get('show_on_top')
if memory:
me = re.match('(.*)/(.*)', memory)
if me:
try:
int(me.group(1))
int(me.group(2))
except Exception:
raise forms.ValidationError('内存使用量和总量为整数')
else:
raise forms.ValidationError('内存格式不对格式为5/16 (用量/总量)')
if disk:
di = re.match('(.*)/(.*)', disk)
if di:
try:
int(di.group(1))
int(di.group(2))
except Exception:
raise forms.ValidationError('硬盘使用量和总量为整数')
else:
raise forms.ValidationError('硬盘格式不对格式为5/16 (用量/总量)')
show_on_top_count = NetworkAsset.objects.filter(show_on_top=True).count()
if show_on_top and show_on_top_count >= 5:
raise forms.ValidationError('首页最多展示5个链接')
class NatRuleForm(forms.ModelForm):
class Meta:
model = NatRule
fields = '__all__'
error_messages = {
'internet_ip': {'required': '请填写公网IP'},
'src_port': {'required': '请填写公网端口'},
'lan_ip': {'required': '请填写内网地址'},
'dest_port': {'required': '请填写内网端口'},
'desc': {'required': '请填写规则说明'}
}
class DomainNameForm(forms.ModelForm):
class Meta:
model = DomainName
fields = '__all__'
error_messages = {
'domain': {'required': '域名不能为空'},
'buyDate': {'required': '请填写域名购买日期'},
'warrantyDate': {'required': '请填写域名到期时间'},
'dn_type': {'required': '域名类型不能为空'}
}

View File

195
apps/cmdb/models.py Normal file
View File

@@ -0,0 +1,195 @@
from datetime import datetime
from django.db import models
from django.contrib.auth import get_user_model
from simple_history.models import HistoricalRecords
User = get_user_model()
class AbstractMode(models.Model):
parent = models.ForeignKey(
'self', blank=True, null=True, on_delete=models.SET_NULL, related_name='child'
)
class Meta:
abstract = True
class Code(AbstractMode):
key = models.CharField(max_length=80, verbose_name='')
value = models.CharField(max_length=80, verbose_name='')
desc = models.CharField(max_length=100, blank=True, default='', verbose_name='备注')
class Meta:
verbose_name = '字典'
verbose_name_plural = verbose_name
class TimeAbstract(models.Model):
add_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")
modify_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
abstract = True
class ConnectionAbstract(models.Model):
auth_method_choices = (
('private_key', '密钥认证'),
('password', '密码认证')
)
hostname = models.CharField(max_length=50, verbose_name='设备地址(IP或域名)')
port = models.IntegerField(default=22, verbose_name='SSH端口')
username = models.CharField(max_length=15, blank=True, default='', verbose_name='SSH用户名')
password = models.CharField(max_length=80, blank=True, default='', verbose_name='SSH密码')
private_key = models.CharField(max_length=100, blank=True, default='', verbose_name='密钥路径')
auth_type = models.CharField(max_length=30, choices=auth_method_choices, default='')
status = models.CharField(max_length=10, blank=True, default='')
class Meta:
abstract = True
class DeviceAbstract(models.Model):
sys_hostname = models.CharField(max_length=150, blank=True, default='', verbose_name='主机名')
mac_address = models.CharField(max_length=150, blank=True, default='', verbose_name='MAC地址')
sn_number = models.CharField(max_length=150, blank=True, default='', verbose_name='SN号码')
os_type = models.CharField(max_length=150, blank=True, default='', verbose_name='系统类型')
device_type = models.CharField(max_length=150, blank=True, default='', verbose_name='设备类型')
class Meta:
abstract = True
class DeviceScanInfo(ConnectionAbstract, DeviceAbstract, TimeAbstract):
error_message = models.CharField(max_length=80, blank=True, default='', verbose_name='错误信息')
class Meta:
verbose_name = '扫描信息'
verbose_name_plural = verbose_name
class ConnectionInfo(ConnectionAbstract, TimeAbstract):
class Meta:
verbose_name = 'SSH连接信息'
verbose_name_plural = verbose_name
class Cabinet(models.Model):
number = models.CharField(max_length=50, verbose_name='机柜编号')
position = models.CharField(max_length=80, verbose_name='机柜位置')
desc = models.TextField(blank=True, default='', verbose_name='备注信息')
class Meta:
verbose_name = '机柜信息'
verbose_name_plural = verbose_name
class DeviceInfo(AbstractMode, DeviceAbstract, TimeAbstract):
hostname = models.CharField(max_length=50, verbose_name='设备地址(IP或域名)')
network_type = models.IntegerField(blank=True, null=True, verbose_name='网络类型')
service_type = models.IntegerField(blank=True, null=True, verbose_name='服务类型')
operation_type = models.IntegerField(blank=True, null=True, verbose_name='所属项目')
config = models.CharField(max_length=80, blank=True, default='', verbose_name='配置信息')
dev_cabinet = models.IntegerField(blank=True, null=True, verbose_name='机柜信息')
dev_connection = models.IntegerField(blank=True, null=True, verbose_name='连接信息')
buyDate = models.DateField(default=datetime.now, blank=True, null=True, verbose_name="购买日期")
warrantyDate = models.DateField(default=datetime.now, blank=True, null=True, verbose_name="到保日期")
desc = models.TextField(blank=True, default='', verbose_name='备注信息')
changed_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL)
history = HistoricalRecords(excluded_fields=['add_time', 'modify_time', 'parent'])
class Meta:
verbose_name = '设备信息'
verbose_name_plural = verbose_name
@property
def _history_user(self):
return self.changed_by
@_history_user.setter
def _history_user(self, value):
self.changed_by = value
class DeviceFile(TimeAbstract):
device = models.ForeignKey('DeviceInfo', blank=True, null=True, on_delete=models.SET_NULL, verbose_name='设备')
file_content = models.FileField(upload_to="asset_file/%Y/%m", null=True, blank=True, verbose_name="资产文件")
upload_user = models.CharField(max_length=20, verbose_name="上传人")
class Supplier(models.Model):
firm = models.CharField(max_length=200, verbose_name='供应商')
contact_details = models.CharField(max_length=200, verbose_name='联系信息')
desc = models.CharField(max_length=200, blank=True, default='', verbose_name='备注信息')
class Meta:
verbose_name = '供应商管理'
verbose_name_plural = verbose_name
class NetworkAsset(models.Model):
name = models.CharField(max_length=100, verbose_name='资产名称')
ip_address = models.CharField(max_length=100, blank=True, default='', verbose_name='IP地址')
management = models.CharField(max_length=100, blank=True, default='', verbose_name='管理地址')
show_on_top = models.BooleanField(default=False, verbose_name='首页展示')
provider = models.ForeignKey('Supplier', blank=True, null=True, on_delete=models.SET_NULL, verbose_name='服务商')
memory = models.CharField(max_length=20, blank=True, default='', verbose_name='内存用量')
disk = models.CharField(max_length=20, blank=True, default='', verbose_name='硬盘用量')
state = models.BooleanField(default=True, verbose_name='状态')
buyDate = models.DateField(default=datetime.now, blank=True, null=True, verbose_name='购买日期')
warrantyDate = models.DateField(default=datetime.now, blank=True, null=True, verbose_name='到保日期')
desc = models.TextField(blank=True, default='', verbose_name='备注信息')
class Meta:
verbose_name = '网络资产'
verbose_name_plural = verbose_name
class NatRule(models.Model):
internet_ip = models.CharField(max_length=80, verbose_name='互联网IP')
src_port = models.IntegerField(verbose_name='源端口')
lan_ip = models.CharField(max_length=80, verbose_name='内网IP')
dest_port = models.IntegerField(verbose_name='目的端口')
state = models.BooleanField(default=True, verbose_name='状态')
dev_cabinet = models.ForeignKey('Cabinet', blank=True, default='', on_delete=models.SET_DEFAULT, verbose_name='机柜信息')
desc = models.CharField(max_length=100, verbose_name='规则说明')
class Meta:
verbose_name = 'NAT规则'
verbose_name_plural = verbose_name
class DomainName(AbstractMode):
dn_type_choices = (('1', '主域名'),('2', '二级域名'))
domain = models.CharField(max_length=200, verbose_name='域名')
dn_type = models.CharField(max_length=20, choices=dn_type_choices, default='1')
nat_rule = models.ManyToManyField('NatRule', blank=True)
resolution_server = models.ForeignKey('Supplier', related_name='res_server',
blank=True, null=True, on_delete=models.SET_NULL, verbose_name='解析服务')
domain_provider = models.ForeignKey('Supplier', related_name='do_provider',
blank=True, null=True, on_delete=models.SET_NULL, verbose_name='域名服务')
operation_type = models.ForeignKey('Code', blank=True, null=True, on_delete=models.SET_NULL, verbose_name='所属项目')
state = models.BooleanField(default=True, verbose_name='状态')
buyDate = models.DateField(default=datetime.now, verbose_name='购买日期')
warrantyDate = models.DateField(default=datetime.now, verbose_name='到期时间')
desc = models.TextField(blank=True, default='', verbose_name='备注信息')
class Meta:
verbose_name = '域名管理'
verbose_name_plural = verbose_name
class SSLCert(TimeAbstract):
domain_name = models.ForeignKey('DomainName', blank=True, null=True, on_delete=models.SET_NULL, verbose_name='域名')
ssl_cert = models.FileField(upload_to="ssl_cert/", verbose_name="域名证书")
buyDate = models.DateField(default=datetime.now, verbose_name='购买日期')
warrantyDate = models.DateField(default=datetime.now, verbose_name='到期时间')
upload_user = models.CharField(max_length=20, verbose_name="上传人")
desc = models.TextField(blank=True, default='', verbose_name='备注信息')

46
apps/cmdb/signals.py Normal file
View File

@@ -0,0 +1,46 @@
import os
from django.dispatch import receiver
from django.db.models.signals import post_delete, post_save
from .models import DeviceFile, DeviceInfo, ConnectionInfo
from utils.db_utils import MongodbDriver
@receiver(post_delete, sender=DeviceFile)
def auto_delete_file(sender, instance, **kwargs):
if instance.file_content:
if os.path.isfile(instance.file_content.path):
os.remove(instance.file_content.path)
@receiver(post_save, sender=DeviceInfo)
def auto_compare_diff(sender, instance, **kwargs):
record = instance.history.latest()
prev_record = record.prev_record
ope_type = {'~': 'update', '+': 'create', '-': 'delete'}
compare_result = {
'id': record.id,
'changed_by': record.changed_by.name,
'history_type': ope_type[record.history_type],
'history_date': record.history_date
}
changes = {}
if prev_record is not None:
delta = record.diff_against(prev_record)
for change in delta.changes:
changes[change.field] = [change.old, change.new]
compare_result['changes'] = changes
if compare_result['changes'] or compare_result['history_type'] == 'create':
try:
mongo = MongodbDriver(collection='change_compare')
mongo.insert(compare_result)
except Exception as e:
pass
@receiver(post_delete, sender=DeviceInfo)
def auto_delete_connection(sender, instance, **kwargs):
dev_connection = getattr(instance, 'dev_connection')
if dev_connection:
ConnectionInfo.objects.filter(id=dev_connection).delete()

56
apps/cmdb/tasks.py Normal file
View File

@@ -0,0 +1,56 @@
import time
import logging
from celery import shared_task
from celery_once import QueueOnce
from utils.sandbox_utils import SandboxScan, LoginExecution
from .models import DeviceScanInfo
info_logger = logging.getLogger('sandbox_info')
@shared_task(base=QueueOnce)
def scan_execution():
scan = SandboxScan()
execution = LoginExecution()
scan_type = execution.get_scan_type()
auth_type = execution.get_auth_type()
start_time = time.time()
if scan_type == 'basic_scan':
hosts = scan.basic_scan()
for host in hosts:
DeviceScanInfo.objects.update_or_create(
hostname=host,
)
else:
hosts = scan.os_scan()
login_hosts = [host for host in hosts if host['os'] in ['Linux', 'embedded']]
nologin_hosts = [host for host in hosts if host not in login_hosts]
for host in nologin_hosts:
DeviceScanInfo.objects.update_or_create(
hostname=host['host'],
defaults={
'os_type': host['os']
}
)
for host in login_hosts:
kwargs = {
'hostname': host['host'],
'username': execution.get_ssh_username(),
'port': execution.get_ssh_port(),
'password': execution.get_ssh_password(),
'private_key': execution.get_ssh_private_key()
}
defaults = execution.login_execution(auth_type=auth_type, **kwargs)
DeviceScanInfo.objects.update_or_create(
hostname=host['host'],
defaults=defaults
)
end_time = time.time()
msg = 'Scan task has been completed, execution time: %(time)s, %(num)s hosts are up.' % {
'time': end_time - start_time,
'num': len(hosts)
}
info_logger.info(msg)
return msg

View File

@@ -0,0 +1,3 @@
# @Time : 2019/2/17 21:28
# @Author : RobbieHan
# @File : __init__.py.py

View File

@@ -0,0 +1,58 @@
from django import template
from django.db.models.query import QuerySet
from django.contrib.auth import get_user_model
from cmdb.models import Code, Cabinet
register = template.Library()
User = get_user_model()
@register.simple_tag
def get_con(context, arg, field):
if isinstance(context, QuerySet):
context = context.values()
instance = [con for con in context if con['id'] == arg]
if instance:
return instance[0][field]
return ''
@register.filter(name='compare_result')
def get_change_compare(changes):
change_compare = []
for key, value in changes.items():
if key in ['network_type', 'service_type', 'operation_type']:
log = replace_log(key, value, Code, 'value')
elif key == 'dev_cabinet':
log = replace_log(key, value, Cabinet, 'number')
else:
log = '字段:"%(field)s",由:"%(old)s",变更为:"%(new)s"' % {
'field': key,
'old': value[0],
'new': value[1]
}
change_compare.append(log)
return ''.join(str(i) for i in change_compare)
def replace_log(key, value, model, field):
old = value[0]
new = value[1]
log_format = '字段:"%(field)s",由:"%(old)s",变更为:"%(new)s"'
try:
data = model.objects.filter(id=old).values()[0]
old_data = data[field]
except Exception:
old_data = old
try:
data = model.objects.filter(id=new).values()[0]
new_data = data[field]
except Exception:
new_data = new
return log_format % {
'field': key,
'old': old_data,
'new': new_data
}

22
apps/cmdb/tests.py Normal file
View File

@@ -0,0 +1,22 @@
from django.test import TestCase
# Create your tests here.
from django.views.generic.base import View
from django.shortcuts import HttpResponse
import logging
from .models import Code
info_logger = logging.getLogger('sandbox_info')
error_logger = logging.getLogger('sandbox_error')
class TestLoggingView(View):
def get(self, request):
print('a')
info_logger.info('The system print a letter "a" ')
try:
Code.objects.get(id=100)
except Exception as e:
error_logger.error(e)
return HttpResponse("OK!")

66
apps/cmdb/urls.py Normal file
View File

@@ -0,0 +1,66 @@
from django.urls import path
from .views import CmdbView
from . import views_code, views_scan, views_eam
app_name = 'cmdb'
urlpatterns = [
path('', CmdbView.as_view(), name='index'),
path('portal/code/', views_code.CodeView.as_view(), name='portal-code'),
path('portal/code/create/', views_code.CodeCreateView.as_view(), name='portal-code-create'),
path('portal/code/list/', views_code.CodeListView.as_view(), name='portal-code-list'),
path('portal/code/update/', views_code.CodeUpdateView.as_view(), name='portal-code-update'),
path('portal/code/delete/', views_code.CodeDeleteView.as_view(), name='portal-code-delete'),
path('portal/scan_config/', views_scan.ScanConfigView.as_view(), name='portal-scan_config'),
path('portal/device_scan/', views_scan.DeviceScanView.as_view(), name='portal-device_scan'),
path('portal/device_scan/list/', views_scan.DeviceScanListView.as_view(), name='portal-device_scan-list'),
path('portal/device_scan/detail/', views_scan.DeviceScanDetailView.as_view(), name='portal-device_scan-detail'),
path('portal/device_scan/delete/', views_scan.DeviceScanDeleteView.as_view(), name='portal-device_scan-delete'),
path('portal/device_scan/exec/', views_scan.DeviceScanExecView.as_view(), name='portal-device_scan-exec'),
path('portal/device_scan/inbound/', views_scan.DeviceScanInboundView.as_view(), name='portal-device_scan-inbound'),
path('eam/cabinet/', views_eam.CabinetView.as_view(), name='eam-cabinet'),
path('eam/cabinet/create/', views_eam.CabinetCreateView.as_view(), name='eam-cabinet-create'),
path('eam/cabinet/update/', views_eam.CabinetUpdateView.as_view(), name='eam-cabinet-update'),
path('eam/cabinet/list/', views_eam.CabinetListView.as_view(), name='eam-cabinet-list'),
path('eam/cabinet/delete/', views_eam.CabinetDeleteView.as_view(), name='eam-cabinet-delete'),
path('eam/device/', views_eam.DeviceView.as_view(), name='eam-device'),
path('eam/device/create/', views_eam.DeviceCreateView.as_view(), name='eam-device-create'),
path('eam/device/update/', views_eam.DeviceUpdateView.as_view(), name='eam-device-update'),
path('eam/device/list/', views_eam.DeviceListView.as_view(), name='eam-device-list'),
path('eam/device/delete/', views_eam.DeviceDeleteView.as_view(), name='eam-device-delete'),
path('eam/device/device2connection/', views_eam.Device2ConnectionView.as_view(), name='eam-device-device2connection'),
path('eam/device/detail/', views_eam.DeviceDetailView.as_view(), name='eam-device-detail'),
path('eam/device/upload/', views_eam.DeviceFileUploadView.as_view(), name='eam-device-upload'),
path('eam/device/file_delete/', views_eam.DeviceFileDeleteView.as_view(), name='eam-device-file_delete'),
path('eam/device/auto_update_device_info/', views_eam.AutoUpdateDeviceInfo.as_view(),
name='eam-device-auto_update_device_info'),
path('eam/supplier/', views_eam.SupplierView.as_view(), name='eam-supplier'),
path('eam/supplier/create/', views_eam.SupplierCreateView.as_view(), name='eam-supplier-create'),
path('eam/supplier/update/', views_eam.SupplierUpdateView.as_view(), name='eam-supplier-update'),
path('eam/supplier/list/', views_eam.SupplierListView.as_view(), name='eam-supplier-list'),
path('eam/supplier/delete/', views_eam.SupplierDeleteView.as_view(), name='eam-supplier-delete'),
path('eam/network_asset/', views_eam.NetworkAssetView.as_view(), name='eam-network_asset'),
path('eam/network_asset/create/', views_eam.NetworkAssetCreateView.as_view(), name='eam-network_asset-create'),
path('eam/network_asset/update/', views_eam.NetworkAssetUpdateView.as_view(), name='eam-network_asset-update'),
path('eam/network_asset/list/', views_eam.NetworkAssetListView.as_view(), name='eam-network_asset-list'),
path('eam/network_asset/delete/', views_eam.NetworkAssetDeleteView.as_view(), name='eam-network_asset-delete'),
path('eam/natrule/', views_eam.NatRuleView.as_view(), name='eam-natrule'),
path('eam/natrule/create/', views_eam.NatRuleCreateView.as_view(), name='eam-natrule-create'),
path('eam/natrule/update/', views_eam.NatRuleUpdateView.as_view(), name='eam-natrule-update'),
path('eam/natrule/list/', views_eam.NatRuleListView.as_view(), name='eam-natrule-list'),
path('eam/natrule/delete/', views_eam.NatRuleDeleteView.as_view(), name='eam-natrule-delete'),
path('eam/domain/', views_eam.DomainNameView.as_view(), name='eam-domain'),
path('eam/domain/create/', views_eam.DomainNameCreateView.as_view(), name='eam-domain-create'),
path('eam/domain/update/', views_eam.DomainNameUpdateView.as_view(), name='eam-domain-update'),
path('eam/domain/list/', views_eam.DomainNameListView.as_view(), name='eam-domain-list'),
path('eam/domain/delete/', views_eam.DomainNameDeleteView.as_view(), name='eam-domain-delete'),
path('eam/domain/dn2nr/', views_eam.DomainName2NatRule.as_view(), name='eam-domain-dn2nr'),
]

51
apps/cmdb/views.py Normal file
View File

@@ -0,0 +1,51 @@
import re
from django.views.generic import View
from django.shortcuts import render
from system.mixin import LoginRequiredMixin
from .models import Cabinet, Code, DeviceInfo, NetworkAsset
class CmdbView(LoginRequiredMixin, View):
def get(self, request):
ret = dict()
cabinet_all = Cabinet.objects.all()
operation_type_all = Code.objects.filter(parent__key='operation_type')
# net_asset=list(NetworkAsset.objects.filter(show_on_top=True).values())
cabinet_list = []
cabinet_count = []
for cabinet in cabinet_all:
cabinet_list.append(cabinet.number)
cabinet_count.append(
DeviceInfo.objects.filter(dev_cabinet=cabinet.id).count()
)
operations = []
for operation in operation_type_all:
count = DeviceInfo.objects.filter(operation_type=operation.id).count()
data = {
'name': operation.value,
'count': count
}
operations.append(data)
# for asset in net_asset:
# disk = asset['disk']
# memory = asset['memory']
# if disk:
# di = re.match('(.*)/(.*)', disk)
# di_used = int(di.group(1))
# di_total = int(di.group(2))
# di_percent = '{:.0%}'.format(di_used / di_total)
# asset['disk'] = {'disk': disk, 'percent': di_percent}
# if memory:
# me = re.match('(.*)/(.*)', memory)
# me_used = int(me.group(1))
# me_total = int(me.group(2))
# me_percent = '{:.0%}'.format(me_used / me_total)
# asset['memory'] = {'memory': memory, 'percent': me_percent}
ret['cabinet_list'] = cabinet_list
ret['cabinet_count'] = cabinet_count
ret['operations'] = operations
# ret['net_asset'] = net_asset
return render(request, 'cmdb/cmdb_index.html', ret)

53
apps/cmdb/views_code.py Normal file
View File

@@ -0,0 +1,53 @@
# @Time : 2018/12/19 13:31
# @Author : RobbieHan
# @File : views_code.py.py
from django.views.generic import TemplateView
from system.mixin import LoginRequiredMixin
from custom import (BreadcrumbMixin, SandboxCreateView,
SandboxListView, SandboxUpdateView, SandboxDeleteView)
from .models import Code
from .forms import CodeCreateForm, CodeUpdateForm
class CodeView(LoginRequiredMixin, BreadcrumbMixin, TemplateView):
template_name = 'cmdb/code.html'
def get_context_data(self):
self.kwargs['code_parent'] = Code.objects.filter(parent=None)
return super().get_context_data(**self.kwargs)
class CodeCreateView(SandboxCreateView):
model = Code
form_class = CodeCreateForm
template_name_suffix = '_create'
def get_context_data(self, **kwargs):
kwargs['code_parent'] = Code.objects.filter(parent=None)
return super().get_context_data(**kwargs)
class CodeListView(SandboxListView):
model = Code
fields = ['id', 'key', 'value', 'parent__value']
def get(self, request):
if 'parent' in request.GET and request.GET['parent']:
self.filters = dict(parent__key=request.GET['parent'])
return super().get(request)
class CodeUpdateView(SandboxUpdateView):
model = Code
form_class = CodeUpdateForm
template_name_suffix = '_update'
def get_context_data(self, **kwargs):
kwargs['code_parent'] = Code.objects.filter(parent=None)
return super().get_context_data(**kwargs)
class CodeDeleteView(SandboxDeleteView):
model = Code

460
apps/cmdb/views_eam.py Normal file
View File

@@ -0,0 +1,460 @@
import re
from datetime import datetime, timedelta
from django.views.generic import TemplateView, View
from django.contrib.auth import get_user_model
from django.shortcuts import get_object_or_404
from django.http import JsonResponse
from django.shortcuts import render
from django.forms.models import model_to_dict
from system.mixin import LoginRequiredMixin
from custom import (BreadcrumbMixin, SandboxDeleteView,
SandboxListView, SandboxUpdateView, SandboxCreateView)
from .models import (Cabinet, DeviceInfo, Code, ConnectionInfo, DeviceFile,
Supplier, NetworkAsset, NatRule, DomainName)
from .forms import (DeviceCreateForm, DeviceUpdateForm, ConnectionInfoForm,
DeviceFileUploadForm, NetworkAssetForm,NatRuleForm, DomainNameForm)
from utils.db_utils import MongodbDriver
from utils.sandbox_utils import LoginExecution
User = get_user_model()
class CabinetView(LoginRequiredMixin, BreadcrumbMixin, TemplateView):
template_name = 'cmdb/cabinet.html'
class CabinetCreateView(SandboxCreateView):
model = Cabinet
fields = '__all__'
class CabinetUpdateView(SandboxUpdateView):
model = Cabinet
fields = '__all__'
class CabinetListView(SandboxListView):
model = Cabinet
fields = ['id', 'number', 'position', 'desc']
def get_filters(self):
data = self.request.GET
filters = {}
if 'number' in data and data['number']:
filters['number__icontains'] = data['number']
if 'position' in data and data['position']:
filters['position__icontains'] = data['position']
return filters
class CabinetDeleteView(SandboxDeleteView):
model = Cabinet
def get_device_public():
all_code = Code.objects.all()
all_cabinet = Cabinet.objects.all()
all_user = User.objects.all()
all_device = DeviceInfo.objects.all()
ret = {
'all_code': all_code,
'all_cabinet': all_cabinet,
'all_user': all_user,
'all_device': all_device,
}
return ret
class DeviceView(LoginRequiredMixin, BreadcrumbMixin, TemplateView):
template_name = 'cmdb/deviceinfo.html'
def get_context_data(self, **kwargs):
device_public = get_device_public()
kwargs.update(device_public)
return super().get_context_data(**kwargs)
class DeviceListView(SandboxListView):
model = DeviceInfo
fields = ['id', 'sys_hostname', 'hostname', 'service_type', 'operation_type', 'config', 'dev_cabinet',
'network_type']
def get_filters(self):
data = self.request.GET
filters = {}
if 'sys_hostname' in data and data['sys_hostname']:
filters['sys_hostname__icontains'] = data['sys_hostname']
if 'hostname' in data and data['hostname']:
filters['hostname__icontains'] = data['hostname']
if 'network_type' in data and data['network_type']:
filters['network_type'] = data['network_type']
if 'service_type' in data and data['service_type']:
filters['service_type'] = data['service_type']
if 'operation_type' in data and data['operation_type']:
filters['operation_type'] = data['operation_type']
if 'dev_cabinet' in data and data['dev_cabinet']:
filters['dev_cabinet'] = data['dev_cabinet']
return filters
def get_datatables_paginator(self, request):
context_data = super().get_datatables_paginator(request)
data = context_data['data']
for device in data:
service_type = device['service_type']
operation_type = device['operation_type']
dev_cabinet = device['dev_cabinet']
network_type = device['network_type']
device['operation_type'] = get_object_or_404(Code, pk=int(operation_type)).value if operation_type else ''
device['network_type'] = get_object_or_404(Code, pk=int(network_type)).value if network_type else ''
device['service_type'] = get_object_or_404(Code, pk=int(service_type)).value if service_type else ''
device['dev_cabinet'] = get_object_or_404(Cabinet, pk=int(dev_cabinet)).number if dev_cabinet else ''
return context_data
class DeviceCreateView(SandboxCreateView):
model = DeviceInfo
form_class = DeviceCreateForm
def get_context_data(self, **kwargs):
public_data = get_device_public()
kwargs.update(public_data)
return super().get_context_data(**kwargs)
class DeviceUpdateView(SandboxUpdateView):
model = DeviceInfo
form_class = DeviceUpdateForm
def get_context_data(self, **kwargs):
public_data = get_device_public()
kwargs.update(public_data)
return super().get_context_data(**kwargs)
class DeviceDeleteView(SandboxDeleteView):
model = DeviceInfo
class Device2ConnectionView(LoginRequiredMixin, View):
def get(self, request):
ret = dict()
if 'id' in request.GET and request.GET['id']:
device = get_object_or_404(DeviceInfo, pk=int(request.GET['id']))
ret['device'] = device
dev_connection = device.dev_connection
if dev_connection:
connection_info = get_object_or_404(
ConnectionInfo, pk=int(dev_connection)
)
ret['connection_info'] = connection_info
return render(request, 'cmdb/deviceinfo2connection.html', ret)
def post(self, request):
res = dict(result=False)
con_info = ConnectionInfo()
if 'id' in request.POST and request.POST['id']:
con_info = get_object_or_404(ConnectionInfo, pk=request.POST['id'])
form = ConnectionInfoForm(request.POST, instance=con_info)
if form.is_valid():
instance = form.save()
con_id = getattr(instance, 'id')
device = get_object_or_404(DeviceInfo, hostname=request.POST['hostname'])
device.dev_connection = con_id
device.save()
res['result'] = True
else:
pattern = '<li>.*?<ul class=.*?><li>(.*?)</li>'
form_errors = str(form.errors)
errors = re.findall(pattern, form_errors)
res['error'] = errors[0]
return JsonResponse(res)
class DeviceDetailView(LoginRequiredMixin, BreadcrumbMixin, TemplateView):
template_name = 'cmdb/deviceinfo_detail.html'
def get_context_data(self, **kwargs):
device = get_object_or_404(DeviceInfo, pk=int(self.request.GET['id']))
mongo = MongodbDriver()
logs = mongo.find(id=int(self.request.GET['id']), sort_by='history_date')
all_file = device.devicefile_set.all()
device_public = get_device_public()
kwargs['device'] = device
kwargs['logs'] = logs
kwargs['all_file'] = all_file
kwargs.update(device_public)
return super().get_context_data(**kwargs)
class DeviceFileUploadView(LoginRequiredMixin, View):
def get(self, request):
ret = dict()
device = get_object_or_404(DeviceInfo, pk=request.GET['id'])
ret['device'] = device
return render(request, 'cmdb/deviceinfo_upload.html', ret)
def post(self, request):
res = dict(result=False)
device_file = DeviceFile()
upload_form = DeviceFileUploadForm(
request.POST, request.FILES, instance=device_file
)
if upload_form.is_valid():
upload_form.save()
res['result'] = True
return JsonResponse(res)
class DeviceFileDeleteView(SandboxDeleteView):
model = DeviceFile
class AutoUpdateDeviceInfo(LoginRequiredMixin, View):
def post(self, request):
res = dict(status='fail')
if 'id' in request.POST and request.POST['id']:
device = get_object_or_404(DeviceInfo, pk=int(request.POST['id']))
con_id = device.dev_connection
conn = ConnectionInfo.objects.filter(id=con_id)
if con_id and conn:
try:
conn_info = conn.get()
kwargs = model_to_dict(conn_info, exclude=['id', 'auth_type'])
auth_type = conn_info.auth_type
le = LoginExecution()
data = le.login_execution(auth_type=auth_type, **kwargs)
conn_info.status = data['status']
conn_info.save()
if data['status'] == 'succeed':
device.sys_hostname = data['sys_hostname']
device.mac_address = data['mac_address']
device.sn_number = data['sn_number']
device.os_type = data['os_type']
device.device_type = data['device_type']
device.save()
res['status'] = 'success'
except conn.model.DoesNotExist:
res['status'] = 'con_empty'
else:
res['status'] = 'con_empty'
return JsonResponse(res)
class SupplierView(LoginRequiredMixin, BreadcrumbMixin, TemplateView):
template_name = 'cmdb/supplier.html'
class SupplierCreateView(SandboxCreateView):
model = Supplier
fields = '__all__'
class SupplierUpdateView(SandboxUpdateView):
model = Supplier
fields = '__all__'
class SupplierListView(SandboxListView):
model = Supplier
fields = ['id', 'firm', 'contact_details', 'desc']
def get_filters(self):
data = self.request.GET
filters = {}
if 'firm' in data and data['firm']:
filters['firm__icontains'] = data['firm']
if 'contact_details' in data and data['contact_details']:
filters['contact_deatils__icontains'] = data['contact_details']
return filters
class SupplierDeleteView(SandboxDeleteView):
model = Supplier
class NetworkAssetView(LoginRequiredMixin, BreadcrumbMixin, TemplateView):
template_name = 'cmdb/network_asset.html'
class NetworkAssetCreateView(SandboxCreateView):
model = NetworkAsset
form_class = NetworkAssetForm
def get_context_data(self, **kwargs):
kwargs['all_provider'] = Supplier.objects.all()
return super().get_context_data(**kwargs)
class NetworkAssetUpdateView(SandboxUpdateView):
model = NetworkAsset
form_class = NetworkAssetForm
def get_context_data(self, **kwargs):
kwargs['all_provider'] = Supplier.objects.all()
return super().get_context_data(**kwargs)
class NetworkAssetListView(SandboxListView):
model = NetworkAsset
fields = ['id', 'name', 'ip_address', 'management', 'provider__firm', 'memory', 'disk', 'buyDate', 'warrantyDate', 'state']
def get_filters(self):
data = self.request.GET
filters = {}
if 'name' in data and data['name']:
filters['name__icontains'] = data['name']
if 'ip_address' in data and data['ip_address']:
filters['ip_address__icontains'] = data['ip_address']
return filters
def get_datatables_paginator(self, request):
context_data = super().get_datatables_paginator(request)
data = context_data['data']
for asset in data:
disk = asset['disk']
memory = asset['memory']
if disk:
di = re.match('(.*)/(.*)', disk)
di_used = int(di.group(1))
di_total = int(di.group(2))
di_percent = '{:.0%}'.format(di_used/di_total)
asset['disk'] = {'disk': disk, 'percent': di_percent}
if memory:
me = re.match('(.*)/(.*)', memory)
me_used = int(me.group(1))
me_total = int(me.group(2))
me_percent = '{:.0%}'.format(me_used / me_total)
asset['memory'] = {'memory': memory, 'percent': me_percent}
return context_data
class NetworkAssetDeleteView(SandboxDeleteView):
model = NetworkAsset
class NatRuleView(LoginRequiredMixin, BreadcrumbMixin, TemplateView):
template_name = 'cmdb/natrule.html'
def get_context_data(self, **kwargs):
kwargs['all_cabinet'] = Cabinet.objects.all()
return super().get_context_data(**kwargs)
class NatRuleCreateView(SandboxCreateView):
model = NatRule
form_class = NatRuleForm
def get_context_data(self, **kwargs):
kwargs['all_cabinet'] = Cabinet.objects.all()
return super().get_context_data(**kwargs)
class NatRuleUpdateView(SandboxUpdateView):
model = NatRule
form_class = NatRuleForm
def get_context_data(self, **kwargs):
kwargs['all_cabinet'] = Cabinet.objects.all()
return super().get_context_data(**kwargs)
class NatRuleListView(SandboxListView):
model = NatRule
fields = ['id', 'internet_ip', 'src_port', 'lan_ip', 'dest_port', 'state', 'dev_cabinet__number', 'desc']
def get_filters(self):
data = self.request.GET
filters = {}
if 'internet_ip' in data and data['internet_ip']:
filters['internet_ip__icontains'] = data['internet_ip']
if 'src_port' in data and data['src_port']:
filters['src_port'] = data['src_port']
if 'lan_ip' in data and data['lan_ip']:
filters['lan_ip__icontains'] = data['lan_ip']
if 'dest_port' in data and data['dest_port']:
filters['dest_port'] = data['dest_port']
if 'dev_cabinet' in data and data['dev_cabinet']:
filters['dev_cabinet'] = data['dev_cabinet']
if 'desc' in data and data['desc']:
filters['desc__icontains'] = data['desc']
return filters
class NatRuleDeleteView(SandboxDeleteView):
model = NatRule
class DomainNameView(LoginRequiredMixin, BreadcrumbMixin, TemplateView):
template_name = 'cmdb/domainname.html'
def get_context_data(self, **kwargs):
kwargs['all_operation'] = Code.objects.filter(parent__key='OPERATION_TYPE')
return super().get_context_data(**kwargs)
class DomainNameCreateView(SandboxCreateView):
model = DomainName
form_class = DomainNameForm
def get_context_data(self, **kwargs):
kwargs['all_supplier'] = Supplier.objects.all()
kwargs['all_nat'] = NatRule.objects.all()
kwargs['all_operation'] = Code.objects.filter(parent__key='OPERATION_TYPE')
return super().get_context_data(**kwargs)
class DomainNameUpdateView(SandboxUpdateView):
model = DomainName
form_class = DomainNameForm
def get_context_data(self, **kwargs):
kwargs['all_supplier'] = Supplier.objects.all()
kwargs['all_nat'] = NatRule.objects.all()
kwargs['all_operation'] = Code.objects.filter(parent__key='OPERATION_TYPE')
return super().get_context_data(**kwargs)
class DomainNameListView(SandboxListView):
model = DomainName
fields = ['id', 'domain', 'dn_type', 'resolution_server__firm',
'domain_provider__firm', 'operation_type__value',
'state', 'warrantyDate', 'desc']
def get_filters(self):
data = self.request.GET
filters = {}
if 'select' in data and data['select']:
select = int(data['select'])
if select == 0:
date_time = datetime.today()
filters['warrantyDate__lte'] = date_time
if select == 1:
now = datetime.today()
date_time = now + timedelta(days=30)
filters['warrantyDate__range'] = (now, date_time)
if 'dn_type' in data and data['dn_type']:
filters['dn_type'] = data['dn_type']
if 'domain' in data and data['domain']:
filters['domain__icontains'] = data['domain']
if 'operation_type' in data and data['operation_type']:
filters['operation_type'] = data['operation_type']
return filters
class DomainNameDeleteView(SandboxDeleteView):
model = DomainName
class DomainName2NatRule(LoginRequiredMixin, View):
def get(self, request):
ret = dict()
if 'id' in request.GET and request.GET['id']:
domain_name = get_object_or_404(DomainName, pk=int(request.GET['id']))
ret['all_nat'] = domain_name.nat_rule.all()
return render(request, 'cmdb/domainname2natrule.html', ret)

118
apps/cmdb/views_scan.py Normal file
View File

@@ -0,0 +1,118 @@
# @Time : 2018/12/29 19:25
# @Author : RobbieHan
# @File : views_scan.py
import ast
import logging
from ruamel import yaml
from django.views.generic import View, TemplateView
from django.http import JsonResponse
from django.shortcuts import render, get_object_or_404
from celery_once import AlreadyQueued
from system.mixin import LoginRequiredMixin
from custom import BreadcrumbMixin, SandboxListView, SandboxDeleteView
from utils.sandbox_utils import ConfigFileMixin
from system.models import Menu
from .models import (DeviceScanInfo, ConnectionInfo, DeviceInfo,
ConnectionAbstract, DeviceAbstract)
from .tasks import scan_execution
error_logger = logging.getLogger('sandbox_error')
class ScanConfigView(LoginRequiredMixin, BreadcrumbMixin, ConfigFileMixin, View):
def get(self, request):
menu = Menu.get_menu_by_request_url(request.path_info)
template_name = 'cmdb/scan_config.html'
context = self.get_conf_content()
context.update(menu)
return render(request, template_name, context)
def post(self, request):
ret = dict(result=False)
config = dict()
hosts = request.POST
try:
config['net_address'] = ast.literal_eval(hosts['net_address'])
config['ssh_username'] = hosts['ssh_username']
config['ssh_port'] = hosts['ssh_port']
config['ssh_password'] = hosts['ssh_password']
config['ssh_private_key'] = hosts['ssh_private_key']
config['commands'] = ast.literal_eval(hosts['commands'])
config['auth_type'] = hosts['auth_type']
config['scan_type'] = hosts['scan_type']
config['email'] = hosts['email']
config['send_email'] = hosts['send_email']
data = dict(hosts=config)
config_file = self.get_config_file()
with open(config_file, 'w', encoding='utf-8') as f:
yaml.dump(data, f, Dumper=yaml.RoundTripDumper, indent=4)
ret['result'] = True
except Exception as e:
error_logger.error(e)
return JsonResponse(ret)
class DeviceScanView(LoginRequiredMixin, BreadcrumbMixin, TemplateView):
template_name = 'cmdb/device_scan.html'
class DeviceScanListView(SandboxListView):
model = DeviceScanInfo
fields = ['id', 'sys_hostname', 'hostname', 'mac_address', 'auth_type', 'status', 'os_type', 'device_type']
class DeviceScanDetailView(LoginRequiredMixin, View):
def get(self, request):
ret = Menu.get_menu_by_request_url(request.path_info)
if 'id' in request.GET and request.GET['id']:
device = get_object_or_404(DeviceScanInfo, pk=int(request.GET['id']))
ret['device'] = device
return render(request, 'cmdb/device_scan_detail.html', ret)
class DeviceScanDeleteView(SandboxDeleteView):
model = DeviceScanInfo
class DeviceScanExecView(LoginRequiredMixin, View):
def get(self, request):
ret = dict(status='fail')
try:
scan_execution.delay()
ret['status'] = 'success'
except AlreadyQueued:
ret['status'] = 'already_queued'
return JsonResponse(ret)
class DeviceScanInboundView(LoginRequiredMixin, View):
def post(self, request):
ret = dict(result=False)
login_succeed = list(DeviceScanInfo.objects.filter(status='succeed').values())
connection_fields = [field.name for field in ConnectionAbstract._meta.fields if field.name is not 'id']
device_fields = [field.name for field in DeviceAbstract._meta.fields if field.name is not 'id']
device_fields.append('hostname')
for host in login_succeed:
connection_defaults = {key: host[key] for key in host.keys() & connection_fields}
device_defaults = {key: host[key] for key in host.keys() & device_fields}
connection_info, _ = ConnectionInfo.objects.update_or_create(
hostname=host['hostname'],
defaults=connection_defaults
)
connection_id = int(getattr(connection_info, 'id'))
device_defaults['dev_connection'] = connection_id
device_defaults['changed_by_id'] = request.user.id
DeviceInfo.objects.update_or_create(
hostname=host['hostname'],
defaults=device_defaults
)
ret['result'] = True
return JsonResponse(ret)

162
apps/custom.py Normal file
View File

@@ -0,0 +1,162 @@
# @Time : 2018/11/9 22:06
# @Author : RobbieHan
# @File : custom.py
import json
import re
from django.views.generic import CreateView, UpdateView, View
from django.shortcuts import HttpResponse
from django.http import Http404, JsonResponse
from django.db.models.query import QuerySet
from django.core.exceptions import ImproperlyConfigured
from system.mixin import LoginRequiredMixin
from system.models import Menu
class BreadcrumbMixin:
def get_context_data(self, **kwargs):
menu = Menu.get_menu_by_request_url(url=self.request.path_info)
if menu is not None:
kwargs.update(menu)
return super().get_context_data(**kwargs)
class SandboxGetObjectMixin:
def get_object(self, queryset=None):
if queryset is None:
queryset = self.get_queryset()
if 'id' in self.request.GET and self.request.GET['id']:
queryset = queryset.filter(id=int(self.request.GET['id']))
elif 'id' in self.request.POST and self.request.POST['id']:
queryset = queryset.filter(id=int(self.request.POST['id']))
else:
raise AttributeError("Generic detail view %s must be called with id. "
% self.__class__.__name__)
try:
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404("No %(verbose_name)s found matching the query" %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
class SandboxMultipleObjectMixin:
filters = {}
fields = []
queryset = None
model = None
def get_queryset(self):
if self.queryset is not None:
queryset = self.queryset
if isinstance(queryset, QuerySet):
queryset = queryset.all()
elif self.model is not None:
queryset = self.model._default_manager.all()
else:
raise ImproperlyConfigured(
"%(cls)s is missing a QuerySet. Define "
"%(cls)s.model, %(cls)s.queryset."
% {'cls': self.__class__.__name__}
)
return queryset
def get_datatables_paginator(self, request):
datatables = request.GET
draw = int(datatables.get('draw'))
start = int(datatables.get('start'))
length = int(datatables.get('length'))
order_column = datatables.get('order[0][column]')
order_dir = datatables.get('order[0][dir]')
order_field = datatables.get('columns[{}][data]'.format(order_column))
queryset = self.get_queryset()
if order_dir == 'asc':
queryset = queryset.order_by(order_field)
else:
queryset = queryset.order_by('-{0}'.format(order_field))
record_total_count = queryset.count()
filters = self.get_filters()
fields = self.get_fields()
if filters:
queryset = queryset.filter(**filters)
if fields:
queryset = queryset.values(*fields)
record_filter_count = queryset.count()
object_list = queryset[start:(start + length)]
data = list(object_list)
return {
'draw': draw,
'recordsTotal': record_total_count,
'recordsFiltered': record_filter_count,
'data': data,
}
def get_filters(self):
return self.filters
def get_fields(self):
return self.fields
class SandboxEditViewMixin:
def post(self, request, *args, **kwargs):
res = dict(result=False)
form = self.get_form()
if form.is_valid():
form.save()
res['result'] = True
else:
pattern = '<li>.*?<ul class=.*?><li>(.*?)</li>'
form_errors = str(form.errors)
errors = re.findall(pattern, form_errors)
res['error'] = errors[0]
return HttpResponse(json.dumps(res), content_type='application/json')
class SandboxCreateView(LoginRequiredMixin, SandboxEditViewMixin, CreateView):
""""
View for create an object, with a response rendered by a template.
Returns information with Json when the data is created successfully or fails.
"""
class SandboxUpdateView(LoginRequiredMixin, SandboxEditViewMixin, SandboxGetObjectMixin, UpdateView):
"""View for updating an object, with a response rendered by a template."""
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super().post(request, *args, **kwargs)
class SandboxListView(LoginRequiredMixin, SandboxMultipleObjectMixin, View):
"""
JsonResponse some json of objects, set by `self.model` or `self.queryset`.
"""
def get(self, request):
context = self.get_datatables_paginator(request)
return JsonResponse(context)
class SandboxDeleteView(LoginRequiredMixin, SandboxMultipleObjectMixin, View):
def post(self, request):
context = dict(result=False)
queryset = self.get_queryset()
if 'id' in request.POST and request.POST['id']:
id_list = map(int, request.POST['id'].split(','))
queryset.filter(id__in=id_list).delete()
context['result'] = True
else:
raise AttributeError("Sandbox delete view %s must be called with id. "
% self.__class__.__name__)
return JsonResponse(context)

View File

@@ -0,0 +1 @@
default_app_config = 'system.apps.SystemConfig'

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -2,4 +2,10 @@ from django.apps import AppConfig
class SystemConfig(AppConfig):
name = 'apps.system'
name = 'system'
def ready(self):
from .signals import create_menu
from .signals import user_logged_in_handler
from .signals import user_logged_out_handler
from .signals import user_login_failed_handler

View File

@@ -2,9 +2,126 @@
# @Author : RobbieHan
# @File : forms.py
import re
from django import forms
from django.contrib.auth import get_user_model
from .models import Structure, Menu
User = get_user_model()
class LoginForm(forms.Form):
username = forms.CharField(required=True, error_messages={"requeired": "请填写用户名"})
password = forms.CharField(required=True, error_messages={"requeired": "请填写密码"})
password = forms.CharField(required=True, error_messages={"requeired": "请填写密码"})
class StructureForm(forms.ModelForm):
class Meta:
model = Structure
fields = ['type', 'name', 'parent']
class UserCreateForm(forms.ModelForm):
password = forms.CharField(
required=True,
min_length=6,
max_length=20,
error_messages={
"required": "密码不能为空",
"min_length": "密码长度最少6位数",
}
)
confirm_password = forms.CharField(
required=True,
min_length=6,
max_length=20,
error_messages={
"required": "确认密码不能为空",
"min_length": "密码长度最少6位数",
}
)
class Meta:
model = User
fields = [
'name', 'gender', 'birthday', 'username', 'mobile', 'email',
'department', 'post', 'superior', 'is_active', 'roles', 'password'
]
error_messages = {
"name": {"required": "姓名不能为空"},
"username": {"required": "用户名不能为空"},
"email": {"required": "邮箱不能为空"},
"mobile": {
"required": "手机号码不能为空",
"max_length": "输入有效的手机号码",
"min_length": "输入有效的手机号码"
}
}
def clean(self):
cleaned_data = super(UserCreateForm, self).clean()
username = cleaned_data.get("username")
mobile = cleaned_data.get("mobile", "")
email = cleaned_data.get("email")
password = cleaned_data.get("password")
confirm_password = cleaned_data.get("confirm_password")
if User.objects.filter(username=username).count():
raise forms.ValidationError('用户名:{}已存在'.format(username))
if password != confirm_password:
raise forms.ValidationError("两次密码输入不一致")
if User.objects.filter(mobile=mobile).count():
raise forms.ValidationError('手机号码:{}已存在'.format(mobile))
REGEX_MOBILE = "^1[3578]\d{9}$|^147\d{8}$|^176\d{8}$"
if not re.match(REGEX_MOBILE, mobile):
raise forms.ValidationError("手机号码非法")
if User.objects.filter(email=email).count():
raise forms.ValidationError('邮箱:{}已存在'.format(email))
class UserUpdateForm(forms.ModelForm):
class Meta:
model = User
fields = [
'name', 'gender', 'birthday', 'username', 'mobile', 'email',
'department', 'post', 'superior', 'is_active', 'roles'
]
class PasswordChangeForm(forms.Form):
password = forms.CharField(
required=True,
min_length=6,
max_length=20,
error_messages={
"required": u"密码不能为空"
})
confirm_password = forms.CharField(
required=True,
min_length=6,
max_length=20,
error_messages={
"required": u"确认密码不能为空"
})
def clean(self):
cleaned_data = super(PasswordChangeForm, self).clean()
password = cleaned_data.get("password")
confirm_password = cleaned_data.get("confirm_password")
if password != confirm_password:
raise forms.ValidationError("两次密码输入不一致")
class MenuForm(forms.ModelForm):
class Meta:
model = Menu
fields = '__all__'

100
apps/system/middleware.py Normal file
View File

@@ -0,0 +1,100 @@
import re
from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
from django.shortcuts import render
class MenuCollection(MiddlewareMixin):
def get_user(self, request):
return request.user
def get_menu_from_role(self, request, user=None):
if user is None:
user = self.get_user(request)
try:
menus = user.roles.values(
'permissions__id',
'permissions__name',
'permissions__url',
'permissions__icon',
'permissions__code',
'permissions__parent'
).distinct()
return [menu for menu in menus if menu['permissions__id'] is not None]
except AttributeError:
return None
def get_permission_url(self, request):
role_menus = self.get_menu_from_role(request)
if role_menus is not None:
permission_url_list = [menu['permissions__url'] for menu in role_menus]
return permission_url_list
def get_permission_menu(self, request):
permission_menu_list = []
role_menus = self.get_menu_from_role(request)
if role_menus is not None:
for item in role_menus:
menu = {
'id': item['permissions__id'],
'name': item['permissions__name'],
'url': item['permissions__url'],
'icon': item['permissions__icon'],
'code': item['permissions__code'],
'parent': item['permissions__parent'],
'status': False,
'sub_menu': [],
}
permission_menu_list.append(menu)
return permission_menu_list
def get_top_reveal_menu(self, request):
top_menu = []
permission_menu_dict = {}
request_url = request.path_info
permission_menu_list = self.get_permission_menu(request)
if permission_menu_list is not None:
for menu in permission_menu_list:
url = menu['url']
if url and re.match(url, request_url):
menu['status'] = True
if menu['parent'] is None:
top_menu.insert(0, menu)
permission_menu_dict[menu['id']] = menu
menu_data = []
for i in permission_menu_dict:
if permission_menu_dict[i]['parent']:
pid = permission_menu_dict[i]['parent']
parent_menu = permission_menu_dict[pid]
parent_menu['sub_menu'].append(permission_menu_dict[i])
else:
menu_data.append(permission_menu_dict[i])
if [menu['sub_menu'] for menu in menu_data if menu['url'] in request_url]:
reveal_menu = [menu['sub_menu'] for menu in menu_data if menu['url'] in request_url][0]
else:
reveal_menu = None
return top_menu, reveal_menu
def process_request(self, request):
if self.get_top_reveal_menu(request):
request.top_menu, request.reveal_menu = self.get_top_reveal_menu(request)
request.permission_url_list = self.get_permission_url(request)
class RbacMiddleware(MiddlewareMixin):
def process_request(self, request):
if hasattr(request, 'permission_url_list'):
request_url = request.path_info
permission_url = request.permission_url_list
for url in settings.SAFE_URL:
if re.match(url, request_url):
return None
if request_url in permission_url:
return None
else:
return render(request, 'page404.html')

View File

@@ -1,112 +0,0 @@
# Generated by Django 2.1.2 on 2018-10-17 15:09
from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
]
operations = [
migrations.CreateModel(
name='UserProfile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('name', models.CharField(default='', max_length=20, verbose_name='姓名')),
('birthday', models.DateField(blank=True, null=True, verbose_name='出生日期')),
('gender', models.CharField(choices=[('male', ''), ('female', '')], default='male', max_length=10, verbose_name='性别')),
('mobile', models.CharField(default='', max_length=11, verbose_name='手机号码')),
('email', models.EmailField(max_length=50, verbose_name='邮箱')),
('image', models.ImageField(blank=True, default='image/default.jpg', null=True, upload_to='image/%Y/%m')),
('post', models.CharField(blank=True, max_length=50, null=True, verbose_name='职位')),
],
options={
'verbose_name': '用户信息',
'verbose_name_plural': '用户信息',
'ordering': ['id'],
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='Menu',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=30, unique=True, verbose_name='菜单名')),
('icon', models.CharField(blank=True, max_length=50, null=True, verbose_name='图标')),
('code', models.CharField(blank=True, max_length=50, null=True, verbose_name='编码')),
('url', models.CharField(blank=True, max_length=128, null=True, unique=True)),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.Menu', verbose_name='父菜单')),
],
options={
'verbose_name': '菜单',
'verbose_name_plural': '菜单',
},
),
migrations.CreateModel(
name='Role',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=32, unique=True, verbose_name='角色')),
('desc', models.CharField(blank=True, max_length=50, null=True, verbose_name='描述')),
('permissions', models.ManyToManyField(blank=True, to='system.Menu', verbose_name='URL授权')),
],
),
migrations.CreateModel(
name='Structure',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=60, verbose_name='名称')),
('type', models.CharField(choices=[('unit', '单位'), ('department', '部门')], default='department', max_length=20, verbose_name='类型')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.Structure', verbose_name='父类架构')),
],
options={
'verbose_name': '组织架构',
'verbose_name_plural': '组织架构',
},
),
migrations.AddField(
model_name='userprofile',
name='department',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.Structure', verbose_name='部门'),
),
migrations.AddField(
model_name='userprofile',
name='groups',
field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'),
),
migrations.AddField(
model_name='userprofile',
name='roles',
field=models.ManyToManyField(blank=True, to='system.Role', verbose_name='角色'),
),
migrations.AddField(
model_name='userprofile',
name='superior',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='上级主管'),
),
migrations.AddField(
model_name='userprofile',
name='user_permissions',
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
),
]

View File

@@ -11,6 +11,7 @@ class Menu(models.Model):
icon = models.CharField(max_length=50, null=True, blank=True, verbose_name="图标")
code = models.CharField(max_length=50, null=True, blank=True, verbose_name="编码")
url = models.CharField(max_length=128, unique=True, null=True, blank=True)
number = models.FloatField(null=True, blank=True, verbose_name="编号")
def __str__(self):
return self.name
@@ -18,10 +19,14 @@ class Menu(models.Model):
class Meta:
verbose_name = '菜单'
verbose_name_plural = verbose_name
ordering = ['number']
@classmethod
def get_menu_by_request_url(cls, url):
return dict(menu=Menu.objects.get(url=url))
try:
return dict(menu=Menu.objects.get(url=url))
except:
None
class Role(models.Model):

50
apps/system/signals.py Normal file
View File

@@ -0,0 +1,50 @@
import logging
from django.dispatch import receiver
from django.db.models.signals import post_save
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
from .models import Role, Menu
error_logger = logging.getLogger('sandbox_error')
info_logger = logging.getLogger('sandbox_info')
@receiver(post_save, sender=Menu)
def create_menu(sender, instance, **kwargs):
queryset = Role.objects.filter(id=1)
try:
admin_role = queryset.get()
admin_role.permissions.add(instance)
except queryset.model.DoesNotExist as e:
error_logger.error(e)
@receiver(user_logged_in)
def user_logged_in_handler(sender, request, user, **kwargs):
ip = request.META.get('REMOTE_ADDR')
msg = 'login user: {user}, remote ip: {ip}, action: login, status: successed'.format(
user=user.username,
ip=ip,
)
info_logger.info(msg)
@receiver(user_logged_out)
def user_logged_out_handler(sender, request, user, **kwargs):
ip = request.META.get('REMOTE_ADDR')
msg = 'login user: {user}, remote ip: {ip}, action: logout, status: successed'.format(
user=user.username,
ip=ip,
)
info_logger.info(msg)
@receiver(user_login_failed)
def user_login_failed_handler(sender, credentials, request, **kwargs):
msg = 'logout failed for: {credentials}'.format(
credentials=credentials,
)
info_logger.info(msg)

View File

@@ -1,3 +1,4 @@
from django.test import TestCase
# Create your tests here.

40
apps/system/urls.py Normal file
View File

@@ -0,0 +1,40 @@
from django.urls import path
from .views import SystemView
from . import views_structure, views_user, views_menu, views_role
app_name = 'system'
urlpatterns = [
path('', SystemView.as_view(), name='login'),
path('basic/structure/', views_structure.StructureView.as_view(), name='basic-structure'),
path('basic/structure/create/', views_structure.StructureCreateView.as_view(), name='basic-structure-create'),
path('basic/structure/list/', views_structure.StructureListView.as_view(), name='basic-structure-list'),
path('basic/structure/delete/', views_structure.StructureDeleteView.as_view(), name='basic-structure-delete'),
path('basic/structure/add_user/', views_structure.Structure2UserView.as_view(), name='basic-structure-add_user'),
path('basic/user/', views_user.UserView.as_view(), name='basic-user'),
path('basic/user/list/', views_user.UserListView.as_view(), name='basic-user-list'),
path('basic/user/create/', views_user.UserCreateView.as_view(), name='basic-user-create'),
path('basic/user/detail/', views_user.UserDetailView.as_view(), name='basic-user-detail'),
path('basic/user/update/', views_user.UserUpdateView.as_view(), name='basic-user-update'),
path('basic/user/password_change/', views_user.PasswordChangeView.as_view(), name='basic-user-password_change'),
path('basic/user/delete/', views_user.UserDeleteView.as_view(), name='basic-user-delete'),
path('basic/user/enable/', views_user.UserEnableView.as_view(), name='basic-user-enable'),
path('basic/user/disable/', views_user.UserDisableView.as_view(), name='basic-user-disable'),
path('rbac/menu/', views_menu.MenuListView.as_view(), name='rbac-menu'),
path('rbac/menu/create/', views_menu.MenuCreateView.as_view(), name='rbac-menu-create'),
path('rbac/menu/update/', views_menu.MenuUpdateView.as_view(), name='rbac-menu-update'),
path('rbac/role/', views_role.RoleView.as_view(), name='rbac-role'),
path('rbac/role/create/', views_role.RoleCreateView.as_view(), name='rbac-role-create'),
path('rbac/role/list/', views_role.RoleListView.as_view(), name='rbac-role-list'),
path('rbac/role/update/', views_role.RoleUpdateView.as_view(), name='rbac-role-update'),
path('rbac/role/delete/', views_role.RoleDeleteView.as_view(), name='rbac-role-delete'),
path('rbac/role/role2user/', views_role.Role2UserView.as_view(), name="rbac-role-role2user"),
path('rbac/role/role2menu/', views_role.Role2MenuView.as_view(), name="rbac-role-role2menu"),
path('rbac/role/role2menu_list/', views_role.Role2MenuListView.as_view(), name="rbac-role-role2menu_list"),
path('personal_password_change/', views_user.PersonalPasswordChangeView.as_view(), name='personal_password_change')
]

View File

@@ -1,3 +1,10 @@
from django.shortcuts import render
from django.views.generic import TemplateView
from .mixin import LoginRequiredMixin
from custom import BreadcrumbMixin
class SystemView(LoginRequiredMixin, BreadcrumbMixin, TemplateView):
template_name = 'system/system_index.html'
# Create your views here.

29
apps/system/views_menu.py Normal file
View File

@@ -0,0 +1,29 @@
from django.views.generic import ListView
from .mixin import LoginRequiredMixin
from apps.custom import SandboxCreateView, SandboxUpdateView, BreadcrumbMixin
from .models import Menu
class MenuCreateView(SandboxCreateView):
model = Menu
fields = '__all__'
def get_context_data(self, **kwargs):
kwargs['menu_all'] = Menu.objects.all()
return super().get_context_data(**kwargs)
class MenuListView(LoginRequiredMixin, BreadcrumbMixin, ListView):
model = Menu
context_object_name = 'menu_all'
class MenuUpdateView(SandboxUpdateView):
model = Menu
fields = '__all__'
template_name_suffix = '_update'
def get_context_data(self, **kwargs):
kwargs['menu_all'] = Menu.objects.all()
return super().get_context_data(**kwargs)

118
apps/system/views_role.py Normal file
View File

@@ -0,0 +1,118 @@
# @Time : 2018/11/13 23:25
# @Author : RobbieHan
# @File : views_role.py
import json
from django.views.generic.base import View
from django.shortcuts import HttpResponse, get_object_or_404
from django.views.generic import TemplateView
from django.contrib.auth import get_user_model
from django.shortcuts import render
from .mixin import LoginRequiredMixin
from .models import Role, Menu
from custom import SandboxCreateView, SandboxUpdateView, BreadcrumbMixin
User = get_user_model()
class RoleView(LoginRequiredMixin, BreadcrumbMixin, TemplateView):
template_name = 'system/role.html'
class RoleCreateView(SandboxCreateView):
model = Role
fields = '__all__'
class RoleListView(LoginRequiredMixin, View):
def get(self, reqeust):
fields = ['id', 'name', 'desc']
ret = dict(data=list(Role.objects.values(*fields)))
return HttpResponse(json.dumps(ret), content_type='application/json')
class RoleUpdateView(SandboxUpdateView):
model = Role
fields = '__all__'
template_name_suffix = '_update'
class RoleDeleteView(LoginRequiredMixin, View):
def post(self, request):
ret = dict(result=False)
if 'id' in request.POST and request.POST['id']:
id_list = map(int, request.POST['id'].split(','))
Role.objects.filter(id__in=id_list).delete()
ret['result'] = True
return HttpResponse(json.dumps(ret), content_type='application/json')
class Role2UserView(LoginRequiredMixin, View):
"""
角色关联用户
"""
def get(self, request):
if 'id' in request.GET and request.GET['id']:
role = get_object_or_404(Role, pk=int(request.GET.get('id')))
added_users = role.userprofile_set.all()
all_users = User.objects.all()
un_add_users = set(all_users).difference(added_users)
ret = dict(role=role, added_users=added_users, un_add_users=list(un_add_users))
return render(request, 'system/role_role2user.html', ret)
def post(self, request):
res = dict(result=False)
id_list = None
role = get_object_or_404(Role, pk=int(request.POST.get('id')))
if 'to' in request.POST and request.POST['to']:
id_list = map(int, request.POST.getlist('to', []))
role.userprofile_set.clear()
if id_list:
for user in User.objects.filter(id__in=id_list):
role.userprofile_set.add(user)
res['result'] = True
return HttpResponse(json.dumps(res), content_type='application/json')
class Role2MenuView(LoginRequiredMixin, View):
"""
角色绑定菜单
"""
def get(self, request):
if 'id' in request.GET and request.GET['id']:
role = get_object_or_404(Role, pk=request.GET['id'])
ret = dict(role=role)
return render(request, 'system/role_role2menu.html', ret)
def post(self, request):
res = dict(result=False)
role = get_object_or_404(Role, pk=request.POST['id'])
tree = json.loads(self.request.POST['tree'])
role.permissions.clear()
for menu in tree:
if menu['checked'] is True:
menu_checked = get_object_or_404(Menu, pk=menu['id'])
role.permissions.add(menu_checked)
res['result'] = True
return HttpResponse(json.dumps(res), content_type='application/json')
class Role2MenuListView(LoginRequiredMixin, View):
"""
获取zTree菜单列表
"""
def get(self, request):
fields = ['id', 'name', 'parent']
if 'id' in request.GET and request.GET['id']:
role = Role.objects.get(id=request.GET.get('id'))
role_menus = role.permissions.values(*fields)
ret = dict(data=list(role_menus))
else:
menus = Menu.objects.all()
ret = dict(data=list(menus.values(*fields)))
return HttpResponse(json.dumps(ret), content_type='application/json')

View File

@@ -0,0 +1,90 @@
# @Time : 2018/10/18 23:04
# @Author : RobbieHan
# @File : views_structure.py
import json
from django.views.generic.base import TemplateView
from django.views.generic.base import View
from django.shortcuts import render
from django.shortcuts import HttpResponse
from django.shortcuts import get_object_or_404
from django.contrib.auth import get_user_model
from .mixin import LoginRequiredMixin
from .models import Structure
from .forms import StructureForm
from apps.custom import BreadcrumbMixin
User = get_user_model()
class StructureView(LoginRequiredMixin, BreadcrumbMixin, TemplateView):
template_name = 'system/structure/structure.html'
class StructureCreateView(LoginRequiredMixin, View):
def get(self, request):
ret = dict(structure_all=Structure.objects.all())
if 'id' in request.GET and request.GET['id']:
structure = get_object_or_404(Structure, pk=request.GET['id'])
ret['structure'] = structure
return render(request, 'system/structure/structure_create.html', ret)
def post(self, request):
res = dict(result=False)
if 'id' in request.POST and request.POST['id']:
structure = get_object_or_404(Structure, pk=request.POST['id'])
else:
structure = Structure()
structure_form = StructureForm(request.POST, instance=structure)
if structure_form.is_valid():
structure_form.save()
res['result'] = True
return HttpResponse(json.dumps(res), content_type='application/json')
class StructureListView(LoginRequiredMixin, View):
def get(self, request):
fields = ['id', 'name', 'type', 'parent__name']
ret = dict(data=list(Structure.objects.values(*fields)))
return HttpResponse(json.dumps(ret), content_type='application/json')
class StructureDeleteView(LoginRequiredMixin, View):
def post(self, request):
ret = dict(result=False)
if 'id' in request.POST and request.POST['id']:
id_list = map(int, request.POST['id'].split(','))
Structure.objects.filter(id__in=id_list).delete()
ret['result'] = True
return HttpResponse(json.dumps(ret), content_type='application/json')
class Structure2UserView(LoginRequiredMixin, View):
def get(self, request):
if 'id' in request.GET and request.GET['id']:
structure = get_object_or_404(Structure, pk=int(request.GET['id']))
added_users = structure.userprofile_set.all()
all_users = User.objects.all()
un_add_users = set(all_users).difference(added_users)
ret = dict(structure=structure, added_users=added_users, un_add_users=list(un_add_users))
return render(request, 'system/structure/structure_user.html', ret)
def post(self, request):
res = dict(result=False)
id_list = None
structure = get_object_or_404(Structure, pk=int(request.POST['id']))
if 'to' in request.POST and request.POST.getlist('to', []):
id_list = map(int, request.POST.getlist('to', []))
structure.userprofile_set.clear()
if id_list:
for user in User.objects.filter(id__in=id_list):
structure.userprofile_set.add(user)
res['result'] = True
return HttpResponse(json.dumps(res), content_type='application/json')

View File

@@ -2,20 +2,31 @@
# @Author : RobbieHan
# @File : views_user.py
from django.shortcuts import render
from django.views.generic.base import View
from django.http import HttpResponseRedirect
from django.contrib.auth import authenticate, login, logout
from django.urls import reverse
import re
import json
from .forms import LoginForm
from django.shortcuts import render, HttpResponse
from django.views.generic.base import View, TemplateView
from django.http import HttpResponseRedirect
from django.contrib.auth import authenticate, login, logout, get_user_model
from django.urls import reverse
from django.contrib.auth.hashers import make_password
from django.shortcuts import get_object_or_404
from django.db.models import Q
from .forms import LoginForm, UserCreateForm, UserUpdateForm, PasswordChangeForm
from .mixin import LoginRequiredMixin
from .models import Structure, Role
from apps.custom import BreadcrumbMixin
User = get_user_model()
class IndexView(LoginRequiredMixin, View):
def get(self, request):
return render(request, 'index.html')
#return render(request, 'index.html')
return HttpResponseRedirect('/cmdb/')
class LoginView(View):
@@ -24,12 +35,13 @@ class LoginView(View):
if not request.user.is_authenticated:
return render(request, 'system/users/login.html')
else:
return HttpResponseRedirect('/')
return HttpResponseRedirect('/cmdb/')
def post(self, request):
redirect_to = request.GET.get('next', '/')
redirect_to = request.GET.get('next', '/cmdb/')
login_form = LoginForm(request.POST)
ret = dict(login_form=login_form)
print(request.META.get('REMOTE_ADDR'))
if login_form.is_valid():
user_name = request.POST['username']
pass_word = request.POST['password']
@@ -51,4 +63,190 @@ class LogoutView(View):
def get(self, request):
logout(request)
return HttpResponseRedirect(reverse('login'))
return HttpResponseRedirect(reverse('login'))
class UserView(LoginRequiredMixin, BreadcrumbMixin, TemplateView):
template_name = 'system/users/user.html'
class UserListView(LoginRequiredMixin, View):
def get(self, request):
fields = ['id', 'name', 'gender', 'mobile', 'email', 'department__name', 'post', 'superior__name', 'is_active']
filters = dict()
if 'select' in request.GET and request.GET['select']:
filters['is_active'] = request.GET['select']
ret = dict(data=list(User.objects.filter(**filters).values(*fields)))
return HttpResponse(json.dumps(ret), content_type='application/json')
class UserCreateView(LoginRequiredMixin, View):
"""
添加用户
"""
def get(self, request):
users = User.objects.exclude(username='admin')
structures = Structure.objects.values()
roles = Role.objects.values()
ret = {
'users': users,
'structures': structures,
'roles': roles,
}
return render(request, 'system/users/user_create.html', ret)
def post(self, request):
user_create_form = UserCreateForm(request.POST)
if user_create_form.is_valid():
new_user = user_create_form.save(commit=False)
new_user.password = make_password(user_create_form.cleaned_data['password'])
new_user.save()
user_create_form.save_m2m()
ret = {'status': 'success'}
else:
pattern = '<li>.*?<ul class=.*?><li>(.*?)</li>'
errors = str(user_create_form.errors)
user_create_form_errors = re.findall(pattern, errors)
ret = {
'status': 'fail',
'user_create_form_errors': user_create_form_errors[0]
}
return HttpResponse(json.dumps(ret), content_type='application/json')
class UserDetailView(LoginRequiredMixin, View):
def get(self, request):
user = get_object_or_404(User, pk=int(request.GET['id']))
users = User.objects.exclude(Q(id=int(request.GET['id'])) | Q(username='admin'))
structures = Structure.objects.values()
roles = Role.objects.values()
user_roles = user.roles.values()
ret = {
'user': user,
'structures': structures,
'users': users,
'roles': roles,
'user_roles': user_roles
}
return render(request, 'system/users/user_detail.html', ret)
class UserUpdateView(LoginRequiredMixin, View):
def post(self, request):
if 'id' in request.POST and request.POST['id']:
user = get_object_or_404(User, pk=int(request.POST['id']))
else:
user = get_object_or_404(User, pk=int(request.user.id))
user_update_form = UserUpdateForm(request.POST, instance=user)
if user_update_form.is_valid():
user_update_form.save()
ret = {"status": "success"}
else:
ret = {"status": "fail", "message": user_update_form.errors}
return HttpResponse(json.dumps(ret), content_type="application/json")
class PasswordChangeView(LoginRequiredMixin, View):
def get(self, request):
ret = dict()
if 'id' in request.GET and request.GET['id']:
user = get_object_or_404(User, pk=int(request.GET.get('id')))
ret['user'] = user
return render(request, 'system/users/passwd_change.html', ret)
def post(self, request):
if 'id' in request.POST and request.POST['id']:
user = get_object_or_404(User, pk=int(request.POST['id']))
form = PasswordChangeForm(request.POST)
if form.is_valid():
new_password = request.POST['password']
user.set_password(new_password)
user.save()
ret = {'status': 'success'}
else:
pattern = '<li>.*?<ul class=.*?><li>(.*?)</li>'
errors = str(form.errors)
password_change_form_errors = re.findall(pattern, errors)
ret = {
'status': 'fail',
'password_change_form_errors': password_change_form_errors[0]
}
return HttpResponse(json.dumps(ret), content_type='application/json')
class UserDeleteView(LoginRequiredMixin, View):
"""
删除数据:支持删除单条记录和批量删除
"""
def post(self, request):
ret = dict(result=False)
if 'id' in request.POST and request.POST['id']:
id_list = map(int, request.POST['id'].split(','))
User.objects.filter(id__in=id_list).delete()
ret['result'] = True
return HttpResponse(json.dumps(ret), content_type='application/json')
class UserEnableView(LoginRequiredMixin, View):
"""
启用用户:单个或批量启用
"""
def post(self, request):
if 'id' in request.POST and request.POST['id']:
id_nums = request.POST.get('id')
queryset = User.objects.extra(where=["id IN(" + id_nums + ")"])
queryset.filter(is_active=False).update(is_active=True)
ret = {'result': 'True'}
return HttpResponse(json.dumps(ret), content_type='application/json')
class UserDisableView(LoginRequiredMixin, View):
"""
启用用户:单个或批量启用
"""
def post(self, request):
if 'id' in request.POST and request.POST['id']:
id_nums = request.POST.get('id')
queryset = User.objects.extra(where=["id IN(" + id_nums + ")"])
queryset.filter(is_active=True).update(is_active=False)
ret = {'result': 'True'}
return HttpResponse(json.dumps(ret), content_type='application/json')
# 用户修改密码临时接口
class PersonalPasswordChangeView(LoginRequiredMixin, View):
"""
登陆用户修改个人密码
"""
def get(self, request):
ret = dict()
user = get_object_or_404(User, pk=int(request.user.id))
ret['user'] = user
return render(request, 'system/users/personal_passwd_change.html', ret)
def post(self, request):
user = get_object_or_404(User, pk=int(request.user.id))
form = PasswordChangeForm(request.POST)
if form.is_valid():
new_password = request.POST.get('password')
user.set_password(new_password)
user.save()
ret = {'status': 'success'}
else:
pattern = '<li>.*?<ul class=.*?><li>(.*?)</li>'
errors = str(form.errors)
passwd_change_form_errors = re.findall(pattern, errors)
ret = {
'status': 'fail',
'password_change_form_errors': passwd_change_form_errors[0]
}
return HttpResponse(json.dumps(ret), content_type='application/json')

3
apps/utils/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
# @Time : 2018/12/29 16:26
# @Author : RobbieHan
# @File : __init__.py.py

18
apps/utils/db_utils.py Normal file
View File

@@ -0,0 +1,18 @@
import pymongo
class MongodbDriver(object):
def __init__(self, db='device', collection='change_compare'):
self.client = pymongo.MongoClient('127.0.0.1', 27017)
self.db = self.client[db]
self.col = self.db[collection]
def insert(self, content):
return self.col.insert(content)
def find(self, sort_by, **filters,):
data = self.col.find(filters)
if sort_by:
data.sort(sort_by, pymongo.DESCENDING)
return data

209
apps/utils/sandbox_utils.py Normal file
View File

@@ -0,0 +1,209 @@
# @Time : 2018/12/29 19:22
# @Author : RobbieHan
# @File : sandbox_utils.py
import os
from django.conf import settings
import yaml
import logging
import nmap
import paramiko
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sandboxMP.settings')
error_logger = logging.getLogger('sandbox_error')
class ConfigFileMixin:
config_file = None
def get_config_file(self):
"""
Return 'config_file' that will be used to look up the scan hosts IP,
network, range of IP, or other config settings.
This method is called by the default implementation of get_hosts(),
"""
if self.config_file is None:
config_file = os.path.join(os.path.join(settings.BASE_DIR, 'config'), 'scanhosts.yml')
if os.path.exists(config_file):
return config_file
else:
msg = ' %(cls)s is missing a config file. Define %(cls)s.config_file, ' \
'or override %(cls)s.get_config_file().' % {'cls': self.__class__.__name__}
error_logger.error(msg)
raise ValueError(msg)
return self.config_file
def get_conf_content(self, *key):
"""
Get the configuration content from config file .
Example ssh_password, commands, email which is in the config file.
"""
_config = self.get_config_file()
with open(_config) as f:
content = yaml.load(f)
if key is not None:
try:
num = 0
while num < len(key):
content = content[key[num]]
num += 1
except Exception as e:
msg = '%(exc)s is not in %(config)s.' % {
'exc': e,
'config': _config
}
error_logger.error(msg)
raise ValueError(msg)
return content
def get_commands(self):
"""
Get the commands from config file.
"""
key = ['hosts', 'commands']
return self.get_conf_content(*key)
def get_net_address(self):
"""
Return the hosts that will be used to scan.
Subclasses can override this to return any hosts.
"""
key = ['hosts', 'net_address']
return self.get_conf_content(*key)
class SandboxScan(ConfigFileMixin):
def basic_scan(self):
"""
Use ICMP discovery online hosts and return online hosts.
"""
hosts = self.get_net_address()
nm = nmap.PortScanner()
nm.scan(hosts=hosts, arguments='-n -sP -PE')
online_hosts = nm.all_hosts()
return online_hosts
def os_scan(self):
"""
Get the system type by nmap scan and return hosts list with os type.
"""
hosts = self.get_net_address()
nm = nmap.PortScanner()
nm.scan(hosts=hosts, arguments='-n sS -O')
online_hosts = []
for host in nm.all_hosts():
try:
os_type = nm[host]['osmatch'][0]['osclass'][0]['osfamily']
except Exception:
os_type = 'unknown'
host_dict = {'host': host, 'os': os_type}
online_hosts.append(host_dict)
return online_hosts
def get_net_address(self):
"""
Return the hosts that will be used to scan.
Subclasses can override this to return any hosts.`
"""
hosts_list = super().get_net_address()
hosts = ' '.join(str(i) for i in hosts_list)
return hosts
class LoginExecution(ConfigFileMixin):
def login_execution(self, auth_type='password', **kwargs):
"""
Support two authentication modes: password or private_key, and auth_type default is password.
"""
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
if auth_type == 'password':
ssh.connect(
kwargs['hostname'],
kwargs['port'],
kwargs['username'],
kwargs['password'],
timeout=3,
)
kwargs['auth_type'] = 'password'
elif auth_type == 'private_key':
kwargs['auth_type'] = 'private_key'
private_key = paramiko.RSAKey.from_private_key_file(kwargs['private_key'])
ssh.connect(
kwargs['hostname'],
kwargs['port'],
kwargs['username'],
private_key,
timeout=3,
)
kwargs['status'] = 'succeed'
kwargs['error_message'] = ''
commands = self.get_commands()
for key, value in commands.items():
stdin, stdout, stderr = ssh.exec_command(value, timeout=5)
result = str(stdout.read()).strip('b').strip("'").strip('\\n')
kwargs[key] = result
except Exception as e:
msg = '%(exc)s hostname %(hostname)s' % {
'exc': e,
'hostname': kwargs['hostname']
}
error_logger.error(msg)
kwargs['status'] = 'failed'
kwargs['error_message'] = str(e)
return kwargs
def password_login_execution(self, **kwargs):
"""
Login to the remote system with a password.
Kwargs is a dict containing hostname, port, username and password.
Example: kwargs = {'hostname': '172.16.3.101', 'port': 22, 'username': 'root', 'password': 'paw123'}
"""
return self.login_execution(**kwargs)
def private_key_login_execution(self, **kwargs):
"""
Login to the remote system with a private_key.
Kwargs is a dict containing hostname, port, username and private key.
Example:kwargs = {'hostname': '172.16.3.101', 'port': 22, 'username': 'root', 'private_key': '/root/.ssh/id_rsa'}
"""
return self.login_execution(auth_type='private_key', **kwargs)
def get_auth_type(self):
key = ['hosts', 'auth_type']
return self.get_conf_content(*key)
def get_ssh_username(self):
key = ['hosts', 'ssh_username']
return self.get_conf_content(*key)
def get_ssh_port(self):
key = ['hosts', 'ssh_port']
return self.get_conf_content(*key)
def get_ssh_password(self):
key = ['hosts', 'ssh_password']
return self.get_conf_content(*key)
def get_ssh_private_key(self):
key = ['hosts', 'ssh_private_key']
return self.get_conf_content(*key)
def get_email(self):
key = ['hosts', 'email']
return self.get_conf_content(*key)
def get_send_email(self):
key = ['hosts', 'send_email']
return self.get_conf_content(*key)
def get_scan_type(self):
key = ['hosts', 'scan_type']
return self.get_conf_content(*key)

File diff suppressed because one or more lines are too long

19
config/celery_worker.ini Normal file
View File

@@ -0,0 +1,19 @@
[program:celery-worker]
command=/root/.virtualenvs/sandboxMP/bin/celery worker -A sandboxMP -l INFO
directory=/opt/app/sandboxMP
environment=PATH="/root/.virtualenvs/sandboxMP/bin/"
stdout_logfile=/opt/app/sandboxMP/slogs/celery_worker.log
stderr_logfile=/opt/app/sandboxMP/slogs/celery_worker.log
autostart=true
autorestart=true
priority=901
[program:celery-flower]
command=/root/.virtualenvs/sandboxMP/bin/celery flower --broker=redis://localhost:6379/0
directory=/opt/app/sandboxMP
environment=PATH="/root/.virtualenvs/sandboxMP/bin/"
stdout_logfile=/opt/app/sandboxMP/slogs/celery_flower.log
stderr_logfile=/opt/app/sandboxMP/slogs/celery_flower.log
autostart=true
autorestart=true
priority=900

64
config/nginx.conf Normal file
View File

@@ -0,0 +1,64 @@
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
server_tokens off;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
log_format nginxlog '$http_host '
'$remote_addr [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time '
'$upstream_response_time';
access_log /var/log/nginx/access.log nginxlog;
keepalive_timeout 60;
client_header_timeout 10;
client_body_timeout 15;
client_max_body_size 100M;
client_body_buffer_size 1024k;
gzip on;
gzip_min_length 1;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 3;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png app lication/vnd.ms-fontobject application/x-font-ttf image/svg+xml;
gzip_vary on;
upstream sandboxmp {
server 127.0.0.1:9000;
}
server {
listen 80;
server_name 0.0.0.0;
charset utf-8;
client_max_body_size 75M;
location /static {
alias /opt/app/sandboxMP/static;
}
location /media {
alias /opt/app/sandboxMP/media;
}
location / {
uwsgi_pass sandboxmp;
include /etc/nginx/uwsgi_params;
}
}
}

View File

@@ -0,0 +1,8 @@
[program:sandboxmp-uwsgi]
command=/root/.virtualenvs/sandboxMP/bin/uwsgi /etc/smp_uwsgi.ini
stdout_logfile=/var/log/uwsgi/smp_uwsgi.log
stderr_logfile=/var/log/uwsgi/smp_uwsgi.log
stdout_logfile_maxbytes = 20MB
autostart=true
autorestart=true
priority=905

18
config/scanhosts.yml Normal file
View File

@@ -0,0 +1,18 @@
hosts:
net_address:
- '172.16.3.0/24'
- '172.16.2.100-105'
ssh_username: 'root'
ssh_port: '22'
ssh_password: '1234@abcd.com'
ssh_private_key: '/root/.ssh/id_rsa'
commands:
sys_hostname: 'hostname'
mac_address: 'cat /sys/class/net/[^tsbvl]*/address'
sn_number: 'dmidecode -s system-serial-number'
os_type: 'cat /etc/redhat-release'
device_type: 'echo `dmidecode -s system-manufacturer && dmidecode -s system-product-name`'
email: 'robbie_han@outlook.com'
send_email: 'false'
scan_type: 'basic_scan'
auth_type: 'private_key'

12
config/smp_uwsgi.ini Normal file
View File

@@ -0,0 +1,12 @@
[uwsgi]
#http = 172.16.3.200:9000
socket = 127.0.0.1:9000
chdir = /opt/app/sandboxMP
module = sandboxMP.wsgi
#static-map=/static=/opt/app/sandboxMP/static
#daemonize =/var/log/uwsgi.log
master = Ture
vacuum = True
processes = 4
threads = 2
buffer-size=32768

Binary file not shown.

BIN
document/images/stepww.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

1
requirements/dev.txt Normal file
View File

@@ -0,0 +1 @@
-r pro.txt

14
requirements/pro.txt Normal file
View File

@@ -0,0 +1,14 @@
django==2.1.5
pillow==5.3.0
mysqlclient==1.3.13
ipython==7.1.1
pyyaml==4.2b1
ruamel.yaml==0.15.80
python-nmap==0.6.1
redis==3.2.1
pymongo==3.7.1
paramiko==2.4.2
django-simple-history==2.6.0
celery==4.2.1
celery-once==2.0.0
flower

View File

@@ -0,0 +1,5 @@
from __future__ import absolute_import, unicode_literals
from .celery import app as celery_app
__all__ = ('celery_app')

38
sandboxMP/celery.py Normal file
View File

@@ -0,0 +1,38 @@
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sandboxMP.settings')
app = Celery('sandbox')
app.config_from_object('django.conf:settings')
app.autodiscover_tasks()
BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/1'
CELERY_TIMEZONE = 'Asia/Shanghai'
CELERY_ENABLE_UTC = False
CELERYD_FORCE_EXECV = True
CELERYD_CONCURRENCY = 5
CELERY_ACKS_LATE = True
CELERYD_MAX_TASKS_PER_CHILD = 100
CELERYD_TASK_TIME_LIMIT = 60 * 5
app.conf.ONCE = {
'backend': 'celery_once.backends.Redis',
'settings': {
'url': 'redis://localhost:6379/2',
'default_timeout': 60 * 5
}
}

View File

@@ -13,6 +13,8 @@ https://docs.djangoproject.com/en/2.1/ref/settings/
import os
import sys
from .celery import *
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -27,7 +29,7 @@ SECRET_KEY = 'o6ijylqj@xxpvxzybcv2khtu5zk@y56nt4ptsb4dbgmdz8t%q='
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
ALLOWED_HOSTS = ['*']
# Application definition
@@ -39,7 +41,10 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'apps.system.apps.SystemConfig',
'simple_history',
'system',
'cmdb',
'cmdb.templatetags',
]
MIDDLEWARE = [
@@ -50,6 +55,9 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'apps.system.middleware.MenuCollection',
'apps.system.middleware.RbacMiddleware',
'simple_history.middleware.HistoryRequestMiddleware',
]
ROOT_URLCONF = 'sandboxMP.urls'
@@ -79,14 +87,24 @@ WSGI_APPLICATION = 'sandboxMP.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# }
# }
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'ENGINE': 'django.db.backends.mysql',
'NAME': 'sandboxMP',
'HOST': '127.0.0.1',
'USER': 'ddadmin',
'PASSWORD': '1234@abcd.com',
'PORT': '3306'
}
}
# Password validation
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
@@ -132,3 +150,73 @@ MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
LOGIN_URL = '/login/'
# safe url
SAFE_URL = [r'^/$',
'/login/',
'/logout',
'/index/',
'/media/',
'/admin/',
'/ckeditor/',
'/test/',
'/system/personal_password_change/'
]
# session timeout
SESSION_COOKIE_AGE = 60 * 20
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
SESSION_SAVE_EVERY_REQUEST = True
# logging config
BASE_LOG_DIR = os.path.join(BASE_DIR, 'slogs')
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': '[%(asctime)s][task_id:%(name)s][%(levelname)s]'
'[%(filename)s:%(lineno)d][%(message)s]'
},
'simple': {
'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
},
},
'handlers': {
'default': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(BASE_LOG_DIR, "sandbox_info.log"),
'maxBytes': 1024 * 1024 * 50,
'backupCount': 3,
'formatter': 'simple',
'encoding': 'utf-8',
},
'error': {
'level': 'ERROR',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(BASE_LOG_DIR, "sandbox_err.log"),
'backupCount': 5,
'formatter': 'standard',
'encoding': 'utf-8',
}
},
'loggers': {
'sandbox_info': {
'handlers': ['default'],
'level': 'INFO',
'propagate': True,
},
'sandbox_error': {
'handlers': ['error'],
'level': 'ERROR',
}
}
}

View File

@@ -14,22 +14,28 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.urls import path, include
from django.conf import settings
from django.urls import re_path
from django.views.static import serve
from system.views_user import IndexView, LoginView, LogoutView
from cmdb.tests import TestLoggingView
urlpatterns = [
path('admin/', admin.site.urls),
path('', IndexView.as_view(), name='index'),
path('login/', LoginView.as_view(), name='login'),
path('logout/', LogoutView.as_view(), name='logout'),
path('system/', include('system.urls', namespace='system')),
path('cmdb/', include('cmdb.urls', namespace='cmdb')),
path('test/', TestLoggingView.as_view()),
]
if settings.DEBUG:
urlpatterns += [
re_path(r'^media/(?P<path>.*)$', serve, {"document_root": settings.MEDIA_ROOT}),
]
]

0
slogs/sandbox_err.log Normal file
View File

0
slogs/sandbox_info.log Normal file
View File

View File

@@ -2,7 +2,7 @@ var DATATABLES_CONSTANT = {
// datatables常量
DATA_TABLES : {
DEFAULT_OPTION : { // DataTables初始化选项
SERVER_SIDE_OPTION : { // DataTables初始化选项
oLanguage : {
sProcessing : "处理中...",
sLengthMenu : "每页 _MENU_ 项",//"显示 _MENU_ 项结果,",

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -21,20 +21,32 @@
<!-- /.search form -->
<!-- Sidebar Menu -->
<ul class="sidebar-menu">
<ul class="sidebar-menu">
<li class="header"></li>
<!-- Optionally, you can add icons to the links -->
<li class="treeview">
<a href="#"><i class="fa fa-calendar"></i> <span>一级菜单</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li><a href="#"><i class="fa fa-caret-right"></i>二级菜单</a></li>
<li><a href="#"><i class="fa fa-caret-right"></i>二级菜单</a></li>
</ul>
</li>
{% for menu in request.reveal_menu %}
{% if not menu.url %}
<li class="treeview" id="{{ menu.code }}">
<a href="">
<i class="{{ menu.icon }}"></i><span>{{ menu.name }}</span>
<span class="pull-right-container"><i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
{% for sub in menu.sub_menu %}
<li id="{{ sub.code }}">
<a href="{{ sub.url }}"><i class="fa fa-caret-right"></i>{{ sub.name }}</a>
</li>
{% endfor %}
</ul>
</li>
{% else %}
<li id="{{ menu.code }}">
<a href="{{ menu.url }}"><i class="{{ menu.icon }}"></i><span>{{ menu.name }}</span>
</a>
</li>
{% endif %}
{% endfor %}
</ul>
<!-- /.sidebar-menu -->
</section>
@@ -44,7 +56,14 @@
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<section class="content-header margin-bottom">
<ol class="breadcrumb">
{% if menu.parent %}
<li class="active"><a href="{{ menu.parent.url | default:'' }}">{{ menu.parent.name }}</a></li>
{% endif %}
<li class="active"><a href="{{ menu.url }}">{{ menu.name }}</a></li>
</ol>
</section>
{% block content %}

View File

@@ -8,13 +8,14 @@ scratch. This page gets rid of all links and provides the needed markup only.
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>SandBoxOA</title>
<title>cmdb</title>
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'plugins/font-awesome/css/font-awesome.min.css' %}">
<link rel="stylesheet" href="{% static 'dist/css/AdminLTE.min.css' %}">
<link rel="stylesheet" href="{% static 'dist/css/myself.css' %}">
<link rel="stylesheet" href="{% static 'dist/css/skins/skin-blue.min.css' %}">
<link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
{% block css %} {% endblock %}
@@ -50,9 +51,9 @@ scratch. This page gets rid of all links and provides the needed markup only.
<i class="menu-icon fa fa-birthday-cake bg-red"></i>
<div class="menu-info">
<h4 class="control-sidebar-subheading">SandBox</h4>
<h4 class="control-sidebar-subheading">CMDB</h4>
<p>沙盒协同办公平台</p>
<p>内部资产管理系统</p>
</div>
</a>
</li>
@@ -76,7 +77,7 @@ scratch. This page gets rid of all links and provides the needed markup only.
<!-- Settings tab content -->
<div class="tab-pane" id="control-sidebar-settings-tab">
<form method="post">
<h3 class="control-sidebar-heading">江苏沙盒科技</h3>
<h3 class="control-sidebar-heading"></h3>
<div class="form-group">
<p>
@@ -104,6 +105,22 @@ scratch. This page gets rid of all links and provides the needed markup only.
<script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
<!-- AdminLTE App -->
<script src="{% static 'dist/js/app.min.js' %}"></script>
<script src="{% static 'js/plugins/layer/layer.js' %}"></script>
<script type="text/javascript">
// 修改密码
function doChangepasswd() {
layer.open({
type: 2,
title: '修改密码',
shadeClose: false,
maxmin: true,
area: ['800px', '280px'],
content: ["{% url 'system:personal_password_change' %}" ],
});
}
</script>
{% block javascripts %}{% endblock %}

276
templates/cmdb/cabinet.html Normal file
View File

@@ -0,0 +1,276 @@
{% extends "base-left.html" %}
{% load staticfiles %}
{% block css %}
<link rel="stylesheet" href="{% static 'plugins/datatables/jquery.dataTables.min.css' %}">
<link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
<link rel="stylesheet" href="{% static 'plugins/select2/select2.min.css' %}">
{% endblock %}
{% block content %}
<!-- Main content -->
<section class="content">
<div id="devlist">
<div class="box box-primary" id="liebiao">
<div class="box-header">
<div class="btn-group pull-left">
<button type="button" id="btnRefresh" class="btn btn-default">
<i class="glyphicon glyphicon-repeat"></i>刷新
</button>
</div>
<div class="btn-group pull-left">&nbsp</div>
<div class="btn-group pull-left">
<button type="button" id="btnCreate" class="btn btn-default">
<i class="glyphicon glyphicon-plus"></i>新增
</button>
</div>
<div class="btn-group pull-left">&nbsp</div>
<div class="btn-group pull-left">
<button type="button" id="btnDelete" class="btn btn-default">
<i class="glyphicon glyphicon-trash"></i>删除
</button>
</div>
<div class="pull-right">
<form class="form-inline" id="queryForm">
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>机柜编号</label>
<input type="text" name="number" class="form-control inputText" id="number">
</div>
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>机柜位置</label>
<input type="text" name="position" class="form-control inputText" id="position">
</div>
<button type="button" id="btnSearch" class="btn btn-default">
<i class="glyphicon glyphicon-search"></i>查询
</button>
</form>
</div>
</div>
<div class="box-body">
<table id="dtbList" class="display" cellspacing="0" width="100%">
<thead>
<tr valign="middle">
<th><input type="checkbox" id="checkAll"></th>
<th>ID</th>
<th>机柜编号</th>
<th>机柜位置</th>
<th>备注信息</th>
<th>操作</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<br> <br>
</div>
</div>
</div>
</section>
<!-- /.content -->
{% endblock %}
{% block javascripts %}
<script src="{% static 'plugins/datatables/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'plugins/datatables/dataTables.const-1.js' %}"></script>
<script src="{% static 'js/plugins/layer/layer.js' %}"></script>
<script src="{% static 'plugins/select2/select2.full.min.js' %}"></script>
<script type="text/javascript">
// 菜单选中高亮
$(function () {
$('#CMDB-EAM').addClass('active');
$('#CMDB-EAM-CABINET').addClass('active');
});
// datatables 初始化配置
var oDataTable = null;
$(function () {
oDataTable = initTable();
function initTable() {
var oTable = $('#dtbList').DataTable($.extend(true, {},
DATATABLES_CONSTANT.DATA_TABLES.SERVER_SIDE_OPTION,
{
ajax: {
"url": "{% url 'cmdb:eam-cabinet-list' %}",
"data": function (d) {
d.number = $("#number").val();
d.position = $("#position").val();
}
},
columns: [
DATATABLES_CONSTANT.DATA_TABLES.COLUMN.CHECKBOX,
{
data: "id",
width: "5%",
},
{
data: "number",
//width : "20%",
},
{
data: "position",
//width : "20%",
},
{
data: "desc",
//width : "20%",
},
{
data: "id",
width: "10%",
bSortable: "false",
render: function (data, type, row, meta) {
var ret = "";
var ret = "<button title='详情-修改' onclick='doUpdate("
+ data + ")'><i class='glyphicon glyphicon-pencil'></i></button>";
ret = ret + "<button title='删除' onclick='doDelete("
+ data + ")'><i class='glyphicon glyphicon-trash'></i></button>";
return ret;
}
}],
}));
return oTable;
}
});
// 刷新数据
$("#btnRefresh").click(function () {
oDataTable.ajax.reload();
});
//新建数据
$("#btnCreate").click(function () {
layer.open({
type: 2,
title: '新增',
shadeClose: false,
maxmin: true,
area: ['800px', '400px'],
content: "{% url 'cmdb:eam-cabinet-create' %}",
end: function () {
//关闭时做的事情
oDataTable.ajax.reload();
}
});
});
//修改数据
function doUpdate(id) {
layer.open({
type: 2,
title: '编辑',
shadeClose: false,
maxmin: true,
area: ['800px', '400px'],
content: ["{% url 'cmdb:eam-cabinet-update' %}" + '?id=' + id, 'no'],
end: function () {
oDataTable.ajax.reload();
}
});
}
//checkbox全选
$("#checkAll").on("click", function () {
if ($(this).prop("checked") === true) {
$("input[name='checkList']").prop("checked", $(this).prop("checked"));
$('#example tbody tr').addClass('selected');
} else {
$("input[name='checkList']").prop("checked", false);
$('#example tbody tr').removeClass('selected');
}
});
//批量删除
$("#btnDelete").click(function () {
if ($("input[name='checkList']:checked").length == 0) {
layer.msg("请选择要删除的记录");
return;
}
var arrId = new Array();
$("input[name='checkList']:checked").each(function () {
//alert($(this).val());
arrId.push($(this).val());
});
sId = arrId.join(',');
layer.alert('确定删除吗?', {
title: '提示'
, icon: 3 //0:感叹号 1对号 2差号 3问号 4小锁 5哭脸 6笑脸
, time: 0 //不自动关闭
, btn: ['YES', 'NO']
, yes: function (index) {
layer.close(index);
$.ajax({
type: "POST",
url: "{% url 'cmdb:eam-cabinet-delete' %}",
data: {"id": sId, csrfmiddlewaretoken: '{{ csrf_token }}'},
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert("操作成功", {icon: 1});
oDataTable.ajax.reload();
} else {
//alert(msg.message);
layer.alert("没有权限", {icon: 2});
}
return;
}
});
}
});
});
//删除单个数据
function doDelete(id) {
layer.alert('确定删除吗?', {
title: '提示'
, icon: 3 //0:感叹号 1对号 2差号 3问号 4小锁 5哭脸 6笑脸
, time: 0 //不自动关闭
, btn: ['YES', 'NO']
, yes: function (index) {
layer.close(index);
$.ajax({
type: "POST",
url: "{% url 'cmdb:eam-cabinet-delete' %}",
data: {"id": id, csrfmiddlewaretoken: '{{ csrf_token }}'},
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert('删除成功', {icon: 1});
oDataTable.ajax.reload();
} else {
//alert(msg.message);
layer.alert('没有权限', {icon: 2});
}
return;
}
});
}
});
}
//select2
$(function () {
//Initialize Select2 Elements
$(".select2").select2();
});
$("#btnSearch").click(function(){
oDataTable.ajax.reload();
});
</script>
{% endblock %}

View File

@@ -0,0 +1,99 @@
{% extends 'base-layer.html' %}
{% load staticfiles %}
{% block css %}
<link rel="stylesheet" href="{% static 'plugins/select2/select2.min.css' %}">
<!-- iCheck for checkboxes and radio inputs -->
{% endblock %}
{% block main %}
<div class="box box-danger">
<form class="form-horizontal" id="addForm" method="post">
<input type="hidden" name='id' value="{{ cabinet.id }}" />
{% csrf_token %}
<div class="box-body">
<fieldset>
<legend>
<h4>机柜信息</h4>
</legend>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">机柜编号</label>
<div class="col-sm-3">
<input class="form-control" name="number" type="text" value="{{ cabinet.number }}"/>
</div>
<label class="col-sm-2 control-label">机柜位置</label>
<div class="col-sm-3">
<input class="form-control" name="position" type="text" value="{{ cabinet.position }}"/>
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">描述信息</label>
<div class="col-sm-8">
<input class="form-control" id="desc" name="desc" type="text" value="{{ cabinet.desc }}"/>
</div>
</div>
</fieldset>
</div>
<div class="box-footer ">
<div class="row span7 text-center ">
<button type="button" id="btnCancel" class="btn btn-default margin-right ">重置</button>
<button type="button" id="btnSave" class="btn btn-info margin-right ">保存</button>
</div>
</div>
</form>
</div>
{% endblock %}
{% block javascripts %}
<script src="{% static 'plugins/select2/select2.full.min.js' %}"></script>
<script type="text/javascript">
function getUrl() {
if ($("input[name='id']").val()) {
var url = "{% url 'cmdb:eam-cabinet-update' %}";
} else {
var url = "{% url 'cmdb:eam-cabinet-create' %}";
}
return url
}
$("#btnSave").click(function () {
var data = $("#addForm").serialize();
$.ajax({
type: $("#addForm").attr('method'),
url: getUrl(),
data: data,
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert('数据保存成功!', {icon: 1}, function (index) {
parent.layer.closeAll(); //关闭所有弹窗
});
} else {
layer.alert(msg.error, {icon: 5});
//$('errorMessage').html(msg.message)
}
return;
}
});
});
/*点取消刷新新页面*/
$("#btnCancel").click(function () {
window.location.reload();
});
$(function () {
//Initialize Select2 Elements
$(".select2").select2();
});
</script>
{% endblock %}

View File

@@ -0,0 +1,221 @@
{% extends "base-left.html" %}
{% load staticfiles %}
{% block css %}{% endblock %}
{% block content %}
<!-- Main content -->
<section class="content">
<!-- Small boxes (Stat box) -->
<div class="row">
<div class="col-lg-3 col-xs-6">
<!-- small box -->
<div class="small-box bg-aqua">
<div class="inner">
<strong><h4>WebSocket</h4></strong>
<p>WSS在线测试工具</p>
</div>
<div class="icon">
<i class="fa fa-unlink "></i>
</div>
<a href="http://tool.hibbba.com/websocket/" class="small-box-footer" target="_blank">使用工具 <i class="fa fa-arrow-circle-right"></i></a>
</div>
</div>
<!-- ./col -->
<div class="col-lg-3 col-xs-6">
<!-- small box -->
<div class="small-box bg-green">
<div class="inner">
<strong><h4>网速通</h4></strong>
<p>域名访问测速</p>
</div>
<div class="icon">
<i class="fa fa-globe"></i>
</div>
<a href="http://ceba.quansucloud.com/wstCeba/http/http-test.action" target="_blank" class="small-box-footer">使用工具 <i class="fa fa-arrow-circle-right"></i></a>
</div>
</div>
<!-- ./col -->
<div class="col-lg-3 col-xs-6">
<!-- small box -->
<div class="small-box bg-yellow">
<div class="inner">
<strong><h4>IPIP</h4></strong>
<p>IP查询工具</p>
</div>
<div class="icon">
<i class="fa fa-delicious"></i>
</div>
<a href="https://www.ipip.net/ip.html" target="_blank" class="small-box-footer">使用工具<i class="fa fa-arrow-circle-right"></i></a>
</div>
</div>
<!-- ./col -->
<div class="col-lg-3 col-xs-6">
<!-- small box -->
<div class="small-box bg-orange">
<div class="inner">
<strong><h4>暂未添加</h4></strong>
<p>其他工具</p>
</div>
<div class="icon">
<i class="fa fa-delicious"></i>
</div>
<a href="#" class="small-box-footer">More info <i class="fa fa-arrow-circle-right"></i></a>
</div>
</div>
<!-- ./col -->
</div>
<!-- /.row -->
<div class="row">
<div class="col-md-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">资产统计信息</h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i>
</button>
<button type="button" class="btn btn-box-tool" data-widget="remove"><i class="fa fa-times"></i></button>
</div>
</div>
<!-- /.box-header -->
<div class="box-body">
<div class="row">
<div class="col-md-6">
<div class="chart">
<!-- Sales Chart Canvas -->
<div id="dev_container" style="height: 400px;"></div>
</div>
<!-- /.chart-responsive -->
</div>
<!-- /.col -->
<div class="col-md-6">
<div id="ope_container" style="height: 400px;"></div>
</div>
<!-- /.col -->
</div>
<!-- /.row -->
</div>
<!-- ./box-body -->
</div>
<!-- /.box -->
</div>
<!-- /.col -->
</div>
</section>
<!-- /.content -->
{% endblock %}
{% block javascripts %}
<script type="text/javascript" src="{% static 'plugins/echarts/echarts.js' %}"></script>
<script type="text/javascript">
var dev_dom = document.getElementById("dev_container");
var dev_Chart = echarts.init(dev_dom, 'macarons');
dev_option = null;
dev_option = {
title : {
text: '主机分布',
subtext: '数据来自设备管理'
},
tooltip : {
trigger: 'axis'
},
toolbox: {
show : true,
feature : {
dataView : {show: true, readOnly: false},
restore : {show: true},
saveAsImage : {show: true}
}
},
calculable : true,
xAxis : [
{
type : 'value',
boundaryGap : [0, 0.01]
}
],
yAxis : [
{
type : 'category',
data : [{% for cab in cabinet_list %} '{{ cab }}', {% endfor %}]
}
],
series : [
{
name:'云主机',
type:'bar',
data:{{ cabinet_count }}
}
]
};
;
if (dev_option && typeof dev_option === "object") {
dev_Chart.setOption(dev_option, true);
}
var ope_dom = document.getElementById("ope_container");
var ope_Chart = echarts.init(ope_dom, 'macarons');
ope_option = null;
ope_option = {
title : {
text: '项目分布',
subtext: '数据来自设备管理',
x:'center'
},
tooltip : {
trigger: 'item',
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
legend: {
orient : 'vertical',
x : 'left',
data: [{% for ope in operations %} '{{ ope.name }}', {% endfor %}]
},
toolbox: {
show : true,
feature : {
dataView : {show: true, readOnly: false},
restore : {show: true},
saveAsImage : {show: true}
}
},
calculable : true,
series : [
{
name:'主机数量',
type:'pie',
radius : '55%',
center: ['50%', '60%'],
data:[
{% for ope in operations %}
{value:{{ ope.count }}, name:'{{ ope.name }}'},
{% endfor %}
]
}
]
};
;
if (ope_option && typeof ope_option === "object") {
ope_Chart.setOption(ope_option, true);
}
//图表窗体自适应
$(window).resize(function(){
ope_Chart.resize();
dev_Chart.resize();
});
//跳转到网络资产
function doNetworkAsset(){
window.location.href="{% url 'cmdb:eam-network_asset' %}";
}
</script>
{% endblock %}

274
templates/cmdb/code.html Normal file
View File

@@ -0,0 +1,274 @@
{% extends "base-left.html" %}
{% load staticfiles %}
{% block css %}
<link rel="stylesheet" href="{% static 'plugins/datatables/jquery.dataTables.min.css' %}">
<link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
<link rel="stylesheet" href="{% static 'plugins/select2/select2.min.css' %}">
{% endblock %}
{% block content %}
<!-- Main content -->
<section class="content">
<div id="devlist">
<div class="box box-primary" id="liebiao">
<div class="box-header">
<div class="btn-group pull-left">
<button type="button" id="btnRefresh" class="btn btn-default">
<i class="glyphicon glyphicon-repeat"></i>刷新
</button>
</div>
<div class="btn-group pull-left">&nbsp</div>
<div class="btn-group pull-left">
<button type="button" id="btnCreate" class="btn btn-default">
<i class="glyphicon glyphicon-plus"></i>新增
</button>
</div>
<div class="btn-group pull-left">&nbsp</div>
<div class="btn-group pull-left">
<button type="button" id="btnDelete" class="btn btn-default">
<i class="glyphicon glyphicon-trash"></i>删除
</button>
</div>
<div class="pull-right">
<form class="form-inline" id="queryForm">
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>字典分类</label>
<select class="form-control inputText select2" name="parent" id="parent">
<option style='text-align:center' value="">---所有---</option>
{% for code in code_parent %}
<option value={{ code.key}}>{{ code.value }}</option>
{% endfor %}
</select>
</div>
</form>
</div>
</div>
<div class="box-body">
<table id="dtbList" class="display" cellspacing="0" width="100%">
<thead>
<tr valign="middle">
<th><input type="checkbox" id="checkAll"></th>
<th>ID</th>
<th>KEY</th>
<th>VALUE</th>
<th>所属</th>
<th>操作</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<br> <br>
</div>
</div>
</div>
</section>
<!-- /.content -->
{% endblock %}
{% block javascripts %}
<script src="{% static 'plugins/datatables/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'plugins/datatables/dataTables.const-1.js' %}"></script>
<script src="{% static 'js/plugins/layer/layer.js' %}"></script>
<script src="{% static 'plugins/select2/select2.full.min.js' %}"></script>
<script type="text/javascript">
// 菜单选中高亮
$(function () {
$('#CMDB-PORTAL').addClass('active');
$('#CDMB-PORTAL-CODE').addClass('active');
});
// datatables 初始化配置
var oDataTable = null;
$(function () {
oDataTable = initTable();
function initTable() {
var oTable = $('#dtbList').DataTable($.extend(true, {},
DATATABLES_CONSTANT.DATA_TABLES.SERVER_SIDE_OPTION,
{
ajax: {
"url": "{% url 'cmdb:portal-code-list' %}",
"data": function (d) {
d.parent = $("#parent").val();
}
},
columns: [
DATATABLES_CONSTANT.DATA_TABLES.COLUMN.CHECKBOX,
{
data: "id",
width: "5%",
},
{
data: "key",
//width : "20%",
},
{
data: "value",
//width : "20%",
},
{
data: "parent__value",
//width : "20%",
},
{
data: "id",
width: "10%",
bSortable: "false",
render: function (data, type, row, meta) {
var ret = "";
var ret = "<button title='详情' onclick='doUpdate("
+ data + ")'><i class='glyphicon glyphicon-pencil'></i></button>";
ret = ret + "<button title='删除' onclick='doDelete("
+ data + ")'><i class='glyphicon glyphicon-trash'></i></button>";
return ret;
}
}],
}));
return oTable;
}
});
//select2
$(function () {
//Initialize Select2 Elements
$(".select2").select2();
});
//过滤刷新接口获取新的数据
$("#parent").change(function () {
oDataTable.ajax.reload();
});
// 刷新数据
$("#btnRefresh").click(function () {
oDataTable.ajax.reload();
});
//新建字典
$("#btnCreate").click(function () {
layer.open({
type: 2,
title: '新增',
shadeClose: false,
maxmin: true,
area: ['800px', '400px'],
content: "{% url 'cmdb:portal-code-create' %}",
end: function () {
//关闭时做的事情
oDataTable.ajax.reload();
}
});
});
//修改字典
function doUpdate(id) {
layer.open({
type: 2,
title: '编辑',
shadeClose: false,
maxmin: true,
area: ['800px', '400px'],
content: ["{% url 'cmdb:portal-code-update' %}" + '?id=' + id, 'no'],
end: function () {
oDataTable.ajax.reload();
}
});
}
//checkbox全选
$("#checkAll").on("click", function () {
if ($(this).prop("checked") === true) {
$("input[name='checkList']").prop("checked", $(this).prop("checked"));
$('#example tbody tr').addClass('selected');
} else {
$("input[name='checkList']").prop("checked", false);
$('#example tbody tr').removeClass('selected');
}
});
//批量删除
$("#btnDelete").click(function () {
if ($("input[name='checkList']:checked").length == 0) {
layer.msg("请选择要删除的记录");
return;
}
var arrId = new Array();
$("input[name='checkList']:checked").each(function () {
//alert($(this).val());
arrId.push($(this).val());
});
sId = arrId.join(',');
layer.alert('确定删除吗?', {
title: '提示'
, icon: 3 //0:感叹号 1对号 2差号 3问号 4小锁 5哭脸 6笑脸
, time: 0 //不自动关闭
, btn: ['YES', 'NO']
, yes: function (index) {
layer.close(index);
$.ajax({
type: "POST",
url: "{% url 'cmdb:portal-code-delete' %}",
data: {"id": sId, csrfmiddlewaretoken: '{{ csrf_token }}'},
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert("操作成功", {icon: 1});
oDataTable.ajax.reload();
} else {
//alert(msg.message);
layer.alert("没有权限", {icon: 2});
}
return;
}
});
}
});
});
//删除单个数据
function doDelete(id) {
layer.alert('确定删除吗?', {
title: '提示'
, icon: 3 //0:感叹号 1对号 2差号 3问号 4小锁 5哭脸 6笑脸
, time: 0 //不自动关闭
, btn: ['YES', 'NO']
, yes: function (index) {
layer.close(index);
$.ajax({
type: "POST",
url: "{% url 'cmdb:portal-code-delete' %}",
data: {"id": id, csrfmiddlewaretoken: '{{ csrf_token }}'},
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert('删除成功', {icon: 1});
oDataTable.ajax.reload();
} else {
//alert(msg.message);
layer.alert('没有权限', {icon: 2});
}
return;
}
});
}
});
}
</script>
{% endblock %}

View File

@@ -0,0 +1,97 @@
{% extends 'base-layer.html' %}
{% load staticfiles %}
{% block css %}
<link rel="stylesheet" href="{% static 'plugins/select2/select2.min.css' %}">
<!-- iCheck for checkboxes and radio inputs -->
{% endblock %}
{% block main %}
<div class="box box-danger">
<form class="form-horizontal" id="addForm" method="post">
{% csrf_token %}
<div class="box-body">
<fieldset>
<legend>
<h4>新建字典</h4>
</legend>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">KEY</label>
<div class="col-sm-3">
<input class="form-control" name="key" type="text"/>
</div>
<label class="col-sm-2 control-label">VALUE</label>
<div class="col-sm-3">
<input class="form-control" name="value" type="text" />
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">父菜单</label>
<div class="col-sm-3">
<select class="form-control select2" name="parent">
<option value=""></option>
{% for parent in code_parent %}
<option value={{ parent.id }}> {{ parent.value }} </option>
{% endfor %}
</select>
</div>
<label class="col-sm-2 control-label">描述信息</label>
<div class="col-sm-3">
<input class="form-control" id="desc" name="desc" type="text" />
</div>
</div>
</fieldset>
</div>
<div class="box-footer ">
<div class="row span7 text-center ">
<button type="button" id="btnCancel" class="btn btn-default margin-right ">重置</button>
<button type="button" id="btnSave" class="btn btn-info margin-right ">保存</button>
</div>
</div>
</form>
</div>
{% endblock %}
{% block javascripts %}
<script src="{% static 'plugins/select2/select2.full.min.js' %}"></script>
<script type="text/javascript">
$("#btnSave").click(function () {
var data = $("#addForm").serialize();
$.ajax({
type: $("#addForm").attr('method'),
url: "{% url 'cmdb:portal-code-create' %}",
data: data,
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert('数据保存成功!', {icon: 1}, function (index) {
parent.layer.closeAll(); //关闭所有弹窗
});
} else {
layer.alert(msg.error, {icon: 5});
//$('errorMessage').html(msg.message)
}
return;
}
});
});
/*点取消刷新新页面*/
$("#btnCancel").click(function () {
window.location.reload();
});
$(function () {
//Initialize Select2 Elements
$(".select2").select2();
});
</script>
{% endblock %}

View File

@@ -0,0 +1,99 @@
{% extends 'base-layer.html' %}
{% load staticfiles %}
{% block css %}
<link rel="stylesheet" href="{% static 'plugins/select2/select2.min.css' %}">
<!-- iCheck for checkboxes and radio inputs -->
{% endblock %}
{% block main %}
<div class="box box-danger">
<form class="form-horizontal" id="addForm" method="post">
<input type="hidden" name='id' type='text' value="{{ code.id }}"/>
{% csrf_token %}
<div class="box-body">
<fieldset>
<legend>
<h4>修改字典</h4>
</legend>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">KEY</label>
<div class="col-sm-3">
<input class="form-control" name="key" type="text" value="{{ code.key }}"/>
</div>
<label class="col-sm-2 control-label">VALUE</label>
<div class="col-sm-3">
<input class="form-control" name="value" type="text" value="{{ code.value }}"/>
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">父菜单</label>
<div class="col-sm-3">
<select class="form-control select2" name="parent">
<option value={{ code.parent.id }}> {{ code.parent.value }} </option>
<option value=""></option>
{% for parent in code_parent %}
<option value={{ parent.id }}> {{ parent.value }} </option>
{% endfor %}
</select>
</div>
<label class="col-sm-2 control-label">描述信息</label>
<div class="col-sm-3">
<input class="form-control" id="desc" name="desc" type="text" value="{{ code.desc }}"/>
</div>
</div>
</fieldset>
</div>
<div class="box-footer ">
<div class="row span7 text-center ">
<button type="button" id="btnCancel" class="btn btn-default margin-right ">重置</button>
<button type="button" id="btnSave" class="btn btn-info margin-right ">保存</button>
</div>
</div>
</form>
</div>
{% endblock %}
{% block javascripts %}
<script src="{% static 'plugins/select2/select2.full.min.js' %}"></script>
<script type="text/javascript">
$("#btnSave").click(function () {
var data = $("#addForm").serialize();
$.ajax({
type: $("#addForm").attr('method'),
url: "{% url 'cmdb:portal-code-update' %}",
data: data,
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert('数据保存成功!', {icon: 1}, function (index) {
parent.layer.closeAll(); //关闭所有弹窗
});
} else {
layer.alert(msg.error, {icon: 5});
//$('errorMessage').html(msg.message)
}
return;
}
});
});
/*点取消刷新新页面*/
$("#btnCancel").click(function () {
window.location.reload();
});
$(function () {
//Initialize Select2 Elements
$(".select2").select2();
});
</script>
{% endblock %}

View File

@@ -0,0 +1,329 @@
{% extends "base-left.html" %}
{% load staticfiles %}
{% block css %}
<link rel="stylesheet" href="{% static 'plugins/datatables/jquery.dataTables.min.css' %}">
<link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
<link rel="stylesheet" href="{% static 'plugins/select2/select2.min.css' %}">
{% endblock %}
{% block content %}
<!-- Main content -->
<section class="content">
<div id="devlist">
<div class="box box-primary" id="liebiao">
<div class="box-header">
<div class="btn-group pull-left">
<button type="button" id="btnRefresh" class="btn btn-default">
<i class="glyphicon glyphicon-repeat"></i> 刷新
</button>
</div>
<div class="btn-group pull-left">&nbsp</div>
<div class="btn-group pull-left">
<button type="button" id="btnScan" class="btn btn-default" onclick="doScan()">
<i class="glyphicon glyphicon-search"></i> 执行扫描
</button>
</div>
<div class="btn-group pull-left">&nbsp</div>
<div class="btn-group pull-left">
<button type="button" id="btnInbound" class="btn btn-default" onclick="doInbound()">
<i class="glyphicon glyphicon-floppy-disk"></i> 执行入库
</button>
</div>
<div class="btn-group pull-left">&nbsp</div>
<div class="btn-group pull-left">
<button type="button" id="btnDelete" class="btn btn-default">
<i class="glyphicon glyphicon-trash"></i> 删除
</button>
</div>
</div>
<div class="box-body">
<table id="dtbList" class="display" cellspacing="0" width="100%">
<thead>
<tr valign="middle">
<th><input type="checkbox" id="checkAll"></th>
<th>ID</th>
<th>主机名</th>
<th>IP地址</th>
<th>MAC地址</th>
<th>认证类型</th>
<th>登陆状态</th>
<th>系统类型</th>
<th>设备类型</th>
<th>操作</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<br>
<small>点击执行入库可将扫描结果中登陆状态为成功succeed的设备数据导入正式设备管理数据库</small>
</div>
</div>
</div>
</section>
<!-- /.content -->
{% endblock %}
{% block javascripts %}
<script src="{% static 'plugins/datatables/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'plugins/datatables/dataTables.const-1.js' %}"></script>
<script src="{% static 'js/plugins/layer/layer.js' %}"></script>
<script src="{% static 'plugins/select2/select2.full.min.js' %}"></script>
<script type="text/javascript">
// 菜单选中高亮
$(function () {
$('#CMDB-PORTAL').addClass('active');
$('#CDMB-PORTAL-DEVICE_SCAN').addClass('active');
});
// datatables 初始化配置
var oDataTable = null;
$(function () {
oDataTable = initTable();
function initTable() {
var oTable = $('#dtbList').DataTable($.extend(true, {},
DATATABLES_CONSTANT.DATA_TABLES.SERVER_SIDE_OPTION,
{
ajax: {
"url": "{% url 'cmdb:portal-device_scan-list' %}",
},
columns: [
DATATABLES_CONSTANT.DATA_TABLES.COLUMN.CHECKBOX,
{
data: "id",
width: "5%",
},
{
data: "sys_hostname",
//width : "20%",
},
{
data: "hostname",
//width : "20%",
},
{
data: "mac_address",
//width : "20%",
},
{
data: "auth_type",
//width : "20%",
},
{
data: "status",
render: function (data, type, row, meta) {
if (data == "succeed") {
var ret = "<button class='btn btn-info btn-xs'>成功</button>";
return ret;
}
if (data == "failed") {
var ret = "<button class='btn btn-danger btn-xs'>失败</button>";
return ret;
}
else {
var ret = "<button class='btn btn-default btn-xs'>未知</button>";
return ret;
}
}
},
{
data: "os_type",
//width : "20%",
},
{
data: "device_type",
//width : "20%",
},
{
data: "id",
width: "10%",
bSortable: "false",
render: function (data, type, row, meta) {
var ret = "";
var ret = "<button title='详情' onclick='doDetail("
+ data + ")'><i class='glyphicon glyphicon-list-alt'></i></button>";
ret = ret + "<button title='删除' onclick='doDelete("
+ data + ")'><i class='glyphicon glyphicon-trash'></i></button>";
return ret;
}
}],
}));
return oTable;
}
});
// 刷新数据
$("#btnRefresh").click(function () {
oDataTable.ajax.reload();
});
function doDetail(id){
window.location.href="/cmdb/portal/device_scan/detail/?id="+id;
}
//checkbox全选
$("#checkAll").on("click", function () {
if ($(this).prop("checked") === true) {
$("input[name='checkList']").prop("checked", $(this).prop("checked"));
$('#example tbody tr').addClass('selected');
} else {
$("input[name='checkList']").prop("checked", false);
$('#example tbody tr').removeClass('selected');
}
});
//批量删除
$("#btnDelete").click(function () {
if ($("input[name='checkList']:checked").length == 0) {
layer.msg("请选择要删除的记录");
return;
}
var arrId = new Array();
$("input[name='checkList']:checked").each(function () {
//alert($(this).val());
arrId.push($(this).val());
});
sId = arrId.join(',');
layer.alert('确定删除吗?', {
title: '提示'
, icon: 3 //0:感叹号 1对号 2差号 3问号 4小锁 5哭脸 6笑脸
, time: 0 //不自动关闭
, btn: ['YES', 'NO']
, yes: function (index) {
layer.close(index);
$.ajax({
type: "POST",
url: "{% url 'cmdb:portal-device_scan-delete' %}",
data: {"id": sId, csrfmiddlewaretoken: '{{ csrf_token }}'},
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert("操作成功", {icon: 1});
oDataTable.ajax.reload();
} else {
//alert(msg.message);
layer.alert("操作失败", {icon: 2});
}
return;
}
});
}
});
});
//删除单个数据
function doDelete(id) {
layer.alert('确定删除吗?', {
title: '提示'
, icon: 3 //0:感叹号 1对号 2差号 3问号 4小锁 5哭脸 6笑脸
, time: 0 //不自动关闭
, btn: ['YES', 'NO']
, yes: function (index) {
layer.close(index);
$.ajax({
type: "POST",
url: "{% url 'cmdb:portal-device_scan-delete' %}",
data: {"id": id, csrfmiddlewaretoken: '{{ csrf_token }}'},
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert('删除成功', {icon: 1});
oDataTable.ajax.reload();
} else {
//alert(msg.message);
layer.alert('删除失败', {icon: 2});
}
return;
}
});
}
});
}
//资产扫描
function doScan() {
layer.alert('确定开始扫描吗?', {
title: '提示'
, icon: 3 //0:感叹号 1对号 2差号 3问号 4小锁 5哭脸 6笑脸
, time: 0 //不自动关闭
, btn: ['YES', 'NO']
, yes: function (index) {
layer.close(index);
$.ajax({
type: "GET",
url: "{% url 'cmdb:portal-device_scan-exec' %}",
cache: false,
beforeSend:function(){
this.layerIndex = layer.load(2, {
shade: [0.1,'#fff']
});
},
success: function (msg) {
layer.closeAll('loading');
if (msg.status == 'success') {
layer.alert('扫描任务已下发', {icon: 1});
oDataTable.ajax.reload();
}
else if (msg.status == 'already_queued') {
layer.alert('当前已有扫描任务正在执行', {icon: 4});
oDataTable.ajax.reload();
}
else {
//alert(msg.message);
layer.alert('扫描失败', {icon: 2});
}
return;
}
});
}
});
}
function doInbound() {
layer.alert('确定将扫描结果导入设备管理库吗?', {
title: '提示'
, icon: 3
, time: 0
, btn: ['YES', 'NO']
, yes: function (index) {
layer.close(index);
$.ajax({
type: "POST",
url: "{% url 'cmdb:portal-device_scan-inbound' %}",
data: {csrfmiddlewaretoken: '{{ csrf_token }}'},
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert('设备已入库', {icon: 1});
}
else {
//alert(msg.message);
layer.alert('设备入库失败', {icon: 2});
}
return;
}
});
}
});
}
</script>
{% endblock %}

View File

@@ -0,0 +1,113 @@
{% extends "base-left.html" %}
{% load staticfiles %}
{% block css %}
<link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
{% endblock %}
{% block content %}
<!-- Main content -->
<section class="content">
<div class="row">
<div class="col-md-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">设备详情</h3>
<div class="box-tools">
<button type="button" class="btn btn-box-tool" data-widget="collapse"><i
class="fa fa-minus"></i>
</button>
</div>
</div>
<div class="box-body no-padding">
<div class="btn-group pull-right margin">
<button type="button" class="btn btn-primary btn-xs margin-r-5" title="返回" id="btnReturn">
<i class="fa fa-undo"> 返回</i>
</button>
</div>
</div>
<div class="table-responsive mailbox-messages">
<table class="table" id="tbWorkList" style="white-space: nowrap;">
<tbody>
<tr class="info">
<td width="10%"><strong>主机名</strong></td>
<td class="text-left">{{ device.sys_hostname }}</td>
<td width="10%"><strong>SN编号</strong></td>
<td class="text-left">{{ device.sn_number }}</td>
</tr>
<tr>
<td><strong>SSH用户名</strong></td>
<td>{{ device.username }}</td>
<td><strong>SSH端口</strong></td>
<td>{{ device.port }}</td>
</tr>
<tr class="info">
<td><strong>认证类型</strong></td>
<td>{{ device.auth_type }}</td>
<td><strong>登陆状态</strong></td>
<td>{{ device.status }}</td>
</tr>
<tr>
<td><strong>IP地址</strong></td>
<td>{{ device.hostname }}</td>
<td><strong>MAC地址</strong></td>
<td>{{ device.mac_address }}</td>
</tr>
<tr class="info">
<td><strong>系统类型</strong></td>
<td>{{ device.os_type }}</td>
<td><strong>设备类型</strong></td>
<td>{{ device.device_type }}</td>
</tr>
<tr>
<td><strong>入库时间</strong></td>
<td>{{ device.add_time }}</td>
<td><strong>变更时间</strong></td>
<td>{{ device.modify_time }}</td>
</tr>
<tr class="info">
<td><strong>错误信息</strong></td>
<td colspan="3">{{ device.error_message }}</td>
</tr>
</tbody>
</table>
</div>
<br>
<div class="box-footer margin-b-10">
<small>该设备信息为自动扫描入库设备不提供修改功能可通过管理页面执行入库按钮将登陆状态为成功succeed的设备迁移到正式设备管理库</small>
</div>
<!-- /.box-footer -->
</div>
<!-- /.box-body -->
</div>
</div>
<!-- /.col -->
<!-- TO DO List -->
</section>
<!-- /.content -->
{% endblock %}
{% block javascripts %}
<script src="{% static 'js/plugins/layer/layer.js' %}"></script>
<script src="{% static 'plugins/masonry/masonry.js' %}"></script>
<script type="text/javascript">
$(function () {
$('#CMDB-PORTAL').addClass('active');
$('#CDMB-PORTAL-DEVICE_SCAN').addClass('active');
});
//返回
$("#btnReturn").click(function () {
history.back();
});
</script>
{% endblock %}

View File

@@ -0,0 +1,359 @@
{% extends "base-left.html" %}
{% load staticfiles %}
{% block css %}
<link rel="stylesheet" href="{% static 'plugins/datatables/jquery.dataTables.min.css' %}">
<link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
<link rel="stylesheet" href="{% static 'plugins/select2/select2.min.css' %}">
{% endblock %}
{% block content %}
<!-- Main content -->
<section class="content">
<div id="devlist">
<div class="box box-primary" id="liebiao">
<div class="box-header">
<div class="btn-group pull-left">
<button type="button" id="btnRefresh" class="btn btn-default">
<i class="glyphicon glyphicon-repeat"></i>刷新
</button>
</div>
<div class="btn-group pull-left">&nbsp</div>
<div class="btn-group pull-left">
<button type="button" id="btnCreate" class="btn btn-default">
<i class="glyphicon glyphicon-plus"></i>新增
</button>
</div>
<div class="btn-group pull-left">&nbsp</div>
<div class="btn-group pull-left">
<button type="button" id="btnDelete" class="btn btn-default">
<i class="glyphicon glyphicon-trash"></i>删除
</button>
</div>
</div>
<div class="box-header">
<form class="form-inline" id="queryForm">
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>主机名</label>
<input type="text" name="sys_hostname" class="form-control inputText" id="sys_hostname">
</div>
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>设备地址</label>
<input type="text" name="hostname" class="form-control inputText" id="hostname">
</div>
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>网络类型</label>
<select class="form-control inputText select2" name="network_type" id="network_type">
<option></option>
{% for code in all_code %}
{% ifequal code.parent.key 'NETWORK_TYPE' %}
<option value="{{ code.id }}">{{ code.value }}</option>
{% endifequal %}
{% endfor %}
</select>
</div>
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>服务类型</label>
<select class="form-control inputText select2" name="service_type" , id="service_type">
<option></option>
{% for code in all_code %}
{% if code.parent.key == 'SERVICE_TYPE' %}
<option value="{{ code.id }}">{{ code.value }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>所属项目</label>
<select class="form-control inputText select2" name="operation_type" , id="operation_type">
<option></option>
{% for code in all_code %}
{% if code.parent.key == 'OPERATION_TYPE' %}
<option value="{{ code.id }}">{{ code.value }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>机柜信息</label>
<select class="form-control inputText select2" name="dev_cabinet" , id="dev_cabinet">
<option></option>
{% for cabinet in all_cabinet %}
<option value="{{ cabinet.id }}">{{ cabinet.number }}</option>
{% endfor %}
</select>
</div>
<button type="button" id="btnSearch" class="btn btn-default">
<i class="glyphicon glyphicon-search"></i>查询
</button>
</form>
</div>
<div class="box-body">
<table id="dtbList" class="display" cellspacing="0" width="100%">
<thead>
<tr valign="middle">
<th><input type="checkbox" id="checkAll"></th>
<th>ID</th>
<th>主机名</th>
<th>IP地址</th>
<th>服务类型</th>
<th>所属项目</th>
<th>配置信息</th>
<th>机柜信息</th>
<th>网络类型</th>
<th>操作</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<br> <br>
</div>
</div>
</div>
</section>
<!-- /.content -->
{% endblock %}
{% block javascripts %}
<script src="{% static 'plugins/datatables/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'plugins/datatables/dataTables.const-1.js' %}"></script>
<script src="{% static 'js/plugins/layer/layer.js' %}"></script>
<script src="{% static 'plugins/select2/select2.full.min.js' %}"></script>
<script type="text/javascript">
// 菜单选中高亮
$(function () {
$('#CMDB-EAM').addClass('active');
$('#CMDB-EAM-DEVICE').addClass('active');
});
// datatables 初始化配置
var oDataTable = null;
$(function () {
oDataTable = initTable();
function initTable() {
var oTable = $('#dtbList').DataTable($.extend(true, {},
DATATABLES_CONSTANT.DATA_TABLES.SERVER_SIDE_OPTION,
{
ajax: {
"url": "{% url 'cmdb:eam-device-list' %}",
"data": function (d) {
d.sys_hostname = $("#sys_hostname").val();
d.hostname = $("#hostname").val();
d.network_type = $("#network_type").val();
d.service_type = $("#service_type").val();
d.operation_type = $("#operation_type").val();
d.dev_cabinet = $("#dev_cabinet").val();
}
},
columns: [
DATATABLES_CONSTANT.DATA_TABLES.COLUMN.CHECKBOX,
{
data: "id",
},
{
data: "sys_hostname",
},
{
data: "hostname",
},
{
data: "service_type",
},
{
data: "operation_type",
},
{
data: "config",
},
{
data: "dev_cabinet",
},
{
data: "network_type",
},
{
data: "id",
bSortable: "false",
render: function (data, type, row, meta) {
var ret = "<button title='详情' onclick='doDetail("
+ data + ")'><i class='glyphicon glyphicon-list-alt'></i></button>";
ret = ret + "<button title='修改' onclick='doUpdate("
+ data + ")'><i class='glyphicon glyphicon-pencil'></i></button>";
ret = ret + "<button title='认证管理' onclick='doDevice2Connection("
+ data + ")'><i class='glyphicon glyphicon-user'></i></button>";
ret = ret + "<button title='删除' onclick='doDelete("
+ data + ")'><i class='glyphicon glyphicon-trash'></i></button>";
return ret;
}
}],
}));
return oTable;
}
});
// 刷新数据
$("#btnRefresh").click(function () {
window.location.reload();
});
//新建数据
$("#btnCreate").click(function () {
var div=layer.open({
type: 2,
title: '新增',
shadeClose: false,
maxmin: true,
area: ['800px', '400px'],
content: "{% url 'cmdb:eam-device-create' %}",
end: function () {
//关闭时做的事情
oDataTable.ajax.reload();
}
});
layer.full(div )
});
//修改数据
function doUpdate(id) {
var div=layer.open({
type: 2,
title: '编辑',
shadeClose: false,
maxmin: true,
area: ['800px', '400px'],
content: ["{% url 'cmdb:eam-device-update' %}" + '?id=' + id, 'no'],
end: function () {
oDataTable.ajax.reload();
}
});
layer.full(div )
}
//checkbox全选
$("#checkAll").on("click", function () {
if ($(this).prop("checked") === true) {
$("input[name='checkList']").prop("checked", $(this).prop("checked"));
$('#example tbody tr').addClass('selected');
} else {
$("input[name='checkList']").prop("checked", false);
$('#example tbody tr').removeClass('selected');
}
});
//批量删除
$("#btnDelete").click(function () {
if ($("input[name='checkList']:checked").length == 0) {
layer.msg("请选择要删除的记录");
return;
}
var arrId = new Array();
$("input[name='checkList']:checked").each(function () {
//alert($(this).val());
arrId.push($(this).val());
});
sId = arrId.join(',');
layer.alert('确定删除吗?', {
title: '提示'
, icon: 3 //0:感叹号 1对号 2差号 3问号 4小锁 5哭脸 6笑脸
, time: 0 //不自动关闭
, btn: ['YES', 'NO']
, yes: function (index) {
layer.close(index);
$.ajax({
type: "POST",
url: "{% url 'cmdb:eam-device-delete' %}",
data: {"id": sId, csrfmiddlewaretoken: '{{ csrf_token }}'},
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert("操作成功", {icon: 1});
oDataTable.ajax.reload();
} else {
//alert(msg.message);
layer.alert("操作失败", {icon: 2});
}
return;
}
});
}
});
});
//删除单个数据
function doDelete(id) {
layer.alert('确定删除吗?', {
title: '提示'
, icon: 3 //0:感叹号 1对号 2差号 3问号 4小锁 5哭脸 6笑脸
, time: 0 //不自动关闭
, btn: ['YES', 'NO']
, yes: function (index) {
layer.close(index);
$.ajax({
type: "POST",
url: "{% url 'cmdb:eam-device-delete' %}",
data: {"id": id, csrfmiddlewaretoken: '{{ csrf_token }}'},
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert('删除成功', {icon: 1});
oDataTable.ajax.reload();
} else {
//alert(msg.message);
layer.alert('删除失败', {icon: 2});
}
return;
}
});
}
});
}
//select2
$(function () {
//Initialize Select2 Elements
$(".select2").select2();
});
$("#btnSearch").click(function(){
oDataTable.ajax.reload();
});
function doDevice2Connection(id) {
layer.open({
type: 2,
title: '认证管理',
shadeClose: false,
maxmin: true,
area: ['800px', '400px'],
content: ["{% url 'cmdb:eam-device-device2connection' %}" + '?id=' + id, 'no'],
end: function () {
oDataTable.ajax.reload();
}
});
}
function doDetail(id){
window.location.href="{% url 'cmdb:eam-device-detail' %}?id="+id;
}
</script>
{% endblock %}

View File

@@ -0,0 +1,108 @@
{% extends 'base-layer.html' %}
{% load staticfiles %}
{% block css %}
<link rel="stylesheet" href="{% static 'plugins/select2/select2.min.css' %}">
<!-- iCheck for checkboxes and radio inputs -->
{% endblock %}
{% block main %}
<div class="box box-danger">
<form class="form-horizontal" id="addForm" method="post">
<input type="hidden" name='id' value="{{ connection_info.id }}" />
<input type="hidden" name='hostname' value="{{ device.hostname }}" />
{% csrf_token %}
<div class="box-body">
<fieldset>
<legend>
<h4>关联设备{{ device.sys_hostname }}({{ device.hostname }})</h4>
</legend>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">用户名</label>
<div class="col-sm-3">
<input class="form-control" name="username" type="text" value="{{ connection_info.username }}"/>
</div>
<label class="col-sm-2 control-label">认证类型</label>
<div class="col-sm-3">
<select class="form-control select2" style="width:100%;" name="auth_type">
<option value="password" {% ifequal connection_info.auth_type 'password' %}selected="selected"{% endifequal %}>密码</option>
<option value="private_key" {% ifequal connection_info.auth_type 'private_key' %}selected="selected"{% endifequal %}>密钥</option>
</select>
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">密码</label>
<div class="col-sm-3">
<input class="form-control" name="password" type="password" value="{{ connection_info.password }}"/>
</div>
<label class="col-sm-2 control-label">密钥</label>
<div class="col-sm-3">
<input class="form-control" name="private_key" type="text" value="{{ connection_info.private_key }}"/>
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">端口</label>
<div class="col-sm-3">
<input class="form-control" name="port" type="text" value="{{ connection_info.port }}"/>
</div>
<label class="col-sm-2 control-label">状态</label>
<div class="col-sm-3">
<input class="form-control" name="status" type="text" value="{{ connection_info.status }}" readonly/>
</div>
</div>
</fieldset>
</div>
<div class="box-footer ">
<div class="row span7 text-center ">
<button type="button" id="btnCancel" class="btn btn-default margin-right ">重置</button>
<button type="button" id="btnSave" class="btn btn-info margin-right ">保存</button>
</div>
</div>
</form>
</div>
{% endblock %}
{% block javascripts %}
<script src="{% static 'plugins/select2/select2.full.min.js' %}"></script>
<script type="text/javascript">
$("#btnSave").click(function () {
var data = $("#addForm").serialize();
$.ajax({
type: $("#addForm").attr('method'),
url: "{% url 'cmdb:eam-device-device2connection' %}",
data: data,
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert('数据保存成功!', {icon: 1}, function (index) {
parent.layer.closeAll(); //关闭所有弹窗
});
} else {
layer.alert(msg.error, {icon: 5});
//$('errorMessage').html(msg.message)
}
return;
}
});
});
/*点取消刷新新页面*/
$("#btnCancel").click(function () {
window.location.reload();
});
$(function () {
//Initialize Select2 Elements
$(".select2").select2();
});
</script>
{% endblock %}

View File

@@ -0,0 +1,316 @@
{% extends "base-left.html" %}
{% load staticfiles %}
{% load extra_tags %}
{% block css %}
<link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
{% endblock %}
{% block content %}
<section class="content">
<div class="nav-tabs-custom">
<ul class="nav nav-tabs">
<li class="active"><a href="#activity" data-toggle="tab">设备详情</a></li>
<li><a href="#history" data-toggle="tab">历史纪录</a></li>
</ul>
<div class="tab-content">
<div class="active tab-pane" id="activity">
<div class="box-body no-padding">
<div class="btn-group pull-right margin">
<button type="button" class="btn btn-primary btn-sm margin-r-5"
onclick="doUpload({{ device.id }})">
<i class="fa fa-cloud-upload"> 上传资料</i>
</button>
<button type="button" class="btn btn-primary btn-sm margin-r-5" title="认证管理" onclick="doDevice2Connection({{ device.id }})">
<i class="fa fa-user"> 认证管理</i>
</button>
<button type="button" class="btn btn-primary btn-sm margin-r-5" title="自动更新" onclick="doAutoUpdate({{ device.id }})">
<i class="fa fa-circle-o-notch"> 自动更新</i>
</button>
<button type="button" class="btn btn-primary btn-sm margin-r-5" title="编辑" onclick="doUpdate({{ device.id }})">
<i class="fa fa-pencil"> 编辑</i>
</button>
<button type="button" id="btnReturn" class="btn btn-primary btn-sm">
<i class="fa fa-arrow-left"></i> 返回
</button>
</div>
</div>
<div class="table-responsive mailbox-messages">
<table class="table" id="tbWorkList" style="white-space: nowrap;">
<tbody>
<tr class="info">
<td width="10%"><strong>主机名</strong></td>
<td class="text-left">{{ device.sys_hostname }}</td>
<td width="10%"><strong>SN编号</strong></td>
<td class="text-left">{{ device.sn_number }}</td>
</tr>
<tr>
<td><strong>系统类型</strong></td>
<td>{{ device.os_type }}</td>
<td><strong>设备类型</strong></td>
<td>{{ device.device_type }}</td>
</tr>
<tr class="info">
<td><strong>设备地址</strong></td>
<td>{{ device.hostname }}</td>
<td><strong>MAC地址</strong></td>
<td>{{ device.mac_address }}</td>
</tr>
<tr>
<td><strong>网络类型</strong></td>
<td>{% get_con all_code device.network_type 'value' %}</td>
<td><strong>服务类型</strong></td>
<td>{% get_con all_code device.service_type 'value' %}</td>
</tr>
<tr class="info">
<td><strong>业务类型</strong></td>
<td>{% get_con all_code device.operation_type 'value' %}</td>
<td><strong>机柜信息</strong></td>
<td>{% get_con all_cabinet device.dev_cabinet 'number' %}</td>
</tr>
<tr>
<td><strong>购买日期</strong></td>
<td>{{ device.buyDate }}</td>
<td><strong>质保日期</strong></td>
<td>{{ device.warrantyDate }}</td>
</tr>
<tr class="info">
<td><strong>所属</strong></td>
<td>{% if device.parent %}
{{ device.parent.sys_hostname }}({{ device.parent.hostname }})
{% endif %}
</td>
<td><strong>配置信息</strong></td>
<td>{{ device.config }}</td>
</tr>
<tr>
<td><strong>入库时间</strong></td>
<td>{{ device.add_time }}</td>
<td><strong>最后变更</strong></td>
<td>{{ device.modify_time }}</td>
</tr>
<tr class="info">
<td><strong>最后操作人</strong></td>
<td>{{ device.changed_by.name }}</td>
<td><strong></strong></td>
<td></td>
</tr>
<tr>
<td><strong>备注信息</strong></td>
<td colspan="3">{{ device.desc }}</td>
</tr>
</tbody>
</table>
</div>
<br>
<div class="box-footer">
<ul class="mailbox-attachments clearfix" id="imageContainer">
{% for file in all_file %}
<li class="imageItem">
<div class="mailbox-attachment-info">
<a href="/media/{{ file.file_content }}" target="_blank"><i
class="fa fa-file-text"></i>
<small>{{ file.file_content|cut:'asset_file/' }}</small>
</a>
<span class="mailbox-attachment-size">
<b>上传人</b>{{ file.upload_user }}
<a href="/media/{{ file.file_content }}" download="{{ file.file_content }}"
class="btn btn-primary btn-xs pull-right">
<i class="fa fa-cloud-download" title="下载文件"></i>
</a>
<button class="btn btn-adn btn-xs pull-right margin-r-5"
onclick="doDelete({{ file.id }})">
<i class="fa fa-trash" title="删除文件"> </i>
</button>
</span>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
<!-- /.tab-pane -->
<div class="tab-pane" id="history">
<div class="box-body">
<ul class="todo-list">
{% for log in logs %}
<li>
<!-- drag handle -->
<span class="handle">
<small class="text-maroon">
<i class="glyphicon glyphicon-time"></i>
{{ log.history_date }}
&nbsp;&nbsp;
{{ log.changed_by }}
&nbsp;&nbsp;
{{ log.history_type }}
</small>
</span>
<span class="text-sm">
{{ log.changes | compare_result }}
</span>
<!--
<button class="btn btn-xs btn-danger pull-right">还原数据</button>
-->
</li>
{% endfor %}
</ul>
</div>
</div>
<!-- /.tab-pane -->
</div>
<!-- /.tab-content -->
</div>
</section>
<!-- /.content -->
{% endblock %}
{% block javascripts %}
<script src="{% static 'js/plugins/layer/layer.js' %}"></script>
<script src="{% static 'plugins/masonry/masonry.js' %}"></script>
<script type="text/javascript">
$(function () {
$('#CMDB-EAM').addClass('active');
$('#CMDB-EAM-DEVICE').addClass('active');
});
$("#btnReturn").click(function(){
history.back();
});
// 资产文件瀑布流
$('#imageContainer').masonry({
columnWidth: 10,
itemSelector: '.imageItem'
});
//上传资料
function doUpload(id) {
var div = layer.open({
type: 2,
title: '上传设备文件',
shadeClose: false,
maxmin: true,
area: ['770px', '400px'],
content: ["/cmdb/eam/device/upload/" + '?id=' + id],
end: function () {
window.location.reload();
}
});
layer.full(div)
}
//删除文件
function doDelete(id) {
layer.alert('确定删除吗?', {
title: '提示'
, icon: 3
, time: 0
, btn: ['YES', 'NO']
, yes: function (index) {
layer.close(index);
$.ajax({
type: "POST",
url: "{% url 'cmdb:eam-device-file_delete' %}",
data: {"id": id, csrfmiddlewaretoken: '{{ csrf_token }}'},
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert('删除成功', {icon: 1}, function () {
parent.location.reload()
});
} else {
//alert(msg.message);
layer.alert('删除失败', {icon: 2});
}
return;
}
});
}
});
}
//自动更新设备信息
function doAutoUpdate(id) {
layer.alert('确定自动更新设备信息?', {
title: '提示'
, icon: 3 //0:感叹号 1对号 2差号 3问号 4小锁 5哭脸 6笑脸
, time: 0 //不自动关闭
, btn: ['YES', 'NO']
, yes: function (index) {
layer.close(index);
$.ajax({
type: "POST",
data: {"id": id, csrfmiddlewaretoken: '{{ csrf_token }}'},
url: "{% url 'cmdb:eam-device-auto_update_device_info' %}",
cache: false,
beforeSend:function(){
this.layerIndex = layer.load(2, {
shade: [0.1,'#fff']
});
},
success: function (msg) {
layer.closeAll('loading');
if (msg.status == 'success') {
layer.alert('设备信息更新成功!', {icon: 1}, function(){
parent.location.reload()
});
}
else if (msg.status == 'con_empty') {
layer.alert('请先添加认证信息!', {icon: 4});
}
else {
//alert(msg.message);
layer.alert('设备信息更新失败!', {icon: 2});
}
return;
}
});
}
});
}
//修改数据
function doUpdate(id) {
var div=layer.open({
type: 2,
title: '编辑',
shadeClose: false,
maxmin: true,
area: ['800px', '400px'],
content: ["{% url 'cmdb:eam-device-update' %}" + '?id=' + id, 'no'],
end: function () {
window.location.reload();
}
});
layer.full(div )
}
function doDevice2Connection(id) {
layer.open({
type: 2,
title: '认证管理',
shadeClose: false,
maxmin: true,
area: ['800px', '400px'],
content: ["{% url 'cmdb:eam-device-device2connection' %}" + '?id=' + id, 'no'],
end: function () {
oDataTable.ajax.reload();
}
});
}
</script>
{% endblock %}

View File

@@ -0,0 +1,215 @@
{% extends 'base-layer.html' %}
{% load staticfiles %}
{% block css %}
<link rel="stylesheet" href="{%static 'plugins/select2/select2.min.css' %}">
<link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap-datetimepicker.min.css' %}">
{% endblock %}
{% block main %}
<div class="box box-danger">
<form class="form-horizontal" id="addForm" method="post">
<input type="hidden" name='id' value="{{ deviceinfo.id }}" />
<input type="hidden" name='changed_by' value="{{ request.user.id }}" />
{% csrf_token %}
<div class="box-body">
<fieldset>
<legend>
</legend>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">主机名</label>
<div class="col-sm-3">
<input class="form-control" name="sys_hostname" type="text" value="{{ deviceinfo.sys_hostname }}" />
</div>
<label class="col-sm-2 control-label">SN编号</label>
<div class="col-sm-3">
<input class="form-control" name="sn_number" type="text" value="{{ deviceinfo.sn_number }}" />
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">系统类型</label>
<div class="col-sm-3">
<input class="form-control" name="os_type" type="text" value="{{ deviceinfo.os_type }}" />
</div>
<label class="col-sm-2 control-label">设备类型</label>
<div class="col-sm-3">
<input class="form-control" name="device_type" type="text" value="{{ deviceinfo.device_type }}"/>
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">设备地址</label>
<div class="col-sm-3">
<input class="form-control" name="hostname" type="text" value="{{ deviceinfo.hostname }}" />
</div>
<label class="col-sm-2 control-label">MAC地址</label>
<div class="col-sm-3">
<input class="form-control" name="mac_address" type="text" value="{{ deviceinfo.mac_address }}"/>
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">网络类型</label>
<div class="col-sm-3">
<select class="form-control select2" style="width:100%;" name="network_type">
<option {% ifequal deviceinfo.network_type '' %}selected="selected"{% endifequal %}></option>
{% for code in all_code %}
{% ifequal code.parent.key 'NETWORK_TYPE' %}
<option value="{{ code.id }}" {% ifequal deviceinfo.network_type code.id %}selected="selected"{% endifequal %}>
{{ code.value }}</option>
{% endifequal %}
{% endfor %}
</select>
</div>
<label class="col-sm-2 control-label">服务类型</label>
<div class="col-sm-3">
<select class="form-control select2" style="width:100%;" name="service_type">
<option {% ifequal deviceinfo.service_type '' %}selected="selected"{% endifequal %}></option>
{% for code in all_code %}
{% if code.parent.key == 'SERVICE_TYPE' %}
<option value="{{ code.id }}" {% ifequal deviceinfo.service_type code.id %}selected="selected"{% endifequal %}>
{{ code.value }}</option>
{% endif %}
{% endfor %}
</select>
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">所属项目</label>
<div class="col-sm-3">
<select class="form-control select2" style="width:100%;" name="operation_type">
<option {% ifequal deviceinfo.operation_type '' %}selected="selected"{% endifequal %}></option>
{% for code in all_code %}
{% if code.parent.key == 'OPERATION_TYPE' %}
<option value="{{ code.id }}" {% ifequal deviceinfo.operation_type code.id %}selected="selected"{% endifequal %}>
{{ code.value }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<label class="col-sm-2 control-label">机柜信息</label>
<div class="col-sm-3">
<select class="form-control select2" style="width:100%;" name="dev_cabinet">
<option {% ifequal deviceinfo.dev_cabinet '' %}selected="selected"{% endifequal %}></option>
{% for cabinet in all_cabinet %}
<option value="{{ cabinet.id }}" {% ifequal deviceinfo.dev_cabinet cabinet.id %}selected="selected"{% endifequal %}>
{{ cabinet.number }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">购买日期</label>
<div class="col-sm-3">
<input type="text" class="form-control pull-right form_datetime" name="buyDate"
value="{{ deviceinfo.buyDate | date:'Y-m-d' }}" readonly/>
</div>
<label class="col-sm-2 control-label">质保日期</label>
<div class="col-sm-3">
<input type="text" class="form-control pull-right form_datetime" name="warrantyDate"
value="{{ deviceinfo.warrantyDate | date:'Y-m-d' }}" readonly/>
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">所属</label>
<div class="col-sm-3">
<select class="form-control select2" style="width:100%;" name="parent">
<option {% ifequal deviceinfo.parent_id '' %}selected="selected"{% endifequal %}></option>
{% for device in all_device %}
<option value="{{ device.id }}" {% ifequal deviceinfo.parent_id device.id %}selected="selected"{% endifequal %}>
{{ device.sys_hostname }}({{ device.hostname }})</option>
{% endfor %}
</select>
</div>
<label class="col-sm-2 control-label">配置信息</label>
<div class="col-sm-3">
<input class="form-control" name="config" type="text" value="{{ deviceinfo.config }}" />
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">备注信息</label>
<div class="col-sm-8">
<textarea class="form-control" name="desc" rows="5" >{{ deviceinfo.desc }}</textarea>
</div>
</div>
</fieldset>
</div>
<div class="box-footer ">
<div class="row span7 text-center ">
<button type="button" id="btnCancel" class="btn btn-default margin-right " >重置</button>
<button type="button" id="btnSave" class="btn btn-info margin-right " >保存</button>
</div>
</div>
</form>
</div>
{% endblock %}
{% block javascripts %}
<script src="{% static 'plugins/select2/select2.full.min.js' %}"></script>
<script src="{% static 'bootstrap/js/bootstrap-datetimepicker.js' %}"></script>
<script type="text/javascript">
function getUrl() {
if ($("input[name='id']").val()) {
var url = "{% url 'cmdb:eam-device-update' %}";
} else {
var url = "{% url 'cmdb:eam-device-create' %}";
}
return url
}
$("#btnSave").click(function () {
var data = $("#addForm").serialize();
$.ajax({
type: $("#addForm").attr('method'),
url: getUrl(),
data: data,
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert('数据保存成功!', {icon: 1}, function (index) {
parent.layer.closeAll(); //关闭所有弹窗
});
} else {
layer.alert(msg.error, {icon: 5});
//$('errorMessage').html(msg.message)
}
return;
}
});
});
/*点取消刷新新页面*/
$("#btnCancel").click(function () {
window.location.reload();
})
/*input 时间输入选择*/
$(".form_datetime").datetimepicker({
language: 'zh',
minView: 'month', //选择范围知道日期不选择时分
//weekStart: 1,
//todayBtn: 1,
autoclose: 1,
todayHighlight: 1,
//startView: 2,
forceParse: 0,
showMeridian: 1,
format: 'yyyy-mm-dd'
}).on('changeDate', function (ev) {
$(this).datetimepicker('hide');
});
// select2
$(function () {
//Initialize Select2 Elements
$(".select2").select2();
});
</script>
{% endblock %}

View File

@@ -0,0 +1,65 @@
{% extends 'base-layer.html' %}
{% load staticfiles %}
{% block css %}
<link rel="stylesheet" href="{%static 'plugins/select2/select2.min.css' %}">
<link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap-datetimepicker.min.css' %}">
<link rel="stylesheet" href="{% static 'plugins/fileinput/fileinput.css' %}">
{% endblock %}
{% block main %}
<div class="box box-danger">
<form class="form-horizontal" id="addForm" method="post">
{% csrf_token %}
<div class="box-body">
<fieldset>
<div class="form-group has-feedback">
<div class="col-sm-12">
<div class="file-loading">
<input id="file_content" name="file_content" type="file" multiple="multiple"/>
</div>
</div>
</div>
</fieldset>
<fieldset>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label"></label>
<div class="col-sm-12">
<p class="text-red">同时最多可上传4个文件支持文件格式png", "jpg", "gif", "zip", "rar"大小不得超过10M</p>
</div>
</div>
</fieldset>
</div>
</form>
</div>
{% endblock %}
{% block javascripts %}
<script src="{% static 'plugins/fileinput/fileinput.js' %}"></script>
<script src="{% static 'plugins/fileinput/zh.js' %}"></script>
<script type="text/javascript">
//上传文件
$(document).on('ready', function() {
$("#file_content").fileinput({
language: "zh",
showUpload: true,
allowedFileExtensions: ["png", "jpg", "gif", "zip", "rar"],
uploadUrl: "{% url 'cmdb:eam-device-upload' %}",
uploadExtraData: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'device': '{{ device.id }}',
'upload_user': '{{ request.user.name }}',
},
maxFileCount: 4,
autoReplace: true,
maxFileSize: 10240,
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,351 @@
{% extends "base-left.html" %}
{% load staticfiles %}
{% block css %}
<link rel="stylesheet" href="{% static 'plugins/datatables/jquery.dataTables.min.css' %}">
<link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
<link rel="stylesheet" href="{% static 'plugins/select2/select2.min.css' %}">
{% endblock %}
{% block content %}
<!-- Main content -->
<section class="content">
<div id="devlist">
<div class="box box-primary" id="liebiao">
<div class="box-header">
<div class="btn-group pull-left">
<button type="button" id="btnRefresh" class="btn btn-default">
<i class="glyphicon glyphicon-repeat"></i>刷新
</button>
</div>
<div class="btn-group pull-left">&nbsp</div>
<div class="btn-group pull-left">
<button type="button" id="btnCreate" class="btn btn-default">
<i class="glyphicon glyphicon-plus"></i>新增
</button>
</div>
<div class="btn-group pull-left">&nbsp</div>
<div class="btn-group pull-left">
<button type="button" id="btnDelete" class="btn btn-default">
<i class="glyphicon glyphicon-trash"></i>删除
</button>
</div>
</div>
<div class="box-header">
<form class="form-inline" id="queryForm">
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>域名</label>
<input type="text" name="domain" class="form-control inputText" id="domain">
</div>
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>所属项目</label>
<select class="form-control inputText select2" name="operation_type" , id="operation_type">
<option></option>
{% for code in all_operation %}
{% if code.parent.key == 'OPERATION_TYPE' %}
<option value="{{ code.id }}">{{ code.value }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>域名类型</label>
<select class="form-control inputText select2" name="dn_type" , id="dn_type">
<option></option>
<option value="1">一级域名</option>
<option value="2">二级域名</option>
</select>
</div>
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>有效期</label>
<select id="select" name="select" class="form-control inputText select2">
<option style="text-align:center" value="">-----所有-----</option>
<option value="0">已过期</option>
<option value="1">三月内即将到期</option>
</select>
</div>
<button type="button" id="btnSearch" class="btn btn-default">
<i class="glyphicon glyphicon-search"></i>查询
</button>
</form>
</div>
<div class="box-body">
<table id="dtbList" class="display" cellspacing="0" width="100%">
<thead>
<tr valign="middle">
<th><input type="checkbox" id="checkAll"></th>
<th>ID</th>
<th>域名</th>
<th>域名类型</th>
<th>域名解析商</th>
<th>域名服务商</th>
<th>所属项目</th>
<th>状态</th>
<th>到期时间</th>
<th>备注信息</th>
<th>操作</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<br> <br>
</div>
</div>
</div>
</section>
<!-- /.content -->
{% endblock %}
{% block javascripts %}
<script src="{% static 'plugins/datatables/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'plugins/datatables/dataTables.const-1.js' %}"></script>
<script src="{% static 'js/plugins/layer/layer.js' %}"></script>
<script src="{% static 'plugins/select2/select2.full.min.js' %}"></script>
<script type="text/javascript">
// 菜单选中高亮
$(function () {
$('#CMDB-EAM').addClass('active');
$('#CMDB-EAM-DOMAIN').addClass('active');
});
// datatables 初始化配置
var oDataTable = null;
$(function () {
oDataTable = initTable();
function initTable() {
var oTable = $('#dtbList').DataTable($.extend(true, {},
DATATABLES_CONSTANT.DATA_TABLES.SERVER_SIDE_OPTION,
{
ajax: {
"url": "{% url 'cmdb:eam-domain-list' %}",
"data": function (d) {
d.domain = $("#domain").val();
d.select = $("#select").val();
d.dn_type = $("#dn_type").val();
d.operation_type = $("#operation_type").val();
}
},
columns: [
DATATABLES_CONSTANT.DATA_TABLES.COLUMN.CHECKBOX,
{
data: "id",
},
{
data: "domain",
},
{
data: "dn_type",
render : function(data, type, row, meta) {
if (data=="1") {
return "一级域名";
}if (data=="2") {
return "二级域名";
}
}
},
{
data: "resolution_server__firm",
},
{
data: "domain_provider__firm",
},
{
data: "operation_type__value",
},
{
data: "state",
render : function(data, type, row, meta) {
if (data==1) {
var ret="<button class='btn btn-success btn-xs'>在用</button>";
return ret;
}if (data==0) {
var ret="<button class='btn btn-warning btn-xs'>停用</button>";
return ret;
}
}
},
{
data: "warrantyDate",
},
{
data: "desc",
},
{
data: "id",
bSortable: "false",
render: function (data, type, row, meta) {
var ret = "<button title='修改' onclick='doUpdate("
+ data + ")'><i class='glyphicon glyphicon-pencil'></i></button>";
ret = ret + "<button title='解析地址' onclick='dodn2nr("
+ data + ")'><i class='glyphicon glyphicon-globe'></i></button>";
ret = ret + "<button title='删除' onclick='doDelete("
+ data + ")'><i class='glyphicon glyphicon-trash'></i></button>";
return ret;
}
}],
}));
return oTable;
}
});
// 刷新数据
$("#btnRefresh").click(function () {
window.location.reload();
});
//新建数据
$("#btnCreate").click(function () {
layer.open({
type: 2,
title: '新增',
shadeClose: false,
maxmin: true,
area: ['800px', '620px'],
content: "{% url 'cmdb:eam-domain-create' %}",
end: function () {
//关闭时做的事情
oDataTable.ajax.reload();
}
});
});
//修改数据
function doUpdate(id) {
layer.open({
type: 2,
title: '编辑',
shadeClose: false,
maxmin: true,
area: ['800px', '620px'],
content: ["{% url 'cmdb:eam-domain-update' %}" + '?id=' + id],
end: function () {
oDataTable.ajax.reload();
}
});
}
//checkbox全选
$("#checkAll").on("click", function () {
if ($(this).prop("checked") === true) {
$("input[name='checkList']").prop("checked", $(this).prop("checked"));
$('#example tbody tr').addClass('selected');
} else {
$("input[name='checkList']").prop("checked", false);
$('#example tbody tr').removeClass('selected');
}
});
//批量删除
$("#btnDelete").click(function () {
if ($("input[name='checkList']:checked").length == 0) {
layer.msg("请选择要删除的记录");
return;
}
var arrId = new Array();
$("input[name='checkList']:checked").each(function () {
//alert($(this).val());
arrId.push($(this).val());
});
sId = arrId.join(',');
layer.alert('确定删除吗?', {
title: '提示'
, icon: 3 //0:感叹号 1对号 2差号 3问号 4小锁 5哭脸 6笑脸
, time: 0 //不自动关闭
, btn: ['YES', 'NO']
, yes: function (index) {
layer.close(index);
$.ajax({
type: "POST",
url: "{% url 'cmdb:eam-domain-delete' %}",
data: {"id": sId, csrfmiddlewaretoken: '{{ csrf_token }}'},
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert("操作成功", {icon: 1});
oDataTable.ajax.reload();
} else {
//alert(msg.message);
layer.alert("操作失败", {icon: 2});
}
return;
}
});
}
});
});
//删除单个数据
function doDelete(id) {
layer.alert('确定删除吗?', {
title: '提示'
, icon: 3 //0:感叹号 1对号 2差号 3问号 4小锁 5哭脸 6笑脸
, time: 0 //不自动关闭
, btn: ['YES', 'NO']
, yes: function (index) {
layer.close(index);
$.ajax({
type: "POST",
url: "{% url 'cmdb:eam-domain-delete' %}",
data: {"id": id, csrfmiddlewaretoken: '{{ csrf_token }}'},
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert('删除成功', {icon: 1});
oDataTable.ajax.reload();
} else {
//alert(msg.message);
layer.alert('删除失败', {icon: 2});
}
return;
}
});
}
});
}
//select2
$(function () {
//Initialize Select2 Elements
$(".select2").select2();
});
$("#btnSearch").click(function(){
oDataTable.ajax.reload();
});
function dodn2nr(id) {
layer.open({
type: 2,
title: '解析地址',
shadeClose: false,
skin: 'layui-layer-lan',
maxmin: true,
area: ['800px', '400px'],
content: ["{% url 'cmdb:eam-domain-dn2nr' %}" + '?id=' + id],
});
}
</script>
{% endblock %}

View File

@@ -0,0 +1,32 @@
{% extends 'base-layer.html' %}
{% load staticfiles %}
{% block main %}
<div class="box">
<div class="box-body">
<table class="table table-bordered">
<tr>
<th style="width: 10px"></th>
<th>公网IP</th>
<th>源端口</th>
<th>内网iP</th>
<th>目的端口</th>
<th>状态</th>
<th>规则说明</th>
</tr>
{% for nat in all_nat %}
<tr>
<td></td>
<td>{{ nat.internet_ip }}</td>
<td>{{ nat.src_port }}</td>
<td>{{ nat.lan_ip }}</td>
<td>{{ nat.dest_port }}</td>
<td>{{ nat.state }}</td>
<td>{{ nat.desc }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,197 @@
{% extends 'base-layer.html' %}
{% load staticfiles %}
{% block css %}
<link rel="stylesheet" href="{%static 'plugins/select2/select2.min.css' %}">
<link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap-datetimepicker.min.css' %}">
{% endblock %}
{% block main %}
<div class="box box-danger">
<form class="form-horizontal" id="addForm" method="post">
<input type="hidden" name='id' value="{{ domainname.id }}" />
{% csrf_token %}
<div class="box-body">
<fieldset>
<legend>
</legend>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">域名地址</label>
<div class="col-sm-8">
<input class="form-control" name="domain" type="text" value="{{ domainname.domain }}" />
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">NAT规则</label>
<div class="col-sm-8">
<select class="form-control select2" multiple="multiple" name="nat_rule" data-placeholder="关联NAT(可多选)"
style="width: 100%;">
{% for nat in all_nat %}
<option value="{{ nat.id }}" {% if nat in domainname.nat_rule.all %}selected="selected"
{% endif %}>{{ nat.internet_ip }}
{% if nat.internet_ip %}:{{ nat.src_port }}{% endif %}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">域名解析</label>
<div class="col-sm-8">
<select class="form-control select2" style="width:100%;" name="resolution_server">
<option {% ifequal domainname.resolution_server '' %}selected="selected"{% endifequal %}></option>
{% for supplier in all_supplier %}
<option value="{{ supplier.id }}" {% ifequal domainname.resolution_server.id supplier.id %}selected="selected"{% endifequal %}>
{{ supplier.firm }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">域名服务</label>
<div class="col-sm-8">
<select class="form-control select2" style="width:100%;" name="domain_provider">
<option {% ifequal domainname.domain_provider '' %}selected="selected"{% endifequal %}></option>
{% for supplier in all_supplier %}
<option value="{{ supplier.id }}" {% ifequal domainname.domain_provider.id supplier.id %}selected="selected"{% endifequal %}>
{{ supplier.firm }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">购买日期</label>
<div class="col-sm-3">
<input type="text" class="form-control pull-right form_datetime" name="buyDate"
value="{{ domainname.buyDate | date:'Y-m-d' }}" readonly/>
</div>
<label class="col-sm-2 control-label">质保日期</label>
<div class="col-sm-3">
<input type="text" class="form-control pull-right form_datetime" name="warrantyDate"
value="{{ domainname.warrantyDate | date:'Y-m-d' }}" readonly/>
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">所属项目</label>
<div class="col-sm-3">
<select class="form-control select2" style="width:100%;" name="operation_type">
<option {% ifequal deviceinfo.operation_type '' %}selected="selected"{% endifequal %}></option>
{% for code in all_operation %}
<option value="{{ code.id }}" {% ifequal domainname.operation_type.id code.id %}selected="selected"{% endifequal %}>
{{ code.value }}</option>
{% endfor %}
</select>
</div>
<label class="col-sm-2 control-label">域名类型</label>
<div class="col-sm-3">
<select class="form-control select2" style="width:100%;" name="dn_type">
<option {% ifequal domanname.dn_type '' %}selected="selected"{% endifequal %}></option>
<option value="1" {% ifequal domainname.dn_type "1" %}selected="selected"{% endifequal %}>一级域名</option>
<option value="2" {% ifequal domainname.dn_type "2" %}selected="selected"{% endifequal %}>二级域名</option>
</select>
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">状态</label>
<div class="col-sm-3">
<label class="control-label">
<input type="radio" class="minimal" name="state" value="True"
{% ifequal domainname.state 1 %}checked{% endifequal %}
{% if not domainname %}checked{% endif %}>&nbsp;&nbsp;在用 &nbsp;&nbsp;
</label>
<label class="control-label">
<input type="radio" class="minimal" name="state" value="False"
{% ifequal natrule.state 0 %}checked{% endifequal %}> &nbsp;&nbsp;停用
</label>
</div>
</div>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label">域名备注</label>
<div class="col-sm-8">
<textarea class="form-control" name="desc" rows="5" >{{ domainname.desc }}</textarea>
</div>
</div>
</fieldset>
</div>
<div class="box-footer ">
<div class="row span7 text-center ">
<button type="button" id="btnCancel" class="btn btn-default margin-right " >重置</button>
<button type="button" id="btnSave" class="btn btn-info margin-right " >保存</button>
</div>
</div>
</form>
</div>
{% endblock %}
{% block javascripts %}
<script src="{% static 'plugins/select2/select2.full.min.js' %}"></script>
<script src="{% static 'bootstrap/js/bootstrap-datetimepicker.js' %}"></script>
<script type="text/javascript">
function getUrl() {
if ($("input[name='id']").val()) {
var url = "{% url 'cmdb:eam-domain-update' %}";
} else {
var url = "{% url 'cmdb:eam-domain-create' %}";
}
return url
}
$("#btnSave").click(function () {
var data = $("#addForm").serialize();
$.ajax({
type: $("#addForm").attr('method'),
url: getUrl(),
data: data,
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert('数据保存成功!', {icon: 1}, function (index) {
parent.layer.closeAll(); //关闭所有弹窗
});
} else {
layer.alert(msg.error, {icon: 5});
//$('errorMessage').html(msg.message)
}
return;
}
});
});
/*点取消刷新新页面*/
$("#btnCancel").click(function () {
window.location.reload();
})
/*input 时间输入选择*/
$(".form_datetime").datetimepicker({
language: 'zh',
minView: 'month', //选择范围知道日期不选择时分
//weekStart: 1,
//todayBtn: 1,
autoclose: 1,
todayHighlight: 1,
//startView: 2,
forceParse: 0,
showMeridian: 1,
format: 'yyyy-mm-dd'
}).on('changeDate', function (ev) {
$(this).datetimepicker('hide');
});
// select2
$(function () {
//Initialize Select2 Elements
$(".select2").select2();
});
</script>
{% endblock %}

325
templates/cmdb/natrule.html Normal file
View File

@@ -0,0 +1,325 @@
{% extends "base-left.html" %}
{% load staticfiles %}
{% block css %}
<link rel="stylesheet" href="{% static 'plugins/datatables/jquery.dataTables.min.css' %}">
<link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
<link rel="stylesheet" href="{% static 'plugins/select2/select2.min.css' %}">
{% endblock %}
{% block content %}
<!-- Main content -->
<section class="content">
<div id="devlist">
<div class="box box-primary" id="liebiao">
<div class="box-header">
<div class="btn-group pull-left">
<button type="button" id="btnRefresh" class="btn btn-default">
<i class="glyphicon glyphicon-repeat"></i>刷新
</button>
</div>
<div class="btn-group pull-left">&nbsp</div>
<div class="btn-group pull-left">
<button type="button" id="btnCreate" class="btn btn-default">
<i class="glyphicon glyphicon-plus"></i>新增
</button>
</div>
<div class="btn-group pull-left">&nbsp</div>
<div class="btn-group pull-left">
<button type="button" id="btnDelete" class="btn btn-default">
<i class="glyphicon glyphicon-trash"></i>删除
</button>
</div>
</div>
<div class="box-header">
<form class="form-inline" id="queryForm">
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>公网IP</label>
<input type="text" name="internet_ip" class="form-control inputText" id="internet_ip">
</div>
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>源端口</label>
<input type="text" name="src_port" class="form-control inputText" id="src_port">
</div>
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>内网IP</label>
<input type="text" name="lan_ip" class="form-control inputText" id="lan_ip">
</div>
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>目的端口</label>
<input type="text" name="dest_port" class="form-control inputText" id="dest_port">
</div>
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>规则说明</label>
<input type="text" name="desc" class="form-control inputText" id="desc">
</div>
<div class="form-group searchArea margin-r-5 margin-top-5">
<label>机柜信息</label>
<select class="form-control inputText select2" name="dev_cabinet" , id="dev_cabinet">
<option></option>
{% for cabinet in all_cabinet %}
<option value="{{ cabinet.id }}">{{ cabinet.number }}</option>
{% endfor %}
</select>
</div>
<button type="button" id="btnSearch" class="btn btn-default">
<i class="glyphicon glyphicon-search"></i>查询
</button>
</form>
</div>
<div class="box-body">
<table id="dtbList" class="display" cellspacing="0" width="100%">
<thead>
<tr valign="middle">
<th><input type="checkbox" id="checkAll"></th>
<th>ID</th>
<th>互联网IP</th>
<th>源端口</th>
<th>内网IP</th>
<th>目的端口</th>
<th>状态</th>
<th>机柜信息</th>
<th>规则说明</th>
<th>操作</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<br> <br>
</div>
</div>
</div>
</section>
<!-- /.content -->
{% endblock %}
{% block javascripts %}
<script src="{% static 'plugins/datatables/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'plugins/datatables/dataTables.const-1.js' %}"></script>
<script src="{% static 'js/plugins/layer/layer.js' %}"></script>
<script src="{% static 'plugins/select2/select2.full.min.js' %}"></script>
<script type="text/javascript">
// 菜单选中高亮
$(function () {
$('#CMDB-EAM').addClass('active');
$('#CMDB-EAM-NATRULE').addClass('active');
});
// datatables 初始化配置
var oDataTable = null;
$(function () {
oDataTable = initTable();
function initTable() {
var oTable = $('#dtbList').DataTable($.extend(true, {},
DATATABLES_CONSTANT.DATA_TABLES.SERVER_SIDE_OPTION,
{
ajax: {
"url": "{% url 'cmdb:eam-natrule-list' %}",
"data": function (d) {
d.internet_ip = $("#internet_ip").val();
d.src_port = $("#src_port").val();
d.lan_ip = $("#lan_ip").val();
d.dest_port = $("#dest_port").val();
d.dev_cabinet = $("#dev_cabinet").val();
d.desc = $("#desc").val();
}
},
columns: [
DATATABLES_CONSTANT.DATA_TABLES.COLUMN.CHECKBOX,
{
data: "id",
},
{
data: "internet_ip",
},
{
data: "src_port",
},
{
data: "lan_ip",
},
{
data: "dest_port",
},
{
data: "state",
render : function(data, type, row, meta) {
if (data==1) {
var ret="<button class='btn btn-success btn-xs'>在用</button>";
return ret;
}if (data==0) {
var ret="<button class='btn btn-warning btn-xs'>停用</button>";
return ret;
}
}
},
{
data: "dev_cabinet__number",
},
{
data: "desc",
},
{
data: "id",
bSortable: "false",
render: function (data, type, row, meta) {
var ret = "<button title='详情-修改' onclick='doUpdate("
+ data + ")'><i class='glyphicon glyphicon-pencil'></i></button>";
ret = ret + "<button title='删除' onclick='doDelete("
+ data + ")'><i class='glyphicon glyphicon-trash'></i></button>";
return ret;
}
}],
}));
return oTable;
}
});
// 刷新数据
$("#btnRefresh").click(function () {
window.location.reload();
});
//新建数据
$("#btnCreate").click(function () {
layer.open({
type: 2,
title: '新增',
shadeClose: false,
maxmin: true,
area: ['800px', '500px'],
content: "{% url 'cmdb:eam-natrule-create' %}",
end: function () {
//关闭时做的事情
oDataTable.ajax.reload();
}
});
});
//修改数据
function doUpdate(id) {
layer.open({
type: 2,
title: '编辑',
shadeClose: false,
maxmin: true,
area: ['800px', '500px'],
content: ["{% url 'cmdb:eam-natrule-update' %}" + '?id=' + id, 'no'],
end: function () {
oDataTable.ajax.reload();
}
});
}
//checkbox全选
$("#checkAll").on("click", function () {
if ($(this).prop("checked") === true) {
$("input[name='checkList']").prop("checked", $(this).prop("checked"));
$('#example tbody tr').addClass('selected');
} else {
$("input[name='checkList']").prop("checked", false);
$('#example tbody tr').removeClass('selected');
}
});
//批量删除
$("#btnDelete").click(function () {
if ($("input[name='checkList']:checked").length == 0) {
layer.msg("请选择要删除的记录");
return;
}
var arrId = new Array();
$("input[name='checkList']:checked").each(function () {
//alert($(this).val());
arrId.push($(this).val());
});
sId = arrId.join(',');
layer.alert('确定删除吗?', {
title: '提示'
, icon: 3 //0:感叹号 1对号 2差号 3问号 4小锁 5哭脸 6笑脸
, time: 0 //不自动关闭
, btn: ['YES', 'NO']
, yes: function (index) {
layer.close(index);
$.ajax({
type: "POST",
url: "{% url 'cmdb:eam-natrule-delete' %}",
data: {"id": sId, csrfmiddlewaretoken: '{{ csrf_token }}'},
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert("操作成功", {icon: 1});
oDataTable.ajax.reload();
} else {
//alert(msg.message);
layer.alert("操作失败", {icon: 2});
}
return;
}
});
}
});
});
//删除单个数据
function doDelete(id) {
layer.alert('确定删除吗?', {
title: '提示'
, icon: 3 //0:感叹号 1对号 2差号 3问号 4小锁 5哭脸 6笑脸
, time: 0 //不自动关闭
, btn: ['YES', 'NO']
, yes: function (index) {
layer.close(index);
$.ajax({
type: "POST",
url: "{% url 'cmdb:eam-natrule-delete' %}",
data: {"id": id, csrfmiddlewaretoken: '{{ csrf_token }}'},
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert('删除成功', {icon: 1});
oDataTable.ajax.reload();
} else {
//alert(msg.message);
layer.alert('删除失败', {icon: 2});
}
return;
}
});
}
});
}
//select2
$(function () {
//Initialize Select2 Elements
$(".select2").select2();
});
$("#btnSearch").click(function(){
oDataTable.ajax.reload();
});
</script>
{% endblock %}

Some files were not shown because too many files have changed in this diff Show More