mirror of
https://github.com/RobbieHan/sandboxMP.git
synced 2026-02-03 19:03:15 +08:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc5a19bdbf | ||
|
|
9430fa0781 | ||
|
|
0e7bb2678f | ||
|
|
667b4bc29c | ||
|
|
24a3f4c107 | ||
|
|
9216755a5b | ||
|
|
892ec0b2de | ||
|
|
05b1cc6665 | ||
|
|
8c00fdd315 | ||
|
|
3182032540 | ||
|
|
2646254e52 | ||
|
|
e3490b3af7 | ||
|
|
b58be33aac | ||
|
|
f1dde180fb | ||
|
|
6765fa8b66 | ||
|
|
6b413bc114 | ||
|
|
bca383fbb4 | ||
|
|
ad74ed1802 | ||
|
|
e6272ccaab | ||
|
|
a36f8d74f6 | ||
|
|
cea6fa7cba | ||
|
|
eb10ffe9be | ||
|
|
9c204933e8 | ||
|
|
371b1ebbe3 | ||
|
|
9d0bd95b69 | ||
|
|
4fbdc88743 | ||
|
|
860ae14d4c | ||
|
|
00f7112b67 | ||
|
|
ba936d7f9e | ||
|
|
c399b77703 | ||
|
|
894fe29e15 | ||
|
|
602edea8f8 | ||
|
|
cbe0c54472 | ||
|
|
9d149a6882 | ||
|
|
0b47673759 | ||
|
|
e2edef0af2 | ||
|
|
1f94ffa857 | ||
|
|
134ea4426f | ||
|
|
04d01aa273 | ||
|
|
4570252f6d | ||
|
|
0f6bd53883 | ||
|
|
8c3158b18e | ||
|
|
cd879f38d7 | ||
|
|
1526767e87 | ||
|
|
6dfb9197e6 | ||
|
|
64a8fab611 | ||
|
|
9bffe64477 | ||
|
|
45630bd451 | ||
|
|
a328e88122 | ||
|
|
2511a3c066 | ||
|
|
b85d89642d | ||
|
|
832c55ab34 | ||
|
|
f72efee2e1 | ||
|
|
2bba133771 | ||
|
|
2261bc6b3a | ||
|
|
005b62a01e |
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*.js linguist-language=python
|
||||
*.css linguist-language=python
|
||||
*.html linguist-language=python
|
||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.idea
|
||||
3
.idea/dictionaries/RobbieHan.xml
generated
3
.idea/dictionaries/RobbieHan.xml
generated
@@ -1,3 +0,0 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="RobbieHan" />
|
||||
</component>
|
||||
4
.idea/misc.xml
generated
4
.idea/misc.xml
generated
@@ -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
8
.idea/modules.xml
generated
@@ -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
32
.idea/sandboxMP.iml
generated
@@ -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="<map/>" />
|
||||
<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
6
.idea/vcs.xml
generated
@@ -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
661
.idea/workspace.xml
generated
@@ -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)) import django; print('Django %s' % django.get_version()) sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS]) if 'setup' in dir(django): django.setup() 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)) import django; print('Django %s' % django.get_version()) sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS]) if 'setup' in dir(django): django.setup() 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
777
README.md
Normal 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命令需要先安装git:yum -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做代理并提供静态资源访问和简单缓存功能。一般项部署项目上线,我会分步骤进行,这样在遇到问题也清楚是哪一个环节出的问题,可以有针对性的进行排错。部署过程如下图所示:
|
||||
|
||||

|
||||
|
||||
## 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.
BIN
apps/__pycache__/custom.cpython-36.pyc
Normal file
BIN
apps/__pycache__/custom.cpython-36.pyc
Normal file
Binary file not shown.
1
apps/cmdb/__init__.py
Normal file
1
apps/cmdb/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
default_app_config = 'cmdb.apps.CmdbConfig'
|
||||
3
apps/cmdb/admin.py
Normal file
3
apps/cmdb/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
8
apps/cmdb/apps.py
Normal file
8
apps/cmdb/apps.py
Normal 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
171
apps/cmdb/forms.py
Normal 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': '域名类型不能为空'}
|
||||
}
|
||||
0
apps/cmdb/migrations/__init__.py
Normal file
0
apps/cmdb/migrations/__init__.py
Normal file
195
apps/cmdb/models.py
Normal file
195
apps/cmdb/models.py
Normal 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
46
apps/cmdb/signals.py
Normal 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
56
apps/cmdb/tasks.py
Normal 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
|
||||
3
apps/cmdb/templatetags/__init__.py
Normal file
3
apps/cmdb/templatetags/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# @Time : 2019/2/17 21:28
|
||||
# @Author : RobbieHan
|
||||
# @File : __init__.py.py
|
||||
58
apps/cmdb/templatetags/extra_tags.py
Normal file
58
apps/cmdb/templatetags/extra_tags.py
Normal 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
22
apps/cmdb/tests.py
Normal 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
66
apps/cmdb/urls.py
Normal 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
51
apps/cmdb/views.py
Normal 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
53
apps/cmdb/views_code.py
Normal 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
460
apps/cmdb/views_eam.py
Normal 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
118
apps/cmdb/views_scan.py
Normal 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
162
apps/custom.py
Normal 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)
|
||||
@@ -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.
BIN
apps/system/__pycache__/middleware.cpython-36.pyc
Normal file
BIN
apps/system/__pycache__/middleware.cpython-36.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
apps/system/__pycache__/urls.cpython-36.pyc
Normal file
BIN
apps/system/__pycache__/urls.cpython-36.pyc
Normal file
Binary file not shown.
BIN
apps/system/__pycache__/views.cpython-36.pyc
Normal file
BIN
apps/system/__pycache__/views.cpython-36.pyc
Normal file
Binary file not shown.
BIN
apps/system/__pycache__/views_menu.cpython-36.pyc
Normal file
BIN
apps/system/__pycache__/views_menu.cpython-36.pyc
Normal file
Binary file not shown.
BIN
apps/system/__pycache__/views_role.cpython-36.pyc
Normal file
BIN
apps/system/__pycache__/views_role.cpython-36.pyc
Normal file
Binary file not shown.
BIN
apps/system/__pycache__/views_structure.cpython-36.pyc
Normal file
BIN
apps/system/__pycache__/views_structure.cpython-36.pyc
Normal file
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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
100
apps/system/middleware.py
Normal 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')
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
@@ -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
50
apps/system/signals.py
Normal 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)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
|
||||
40
apps/system/urls.py
Normal file
40
apps/system/urls.py
Normal 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')
|
||||
]
|
||||
@@ -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
29
apps/system/views_menu.py
Normal 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
118
apps/system/views_role.py
Normal 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')
|
||||
90
apps/system/views_structure.py
Normal file
90
apps/system/views_structure.py
Normal 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')
|
||||
@@ -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
3
apps/utils/__init__.py
Normal 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
18
apps/utils/db_utils.py
Normal 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
209
apps/utils/sandbox_utils.py
Normal 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)
|
||||
219
config/basic_data_20190225.sql
Normal file
219
config/basic_data_20190225.sql
Normal file
File diff suppressed because one or more lines are too long
19
config/celery_worker.ini
Normal file
19
config/celery_worker.ini
Normal 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
64
config/nginx.conf
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
config/sandboxmp_uwsgi.ini
Normal file
8
config/sandboxmp_uwsgi.ini
Normal 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
18
config/scanhosts.yml
Normal 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
12
config/smp_uwsgi.ini
Normal 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
|
||||
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
BIN
document/images/stepww.png
Normal file
BIN
document/images/stepww.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
1
requirements/dev.txt
Normal file
1
requirements/dev.txt
Normal file
@@ -0,0 +1 @@
|
||||
-r pro.txt
|
||||
14
requirements/pro.txt
Normal file
14
requirements/pro.txt
Normal 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
|
||||
@@ -0,0 +1,5 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from .celery import app as celery_app
|
||||
|
||||
__all__ = ('celery_app')
|
||||
Binary file not shown.
Binary file not shown.
38
sandboxMP/celery.py
Normal file
38
sandboxMP/celery.py
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
0
slogs/sandbox_err.log
Normal file
0
slogs/sandbox_info.log
Normal file
0
slogs/sandbox_info.log
Normal file
@@ -2,7 +2,7 @@ var DATATABLES_CONSTANT = {
|
||||
|
||||
// datatables常量
|
||||
DATA_TABLES : {
|
||||
DEFAULT_OPTION : { // DataTables初始化选项
|
||||
SERVER_SIDE_OPTION : { // DataTables初始化选项
|
||||
oLanguage : {
|
||||
sProcessing : "处理中...",
|
||||
sLengthMenu : "每页 _MENU_ 项",//"显示 _MENU_ 项结果,",
|
||||
|
||||
12
static/plugins/echarts/echarts.js
Normal file
12
static/plugins/echarts/echarts.js
Normal file
File diff suppressed because one or more lines are too long
2462
static/plugins/masonry/masonry.js
Normal file
2462
static/plugins/masonry/masonry.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 %}
|
||||
|
||||
|
||||
|
||||
@@ -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
276
templates/cmdb/cabinet.html
Normal 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"> </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"> </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 %}
|
||||
99
templates/cmdb/cabinet_form.html
Normal file
99
templates/cmdb/cabinet_form.html
Normal 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 %}
|
||||
221
templates/cmdb/cmdb_index.html
Normal file
221
templates/cmdb/cmdb_index.html
Normal 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
274
templates/cmdb/code.html
Normal 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"> </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"> </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 %}
|
||||
97
templates/cmdb/code_create.html
Normal file
97
templates/cmdb/code_create.html
Normal 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 %}
|
||||
99
templates/cmdb/code_update.html
Normal file
99
templates/cmdb/code_update.html
Normal 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 %}
|
||||
329
templates/cmdb/device_scan.html
Normal file
329
templates/cmdb/device_scan.html
Normal 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"> </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"> </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"> </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 %}
|
||||
113
templates/cmdb/device_scan_detail.html
Normal file
113
templates/cmdb/device_scan_detail.html
Normal 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 %}
|
||||
359
templates/cmdb/deviceinfo.html
Normal file
359
templates/cmdb/deviceinfo.html
Normal 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"> </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"> </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 %}
|
||||
108
templates/cmdb/deviceinfo2connection.html
Normal file
108
templates/cmdb/deviceinfo2connection.html
Normal 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 %}
|
||||
316
templates/cmdb/deviceinfo_detail.html
Normal file
316
templates/cmdb/deviceinfo_detail.html
Normal 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 }}
|
||||
|
||||
{{ log.changed_by }}
|
||||
|
||||
{{ 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 %}
|
||||
215
templates/cmdb/deviceinfo_form.html
Normal file
215
templates/cmdb/deviceinfo_form.html
Normal 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 %}
|
||||
65
templates/cmdb/deviceinfo_upload.html
Normal file
65
templates/cmdb/deviceinfo_upload.html
Normal 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 %}
|
||||
351
templates/cmdb/domainname.html
Normal file
351
templates/cmdb/domainname.html
Normal 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"> </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"> </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 %}
|
||||
32
templates/cmdb/domainname2natrule.html
Normal file
32
templates/cmdb/domainname2natrule.html
Normal 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 %}
|
||||
197
templates/cmdb/domainname_form.html
Normal file
197
templates/cmdb/domainname_form.html
Normal 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 %}> 在用
|
||||
</label>
|
||||
<label class="control-label">
|
||||
<input type="radio" class="minimal" name="state" value="False"
|
||||
{% ifequal natrule.state 0 %}checked{% endifequal %}> 停用
|
||||
</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
325
templates/cmdb/natrule.html
Normal 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"> </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"> </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
Reference in New Issue
Block a user