私信
兜兜
文章
203
评论
12
点赞
98
原创 177
翻译 4
转载 22

文章
关注
粉丝
收藏

个人分类:

兜兜    2021-11-30 16:34:06    2022-01-25 09:25:41   

H3C 交换机
阅读 1066 评论 0 收藏 0
阅读 1066
评论 0
收藏 0


兜兜    2021-11-23 23:13:52    2021-11-23 23:20:51   

frp 内网穿透
阅读 490 评论 0 收藏 0
阅读 490
评论 0
收藏 0


兜兜    2021-09-22 10:19:33    2022-01-25 09:30:00   

ceph
```sh Cephadm通过manager daemon SSH连接到主机部署和管理Ceph群集,以添加,删除或更新Ceph daemon containers。它不依赖于诸如Ansible,Rook或Salt的外部配置或编排工具。 Cephadm管理Ceph集群的整个生命周期。它首先在一个节点上引导一个微小的Ceph集群(one monitor and one manager),然后自动将集群扩展到多个主机节点,并提供所有Ceph守护程序和服务。这可以通过Ceph命令行界面(CLI)或仪表板(GUI)执行。Cephadm是Octopus v15.2.0版本中的新增功能,并且不支持旧版本的Ceph。 ``` 节点规划: ```sh +--------+---------------+------+---------------------------------+ | Host | ip | Disk | Role | +--------+---------------+------+---------------------------------+ | test01 | 172.16.100.1 | sdc | cephadm,mon,mgr,mds,osd | | test02 | 172.16.100.2 | sdc | mon,mgr,osd | | test11 | 172.16.100.11 | sdb | mon,mds,osd | +--------+---------------+------+---------------------------------+ ``` ##### 安装说明 ```sh ceph版本:15.2.14 octopus(stable) python: 3+ ``` #### 安装docker [参考CentOS7安装Docker-CE](https://ynotes.cn/blog/article_detail/118) #### 安装cephadm(`test01`) ```sh curl --silent --remote-name --location https://github.com/ceph/ceph/raw/octopus/src/cephadm/cephadm chmod +x cephadm ./cephadm add-repo --release octopus ./cephadm install ``` cephadm引导ceph集群 ```sh cephadm bootstrap --mon-ip 172.16.100.1 ``` ```sh Creating directory /etc/ceph for ceph.conf Verifying podman|docker is present... Verifying lvm2 is present... Verifying time synchronization is in place... Unit chronyd.service is enabled and running Repeating the final host check... podman|docker (/usr/bin/docker) is present systemctl is present lvcreate is present Unit chronyd.service is enabled and running Host looks OK Cluster fsid: 797b1aa4-16cf-11ec-a787-00505693fd22 Verifying IP 172.16.100.1 port 3300 ... Verifying IP 172.16.100.1 port 6789 ... Mon IP 172.16.100.1 is in CIDR network 172.16.100.0/24 Pulling container image docker.io/ceph/ceph:v15... Extracting ceph user uid/gid from container image... Creating initial keys... Creating initial monmap... Creating mon... Waiting for mon to start... Waiting for mon... mon is available Assimilating anything we can from ceph.conf... Generating new minimal ceph.conf... Restarting the monitor... Setting mon public_network... Creating mgr... Verifying port 9283 ... Wrote keyring to /etc/ceph/ceph.client.admin.keyring Wrote config to /etc/ceph/ceph.conf Waiting for mgr to start... Waiting for mgr... mgr not available, waiting (1/10)... mgr not available, waiting (2/10)... mgr not available, waiting (3/10)... mgr not available, waiting (4/10)... mgr not available, waiting (5/10)... mgr not available, waiting (6/10)... mgr is available Enabling cephadm module... Waiting for the mgr to restart... Waiting for Mgr epoch 5... Mgr epoch 5 is available Setting orchestrator backend to cephadm... Generating ssh key... Wrote public SSH key to to /etc/ceph/ceph.pub Adding key to root@localhost's authorized_keys... Adding host test01... Deploying mon service with default placement... Deploying mgr service with default placement... Deploying crash service with default placement... Enabling mgr prometheus module... Deploying prometheus service with default placement... Deploying grafana service with default placement... Deploying node-exporter service with default placement... Deploying alertmanager service with default placement... Enabling the dashboard module... Waiting for the mgr to restart... Waiting for Mgr epoch 13... Mgr epoch 13 is available Generating a dashboard self-signed certificate... Creating initial admin user... Fetching dashboard port number... Ceph Dashboard is now available at: URL: https://ceph1:8443/ User: admin Password: hj49zqnfm2 You can access the Ceph CLI with: sudo /usr/sbin/cephadm shell --fsid 797b1aa4-16cf-11ec-a787-00505693fd22 -c /etc/ceph/ceph.conf -k /etc/ceph/ceph.client.admin.keyring Please consider enabling telemetry to help improve Ceph: ceph telemetry on For more information see: https://docs.ceph.com/docs/master/mgr/telemetry/ Bootstrap complete. ``` ```sh 在本地主机上为新集群创建monitor 和 manager daemon守护程序。 为Ceph集群生成一个新的SSH密钥,并将其添加到root用户的/root/.ssh/authorized_keys文件中。 将与新群集进行通信所需的最小配置文件保存到/etc/ceph/ceph.conf。 向/etc/ceph/ceph.client.admin.keyring写入client.admin管理(特权!)secret key的副本。 将public key的副本写入/etc/ceph/ceph.pub ``` 查看节点运行的docker容器 ```sh $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3a707133d00e ceph/ceph:v15 "/usr/bin/ceph-mds -…" 5 days ago Up 5 days ceph-797b1aa4-16cf-11ec-a787-00505693fd22-mds.cephfs.test01.kqizcm 36ada96953ea ceph/ceph:v15 "/usr/bin/ceph-osd -…" 5 days ago Up 5 days ceph-797b1aa4-16cf-11ec-a787-00505693fd22-osd.0 08aa352c61f1 ceph/ceph:v15 "/usr/bin/ceph-mgr -…" 5 days ago Up 5 days ceph-797b1aa4-16cf-11ec-a787-00505693fd22-mgr.test01.soadiy 47be185fc946 prom/node-exporter:v0.18.1 "/bin/node_exporter …" 5 days ago Up 5 days ceph-797b1aa4-16cf-11ec-a787-00505693fd22-node-exporter.test01 dad697dd6669 ceph/ceph-grafana:6.7.4 "/bin/sh -c 'grafana…" 5 days ago Up 5 days ceph-797b1aa4-16cf-11ec-a787-00505693fd22-grafana.test01 754ba4d9e132 prom/alertmanager:v0.20.0 "/bin/alertmanager -…" 5 days ago Up 5 days ceph-797b1aa4-16cf-11ec-a787-00505693fd22-alertmanager.test01 2b7e3664edbf ceph/ceph:v15 "/usr/bin/ceph-crash…" 5 days ago Up 5 days ceph-797b1aa4-16cf-11ec-a787-00505693fd22-crash.test01 c7cf9f5502de ceph/ceph:v15 "/usr/bin/ceph-mon -…" 5 days ago Up 5 days ceph-797b1aa4-16cf-11ec-a787-00505693fd22-mon.test01 ba3958a9dec8 prom/prometheus:v2.18.1 "/bin/prometheus --c…" 5 days ago Up 5 days ceph-797b1aa4-16cf-11ec-a787-00505693fd22-prometheus.test01 c737fb65080c elasticsearch:7.8.0 "/tini -- /usr/local…" 7 months ago Up 5 days 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp es7.6 ``` 添加节点 ```sh ceph orch host add test02 172.16.100.2 ceph orch host add test11 172.16.100.11 ``` 添加硬盘 ```sh ceph orch daemon add osd test01:/dev/sdc ceph orch daemon add osd test02:/dev/sdc ceph orch daemon add osd test11:/dev/sdb ``` ceph osd pool create k8s 64
阅读 376 评论 0 收藏 0
阅读 376
评论 0
收藏 0


兜兜    2021-08-20 16:24:24    2021-08-21 15:23:08   

jenkins
#### _介绍:jenkins的Image Tag Parameter插件支持harbor仓库中获取项目的Tag,可惜阿里云容器镜像仓库不支持Docker V2 API,不过阿里云镜像仓库提供自己一套API。_ #### _`解决方案:python Flask封装阿里云的API(阿里云API是通过access_key和access_secret认证授权,REST list Parameter插件目前不支持),jenkins通过REST list Parameter插件获取数据。`_ #### 一、封装阿里云的API 1.1 python安装Flask和阿里云SDK ```bash pip install flask pip install aliyun-python-sdk-cr==4.1.2 ``` 1.2 添加tools.py(封装阿里云的SDK) ```python #!/usr/bin/env python #coding=utf-8 from aliyunsdkcore.client import AcsClient from aliyunsdkcore.acs_exception.exceptions import ClientException from aliyunsdkcore.acs_exception.exceptions import ServerException from aliyunsdkcore.auth.credentials import AccessKeyCredential from aliyunsdkcore.auth.credentials import StsTokenCredential from aliyunsdkcr.request.v20181201.ListRepoTagRequest import ListRepoTagRequest from aliyunsdkcr.request.v20181201.GetRepositoryRequest import GetRepositoryRequest import json class ContainerImage: def __init__(self, access_key, access_secret, instance_id, region_id='cn-shenzhen', accept_format='json', encoding='utf-8'): self.client = AcsClient(region_id=region_id, credential=AccessKeyCredential(access_key,access_secret)) self.instance_id = instance_id self.accept_format = accept_format self.encoding = encoding def get_repo(self, space_name, repo_name): request = GetRepositoryRequest() request.set_accept_format(self.accept_format) request.set_InstanceId(self.instance_id) request.set_RepoNamespaceName(space_name) request.set_RepoName(repo_name) response = self.client.do_action_with_exception(request) return json.loads(str(response,encoding=self.encoding)) def list_repo_tag(self, space_name, repo_name): repo_obj = self.get_repo(space_name, repo_name) repo_id = repo_obj['RepoId'] request = ListRepoTagRequest() request.set_accept_format(self.accept_format) request.set_InstanceId(self.instance_id) request.set_RepoId(repo_id) response = self.client.do_action_with_exception(request) return json.loads(str(response,encoding=self.encoding)) ``` 1.3 添加Flask的文件app.py ```python from flask import Flask from tools import ContainerImage #导入tools中的ContainerImage类 #配置access_key和access_secret access_key='LTAI5tG3YCyHxxxxxxxxxx' access_secret='oNBXXKfIxxxxxxxxxxxxxxxxx' region_id='cn-shenzhen' instance_id='cri-xxxxxxxxxx' container_image=ContainerImage(access_key, access_secret, instance_id) app = Flask(__name__) #通过url路径获取space_name和repo_name @app.route('/repo/<space_name>/<repo_name>/tags') def list_tags(space_name,repo_name): list_repo_tags=container_image.list_repo_tag(space_name,repo_name) return list_repo_tags if __name__ == '__main__': app.run(host='0.0.0.0', debug=True) ``` 1.4 启动Flask ```bash python app.py ``` 1.5 测试结果 ```bash curl http://172.16.100.202:5000/repo/<space_name>/<repo_name>/tags ``` ```json { "Code": "success", "Images": [ { "Digest": "16c579443109881cd3ba264913824cb074d8e977bfd89d5860aaafad0b10194f", "ImageCreate": 1629278747000, "ImageId": "f79086b9b1a4532e44b30efbf761fde76792cd61be26e9bf5f19469d1e8e358d", "ImageSize": 55157349, "ImageUpdate": 1629278747000, "Status": "NORMAL", "Tag": "master-7d9acb6-17" }, { "Digest": "d577c281172233318ee4d9394882ae0bb6582bb01efc694654890ebf8118b0cf", "ImageCreate": 1629272078000, "ImageId": "8b52daeee868663c3d1fcd49447d17cf8bdd7f9b87ba07904e3a675e008ce90f", "ImageSize": 55157354, "ImageUpdate": 1629272078000, "Status": "NORMAL", "Tag": "master-7d9acb6-16" } ], "IsSuccess": true, "PageNo": 1, "PageSize": 30, "RequestId": "B81C478C-3607-590E-90EC-6C5120446D48", "TotalCount": 2 } ``` #### 三、jenkins pipeline配置REST list Parameter ```groovy parameters { RESTList( name: 'BUILD_IMAGE_TAG', description: '', restEndpoint: 'http://172.16.100.202:5000/repo/<space_name>/<repo_name>/tags', credentialId: '', mimeType: 'APPLICATION_JSON', valueExpression: '$.Images[*].Tag', cacheTime: 10, // optional defaultValue: '', // optional filter: '.*', // optional valueOrder: 'ASC' // optional ) } ```
阅读 684 评论 0 收藏 0
阅读 684
评论 0
收藏 0


兜兜    2021-08-12 16:12:02    2021-08-12 17:59:00   

ssl https certbot
`certbot(pip)安装要求:安装python3且安装了ssl模块,验证方式:import ssl,如果当前环境已满足要求,则直接跳到三、安装certbot` #### 一、安装openssl ```bash ##Download openssl file wget https://www.openssl.org/source/openssl-1.1.1a.tar.gz tar -xzvf openssl-1.1.1a.tar.gz ##decompression #Compile and install, install path is/usr/local/openssl cd openssl-1.1.1a ./config shared zlib --prefix=/usr/local/openssl && make && make install ./config -t make depend #Enter / usr/local to execute the following command ln -s /usr/local/openssl /usr/local/ssl ##Create Links #In/etc/Ld.so.confAt the end of the file, add the following: echo "/usr/local/openssl/lib" >> /etc/ld.so.conf #Execute the following command ldconfig #Set the environment variable for OPESSL and add it on the last line of the etc/profile file: cat >> /etc/profile <<EOF export OPENSSL=/usr/local/openssl/bin export PATH=\$OPENSSL:\$PATH:\$HOME/bin EOF ``` &nbsp; #### 二、安装python3 ```python wget https://www.python.org/ftp/python/3.9.2/Python-3.9.2.tgz#Download Python 3.9 tar zxvf Python-3.9.2.tgz #decompression cd Python-3.9.2 ``` 编辑文件Python3.9/Module/setup ```python # Socket module helper for socket(2) _socket socketmodule.c #Install socket module, source code is socketmodule.c # Socket module helper for SSL support; you must comment out the other # socket line above, and possibly edit the SSL variable: SSL=/usr/local/ssl _ssl _ssl.c \ #Install SSL module, source code is ssl.c -DUSE_SSL -I$(SSL)/include -I$(SSL)/include/openssl \ -L$(SSL)/lib -lssl -lcrypto ``` ```bash ./configure --with-openssl=/usr/local/openssl #Preinstall openssl directory --enable-optimizations #Optimize installation --with-ssl-default-suites=python #Install python's own ssl by default, #It's a little unclear--the difference between the with-openssl and--with-ssl-default-suites commands, but I still run them together make make install ``` ##### 测试python的ssl模块 ```python import ssl ``` &nbsp; #### 三、安装certbot ##### 安装python虚拟环境 ```bash python3 -m venv /opt/certbot/ /opt/certbot/bin/pip install --upgrade pip ``` ##### 安装certbot包 ```bash /opt/certbot/bin/pip install certbot certbot-nginx ln -s /opt/certbot/bin/certbot /usr/bin/certbot ``` certbot获取证书两种方式 `方式一:验证nginx获取证书` ```bash certbot certonly --nginx ``` `方式二:webroot文件获取证书` 修改nginx的server添加验证的location ```bash server { listen 443; server_name ynotes.cn www.ynotes.cn; ... # 配置webroot验证目录 location ^~ /.well-known/acme-challenge/ { default_type "text/plain"; root /var/www/letsencrypt; } } ``` webroot方式获取证书 ```bash certbot certonly --webroot --agree-tos --email sheyinsong@qq.com --webroot-path /var/www/letsencrypt --domains ynotes.cn ``` 配置nginx的SSL证书 ```bash server { listen 443; server_name ynotes.cn www.ynotes.cn; ssl on; ssl_certificate /etc/letsencrypt/live/www.ynotes.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/www.ynotes.cn/privkey.pem; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location ^~ / { root /var/www/html/v3/ynotes.cn; } error_page 500 502 503 504 /50x.html; error_page 404 https://www.ynotes.cn/; location = /50x.html { root html; } } ``` 添加计划任务 ```bash echo "0 0,12 * * * root /opt/certbot/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)' && certbot renew -q" | sudo tee -a /etc/crontab > /dev/null ```
阅读 449 评论 0 收藏 0
阅读 449
评论 0
收藏 0


兜兜    2019-06-20 11:49:12    2019-06-24 14:49:10   

django ztree
### ztree文档 http://www.treejs.cn/v3/api.php ### django配置 #### 创建虚拟环境 ```bash virtualenv venv27 source venv27/bin/activate ``` #### 安装django ```bash pip install django binaryornot ``` #### 创建项目 ```bash django-admin startproject myproject ``` #### 创建app ```bash cd myproject python manage.py startapp blog ``` #### 配置项目settings.py ```bash vim myproject/settings.py ``` ```ini # -*- coding:utf-8 -*- BASE_DIR = os.path.dirname(os.path.abspath(__file__)) ALLOWED_HOSTS = ['*'] INSTALLED_APPS = [ ... 'blog',#安装app ] MEDIA_URL = "/upload/" #配置上传目录 MEDIA_ROOT = os.path.join(BASE_DIR, 'upload') STATIC_URL = '/static/' #配置静态文件目录 STATIC_DIR = os.path.join(BASE_DIR, 'static') STATICFILES_DIRS = [STATIC_DIR, ] ``` #### 创建上传和静态文件目录 ```bash mkdir myproject/{upload,static} ``` #### 配置项目urls.py ```bash vim myproject/urls.py ``` ```python # -*- coding:utf-8 -*- from django.conf.urls import url,include from django.conf.urls.static import static from . import settings urlpatterns = [ url(r'^blog/', include('blog.urls', namespace='blog')), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ``` #### 配置应用urls.py ```bash vim blog/urls.py ``` ```python # -*- coding:utf-8 -*- from django.conf.urls import url from blog import views,filetree_views urlpatterns = [ url(r'^demo/$', views.demo, name='demo'),#展示视图 url(r'^async_get_nodes/', filetree_views.async_get_nodes, name='async_get_nodes'), #异步加载节点 url(r'^rename_node/', filetree_views.rename_node, name='rename_node'), #重命名节点 url(r'^add_node/', filetree_views.add_node, name='add_node'), #添加节点 url(r'^remove_node/', filetree_views.remove_node, name='remove_node'), #移除节点 url(r'^move_node/', filetree_views.move_node, name='move_node'), #移动节点 url(r'^read_node/', filetree_views.read_node, name='read_node'), #读取节点 url(r'^save_node/', filetree_views.save_node, name='save_node'), #保存节点 url(r'^upload_file/', filetree_views.upload_file, name='upload_file'), #节点上传文件 url(r'^upload_folder/', filetree_views.upload_folder, name='upload_folder'), #节点上传文件夹 url(r'^download_node/', filetree_views.download_node, name='download_node'), #下载节点 ] ``` #### 配置项目views.py ```bash vim blog/views.py ``` ```python # -*- coding: utf-8 -*- from __future__ import unicode_literals from django.shortcuts import render from django.conf import settings from datetime import datetime import random import base64 import os code_root_path=os.path.join(settings.BASE_DIR,'upload/pygame/code_folder_path') def demo(request): #获取加密的参数 code_folder_enc=request.get_signed_cookie("code_folder_enc",default='',salt='ynotes') if not code_folder_enc: #创建随机目录 datetime_str=datetime.now().strftime('%Y%m%d%H%M%S%f') random_str=random.random() code_folder="{0}__{1}".format(datetime_str,random_str) code_folder_enc=base64.b64encode(code_folder) code_folder_path=code_folder code_folder_abspath=os.path.join(code_root_path,code_folder_path) os.makedirs(code_folder_abspath) else: #解密参数值 code_folder=base64.b64decode(code_folder_enc) code_folder_path=code_folder response = render(request, 'blog/demo.html',{'code_folder_path': code_folder_path}) response.set_signed_cookie('code_folder_enc',code_folder_enc,salt='ynotes',max_age=864000) return response ``` #### 配置项目filetree_views.py ```bash vim blog/filetree_views.py ``` ```python # -*- coding: utf-8 -*- from __future__ import unicode_literals from django.http import HttpResponse from django.shortcuts import render from django.conf import settings import os import json import shutil import base64 import zipfile import random from datetime import datetime from binaryornot.check import is_binary code_relative_path='upload/pygame/code_folder_path' code_root_path=os.path.join(settings.BASE_DIR,code_relative_path) #zip压缩目录 def zipdir(path, ziph): for root, dirs, files in os.walk(path): for file in files: ziph.write(os.path.join(root, file)) def async_get_nodes(request): """异步加载节点""" code_folder_path=request.POST.get('codeFolderPath','')#获取代码的临时目录 code_folder_abspath=os.path.join(code_root_path,code_folder_path)#拼接临时目录绝对路径 treenode_path=request.POST.get('treeNodePath','') #获取选中节点的相对路径 get_nodes_dir=os.path.join(code_folder_abspath,treenode_path) #拼接选中节点的绝对路径 nodes_list=[] #如果code_folder_path参数为空或者绝对路径不存在返回空列表json if not code_folder_path or not os.path.exists(code_folder_abspath): return HttpResponse(json.dumps([])) #循环拼接ztree的数据 for i in os.listdir(get_nodes_dir): node_dict={} get_file=os.path.join(get_nodes_dir,i) inode=os.stat(get_file).st_ino node_dict['id']=inode node_dict['name']=i #通过文件后缀设置不同的zTree图标 if os.path.isdir(get_file): node_dict['isParent']='true' else: node_dict['isParent']='false' nodes_list.append(node_dict) return HttpResponse(json.dumps(nodes_list,ensure_ascii=False)) def rename_node(request): """重命名节点""" code_folder_path=request.POST.get('codeFolderPath','') code_folder_abspath=os.path.join(code_root_path,code_folder_path) treenode_path=request.POST.get('treeNodePath','') old_path_name=os.path.join(code_folder_abspath,treenode_path) new_name=request.POST.get('newName','') if not code_folder_abspath: return HttpResponse('{"result":"error","msg":"参数异常"}') if not new_name: return HttpResponse('{"result":"error","msg":"请输入文件名"}') new_path_name=os.path.join(os.path.dirname(os.path.join(code_folder_abspath,treenode_path)),new_name) try: if os.path.exists(new_path_name): return HttpResponse('{"result":"error","msg":"文件名已存在"}') os.rename(old_path_name,new_path_name) except: return HttpResponse('{"result":"error","msg":"重命名异常"}') return HttpResponse('{"result":"ok","msg":"重命名完成"}') def add_node(request): """添加节点""" code_folder_path=request.POST.get('codeFolderPath','') code_folder_abspath=os.path.join(code_root_path,code_folder_path) treenode_path=request.POST.get('treeNodePath','') file_type=request.POST.get('fileType','file') new_name=request.POST.get('newName','') if not code_folder_abspath: return HttpResponse('{"result":"error","msg":"参数异常"}') if not new_name: return HttpResponse('{"result":"error","msg":"请输入文件名"}') new_path_name=os.path.join(code_folder_abspath,treenode_path,new_name) try: if os.path.exists(new_path_name): return HttpResponse('{"result":"error","msg":"文件名已存在"}') if file_type == 'file': os.mknod(new_path_name,0644) else: os.mkdir(new_path_name) except Exception as e: return HttpResponse('{"result":"error","msg":"新建节点异常"}') return HttpResponse('{"result":"ok","msg":"新建节点完成"}') def remove_node(request): """移除节点""" code_folder_path=request.POST.get('codeFolderPath','') code_folder_abspath=os.path.join(code_root_path,code_folder_path) treenode_path=request.POST.get('treeNodePath','') name=request.POST.get('name','') if not code_folder_abspath: return HttpResponse('{"result":"error","msg":"参数异常"}') if not name: return HttpResponse('{"result":"error","msg":"节点名为空"}') path_name=os.path.join(code_folder_abspath,treenode_path,name) try: if not os.path.exists(path_name): return HttpResponse('{"result":"error","msg":"节点不存在"}') else: if os.path.isdir(path_name): shutil.rmtree(path_name) else: os.remove(path_name) except Exception as e: return HttpResponse('{"result":"error","msg":"删除节点异常"}') return HttpResponse('{"result":"ok","msg":"删除成功"}') def move_node(request): """移动节点""" code_folder_path=request.POST.get('codeFolderPath','') code_folder_abspath=os.path.join(code_root_path,code_folder_path) src_treenode_path=request.POST.get('srcTreeNodePath','') dst_treenode_path=request.POST.get('dstTreeNodePath','') src_path_name=os.path.join(code_folder_abspath,src_treenode_path) dst_path_name=os.path.join(code_folder_abspath,dst_treenode_path) if not code_folder_abspath: return HttpResponse('{"result":"error","msg":"参数异常"}') try: if not os.path.exists(src_path_name): return HttpResponse('{"result":"error","msg":"原节点不存在"}') if not os.path.exists(dst_path_name): return HttpResponse('{"result":"error","msg":"目的节点不存在"}') shutil.move(src_path_name,dst_path_name) except Exception as e: return HttpResponse('{"result":"error","msg":"移动节点异常"}') return HttpResponse('{"result":"ok","msg":"移动成功"}') def read_node(request): """读取节点""" code_folder_path=request.POST.get('codeFolderPath','') code_folder_abspath=os.path.join(code_root_path,code_folder_path) treenode_path=request.POST.get('treeNodePath','') path_name=os.path.join(code_folder_abspath,treenode_path) if not code_folder_abspath: return HttpResponse('{"result":"error","msg":"参数异常"}') if not os.path.exists(path_name): return HttpResponse('{"result":"error","msg":"文件不存在"}') print(path_name+"是否是二进制文件",is_binary(path_name)) if not is_binary(path_name): with open(path_name) as file_object: content=file_object.read() data={"result":"ok","msg":"读取完成","data":base64.b64encode(content)} else: data={"result":"error","msg":"非文本文件不能读取"} return HttpResponse(json.dumps(data)) def save_node(request): """保存节点,对非二进制文件节点进行保存""" code_folder_path=request.POST.get('codeFolderPath','') code_folder_abspath=os.path.join(code_root_path,code_folder_path) treenode_path=request.POST.get('treeNodePath','') encode_file_content=request.POST.get('encodedFileContent','') #获取base64编码的文本内容 path_name=os.path.join(code_folder_abspath,treenode_path) if not code_folder_abspath: return HttpResponse('{"result":"error","msg":"参数异常"}') if not os.path.exists(path_name): return HttpResponse('{"result":"error","msg":"文件不存在"}') if not is_binary(path_name): with open(path_name,'w') as file_object: if len(encode_file_content) > 0: file_object.write(base64.b64decode(encode_file_content)) else: file_object.write('') data={"result":"ok","msg":"保存完成"} else: data={"result":"error","msg":"非文本文件不能保存"} return HttpResponse(json.dumps(data)) def upload_file(request): """节点上传文件""" if request.method == 'POST': code_folder_path=request.POST.get('codeFolderPath','') code_folder_abspath=os.path.join(code_root_path,code_folder_path) treenode_path=request.POST.get('treeNodePath','') upload_file=request.FILES.get('uploadFile','') #上传的文件 if code_folder_path and upload_file: upload_file_abspath=os.path.join(code_folder_abspath,treenode_path,upload_file.name) #拼接上传文件后的绝对路径 if os.path.exists(upload_file_abspath) and os.path.isdir(upload_file_abspath): dict_data={"result":"error","msg":"上传失败,存在同名的目录"} else: with open(upload_file_abspath,'w') as f: f.write(upload_file.read()) if os.path.exists(upload_file_abspath): inode=os.stat(upload_file_abspath).st_ino dict_data={"result":"ok","msg":"上传成功","id":inode} else: dict_data={"result":"error","msg":"上传文件异常"} return HttpResponse(json.dumps(dict_data)) return HttpResponse(json.dumps({"result":"error","msg":"请求参数错误"})) return HttpResponse(json.dumps({"result":"error","msg":"请求方法不支持"})) def upload_folder(request): """节点上传文件夹""" if request.method == 'POST': code_folder_path=request.POST.get('codeFolderPath','') code_folder_abspath=os.path.join(code_root_path,code_folder_path) treenode_path=request.POST.get('treeNodePath','') upload_files=request.FILES.getlist('uploadFiles',[]) #获取上传文件列表 upload_file_paths=request.POST.getlist('uploadFilePaths',[]) #获取上传文件的相对路径列表 is_exist_upload_success=False dict_data={} #判断上传文件列表和上传文件的相对路径列表是否相等 if code_folder_path and upload_files and len(upload_files) == len(upload_file_paths): upload_files_dirpath=os.path.join(code_folder_abspath,treenode_path) upload_fail_files_dict={} for idx in range(0,len(upload_files)): #逐级判断是否存在同名的文件 dir_temp_path='' is_exist_same_file=False for d in os.path.dirname(upload_file_paths[idx]).split('/'): dir_temp_path=os.path.join(dir_temp_path,d) dir_temp_abspath=os.path.join(upload_files_dirpath,dir_temp_path) if os.path.exists(dir_temp_abspath) and not os.path.isdir(dir_temp_abspath): is_exist_same_file=True #需要创建的目录存在同名的文件的字典key是否存在 if dir_temp_path in upload_fail_files_dict: upload_fail_files_dict[dir_temp_path].append(upload_files[idx].name) else: upload_fail_files_dict[dir_temp_path]=[] upload_fail_files_dict[dir_temp_path].append(upload_files[idx].name) #如果存在同名的文件没必要往下判断 break #如果存在同名的文件则直接跳到下一个文件 if is_exist_same_file: continue #判断是否存在目录,不存在则创建 upload_file_dir_abspath=os.path.join(upload_files_dirpath,os.path.dirname(upload_file_paths[idx])) if not os.path.exists(upload_file_dir_abspath): os.makedirs(upload_file_dir_abspath) #把文件保存到相应的路径下 #判断上传的文件名是否存在同名的目录 upload_file_abspath=os.path.join(upload_file_dir_abspath,upload_files[idx].name) if os.path.exists(upload_file_abspath) and os.path.isdir(upload_file_abspath): if upload_file_abspath in upload_fail_files_dict: upload_fail_files_dict[upload_file_paths[idx]].append(upload_file_paths[idx]) else: upload_fail_files_dict[upload_file_paths[idx]]=[] upload_fail_files_dict[upload_file_paths[idx]].append(upload_file_paths[idx]) continue with open(upload_file_abspath,'w') as f: f.write(upload_files[idx].read()) is_exist_upload_success=True fail_msg="" if upload_fail_files_dict: for d in upload_fail_files_dict.keys(): for f in upload_fail_files_dict[d]: if f == d: fail_msg+="已存在同名的目录{0},导致文件{1}上传失败.\n".format(d,f) else: fail_msg+="已存在同名的文件{0},创建目录{0}失败,导致该目录下的文件{1}上传失败.\n".format(d,f) if fail_msg: if is_exist_upload_success: dict_data["msg"]="警告:\n"+fail_msg dict_data["result"]="error" else: dict_data["msg"]="警告:\n"+fail_msg dict_data["result"]="error" else: dict_data["msg"]="上传文件夹成功" dict_data["result"]="ok" ##获取父目录的id if is_exist_upload_success: inode=os.stat(os.path.join(upload_files_dirpath,upload_file_paths[0].split('/')[0])).st_ino dict_data["id"]=inode return HttpResponse(json.dumps(dict_data)) return HttpResponse(json.dumps({"result":"error","msg":"请求参数错误"})) return HttpResponse(json.dumps({"result":"error","msg":"请求方法不支持"})) def download_node(request): """下载节点""" if request.method == 'POST': code_folder_path=request.POST.get('codeFolderPath','') code_folder_abspath=os.path.join(code_root_path,code_folder_path) treenode_path=request.POST.get('treeNodePath','') if code_folder_path: download_file=os.path.join(code_folder_abspath,treenode_path) dict_data={} if os.path.exists(download_file): if os.path.isdir(download_file): #生成一个压缩包目录 datetime_str=datetime.now().strftime('%Y%m%d%H%M%S%f') random_str=random.random() zip_folder="{0}__{1}".format(datetime_str,random_str) zip_folder_abspath=os.path.join(code_root_path,'zip',zip_folder) os.makedirs(zip_folder_abspath) #生成压缩包 cwd=os.getcwd() try: zip_filename = "%s" % os.path.basename(download_file) if treenode_path else "All" workspace_dir=os.path.dirname(download_file) if treenode_path else code_root_path os.chdir(workspace_dir) zip_dirname=os.path.basename(download_file) if treenode_path else code_folder_path shutil.make_archive(os.path.join(zip_folder_abspath,zip_filename), 'zip',zip_dirname) os.chdir(cwd) except Exception as e: os.chdir(cwd) #返回zip的下载链接 url=os.path.join(code_relative_path,'zip',zip_folder,zip_filename+'.zip') filename=zip_filename dict_data={"result":"ok","msg":"文件压缩包下载链接获取成功","url":url,"filename":filename} else: url=os.path.join(code_relative_path,code_folder_path,treenode_path) filename=os.path.basename(treenode_path) dict_data={"result":"ok","msg":"文件下载链接获取成功","url":url,"filename":filename} return HttpResponse(json.dumps(dict_data)) return HttpResponse(json.dumps({"result":"error","msg":"文件不存在"})) return HttpResponse(json.dumps({"result":"error","msg":"请求参数错误"})) return HttpResponse(json.dumps({"result":"error","msg":"请求方法不支持"})) ``` #### 配置django模板 创建模板目录 ```bash mkdir blog/templates/blog -p ``` 创建基础模板 ```bash vim blog/templates/blog/base.html ``` ##### 配置base.html ```html <!DOCTYPE html> {% load staticfiles %} <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <!--<meta name="viewport" content="width=device-width, initial-scale=1.0">--> <meta name='viewport' content='width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0' /> <meta name='apple-mobile-web-app-capable' content='yes' /> <title>Ztree demo</title> {% block css %} <link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"> {% endblock %} {% block javascript %} <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <script src="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script> {% endblock %} </head> <body> {% block body %} {% endblock %} {% block extra_javascript %} {% endblock %} </body> </html> ``` ```bash vim blog/templates/blog/demo.html ``` ```html {% extends 'blog/base.html' %} {% load staticfiles %} {% block css %} {{ block.super }} <link rel="stylesheet" href="{% static 'css/ztree/zTreeStyle.css' %}" type="text/css"> <style> div#rMenu {position:absolute; visibility:hidden; top:0; text-align: left;padding: 1px;} div#rMenu ul li{ margin: 0px 0; padding: 0 5px; cursor: pointer; list-style: none outside none; font-weight:bold; background-color: #90CAF9; font-family:"Microsoft YaHei"; } //设置ztree显示样式 .highlight_red {color:#A60000;} .highlight_green {color:#A7F43D;} //li {list-style: circle;font-size: 12px;} li.title {list-style: none;} ul.list {margin-left: 17px;} div.content_wrap {width: 600px;height:380px;} div.content_wrap div.left{float: left;width: 250px;} div.content_wrap div.right{float: right;width: 340px;} div.zTreeDemoBackground {width:250px;height:362px;text-align:left;} ul.ztree {margin-top: 0px;border: 1px solid #617775;background: #f0f6e4;width:220px;height:360px;overflow-y:scroll;overflow-x:auto;} ul.ztree {margin-top: 0px;border: 1px solid #617775;background: #f0f6e4;overflow-y:scroll;overflow-x:auto;} ul.log {border: 1px solid #617775;background: #f0f6e4;width:300px;height:170px;overflow: hidden;} ul.log.small {height:45px;} ul.log li {color: #666666;list-style: none;padding-left: 10px;} ul.log li.dark {background-color: #E3E3E3;} /* ruler */ div.ruler {height:20px; width:220px; background-color:#f0f6e4;border: 1px solid #333; margin-bottom: 5px; cursor: pointer} div.ruler div.cursor {height:20px; width:30px; background-color:#3C6E31; color:white; text-align: right; padding-right: 5px; cursor: pointer} .ztree li span.button.add {margin-left:2px; margin-right: -1px; background-position:-144px 0; vertical-align:top; *vertical-align:middle} [hidden] { display: none !important; } //配置图片div的显示区域 #yourimg_div {width:800px;height:600px;} </style> {% endblock %} {% block javascript %} {{ block.super }} <script type="text/javascript" src="{% static 'js/ztree/jquery.ztree.core.min.js' %}"></script> <script type="text/javascript" src="{% static 'js/ztree/jquery.ztree.exedit.min.js' %}"></script> <script type="text/javascript" src="{% static 'js/ztree/custom-ztree.js' %}"></script> {% endblock %} {% block body %} <div class="container-fluid"> <div class="row"> <div class="col-md-2"> <label class="btn btn-success btn-sm"> 上传文件<input type="file" id="uploadFile" value="uploadFile" hidden> </label> <label class="btn btn-success btn-sm"> 上传目录<input type="file" id="uploadFolder" value="uploadFolder" webkitdirectory directory multiple hidden> </label> <label class="btn btn-success btn-sm" id="downloadFile"> 下载 </label> </div> <div class="col-md-2"> <div id="notice"> </div> </div> </div> <div class="row"> <div class="col-md-2" id="treeDemo-parent-col"> <div id="filetree" class="content_wrap" style="width:100%;"> <div class="zTreeDemoBackground left" style="width:100%;"> <ul id="treeDemo" class="ztree" style="width:100%;"></ul> </div> </div> </div> <div class="col-md-5"> <div id="yourcode_div"> </div> <div id="yourimg_div" style="display:none;width:800px;height:600px;" > </div> <button class="btn btn-primary" id="saveCode" type="button">保存</button> </div> </div> </div> {% csrf_token %} <input id="codeFolderPath" value="{{code_folder_path}}" type="hidden"> <div id="rMenu"> <ul style="padding-left:0px;"> <li id="m_add_file" onclick="addTreeNode('file');">增加文件</li> <li id="m_add_folder" onclick="addTreeNode('folder');">增加目录</li> <li id="m_rename" onclick="renameTreeNode();">重命名</li> <li id="m_del" onclick="removeTreeNode();">删除</li> </ul> </div> {% endblock %} {% block extra_javascript %} <script type="text/javascript"> $(document).ready(function(){ //ztree $.fn.zTree.init($("#treeDemo"), setting); zTree = $.fn.zTree.getZTreeObj("treeDemo"); rMenu = $("#rMenu"); $("#uploadFile").bind("change",uploadFile); $("#uploadFile").bind("click",beforeUploadFile); $("#uploadFolder").bind("change",uploadFolder); $("#uploadFolder").bind("click",beforeUploadFolder); $("#downloadFile").bind("click",downloadFile); $("#saveCode").bind("click",saveNode); }); </script> {% endblock %} ``` #### 创建ztree目录 ```bash mkdir myproject/static/{js,css}/ztree/ -p ``` #### 下载zTree ztree.core(核心模块):https://gitee.com/zTree/zTree_v3/blob/master/js/jquery.ztree.core.min.js ztree.exedit(exedit扩展模块):https://gitee.com/zTree/zTree_v3/blob/master/js/jquery.ztree.exedit.min.js ztree css:https://gitee.com/zTree/zTree_v3/blob/master/css/zTreeStyle/zTreeStyle.css ztree 图片:https://gitee.com/zTree/zTree_v3/tree/master/css/zTreeStyle/img js文件放到 `myproject/static/js/ztree`目录 css文件放到 `myproject/static/css/ztree`目录 img文件夹放到 `myproject/static/css/ztree`目录 #### 创建自定义js文件 ```bash vim myproject/static/js/ztree/custom-ztree.js ``` ```javascript var setting = { async: { enable: true, url: '/blog/async_get_nodes/', otherParam: getOtherParam, }, view: { selectedMulti: false, showLine: false, expandSpeed: "slow" }, edit: { drag:{ isCopy: false, isMove: true, prev: true, next: true, inner: true }, enable: true, editNameSelectAll: true, showRemoveBtn: false, showRenameBtn: false, }, data: { keep: { leaf: true, parent: true }, simpleData: { enable: true } }, callback: { beforeExpand: beforeExpand, beforeDrag: beforeDrag, beforeDrop: beforeDrop, beforeClick: beforeClick, beforeRename: beforeRename, onRightClick: OnRightClick, onDblClick: OnDblClick, onClick: OnClick } }; function showNotice(type,msg) { if(type=="ok"){ $('#notice').html('<span class="glyphicon glyphicon-ok-sign"></span> '+msg); $('#notice').css('color', 'green'); }else if(type=="error"){ $('#notice').html('<span class="glyphicon glyphicon-remove-sign"></span> '+msg); $('#notice').css('color', 'red'); }else if(type=="hidden"){ $('#notice').html(''); } } function getOtherParam(treeId, treeNode){ var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); var treeNodePath=getTreeNodePath(treeNode); return {'codeFolderPath':codeFolderPath,'treeNodePath':treeNodePath,'csrfmiddlewaretoken':token} } function getTreeNodePath(treeNode){ if(treeNode == null){ return ''; } var ParentNode=treeNode.getParentNode(); if(ParentNode==null){ return treeNode.name; } return getTreeNodePath(ParentNode)+"/"+treeNode.name; } function beforeExpand(treeId, treeNode) { if (!treeNode.isAjaxing) { startTime = new Date(); treeNode.times = 1; ajaxGetNodes(treeNode, "refresh"); return true; } else { showNotice("ok","加载数据中,请稍后..."); return false; } } function ajaxGetNodes(treeNode, reloadType) { var zTree = $.fn.zTree.getZTreeObj("treeDemo"); showNotice("ok","数据加载中..."); if (reloadType == "refresh") { zTree.updateNode(treeNode); } zTree.reAsyncChildNodes(treeNode, reloadType, true,function(){ showNotice("ok","数据加载完成"); }); } function beforeDrag(treeId, treeNodes) { for (var i=0,l=treeNodes.length; i<l; i++) { if (treeNodes[i].drag === false) { return false; } } return true; } function beforeDrop(treeId, treeNodes, targetNode, moveType) { ajaxDragNode(treeNodes[0],targetNode); setTimeout(function(){ zTree.reAsyncChildNodes(targetNode, "refresh", false); },100); return targetNode ? targetNode.drop !== false : true; } function ajaxDragNode(treeNode,targetNode){ var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); var srcTreeNodePath=getTreeNodePath(treeNode); var dstTreeNodePath=getTreeNodePath(targetNode); $.post('/blog/move_node/', { codeFolderPath: codeFolderPath, srcTreeNodePath: srcTreeNodePath, dstTreeNodePath: dstTreeNodePath, csrfmiddlewaretoken: token}, function(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.hasOwnProperty('result')){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); console.log(obj.msg); if(obj.result == "ok"){ showNotice("ok",'节点移动完成'); return true; } showNotice('error','节点移动失败'); return false; } }).fail(function(){ showNotice('error','节点移动异常,请联系管理员'); return false; }); } function OnDblClick(event, treeId, treeNode) { var zTree = $.fn.zTree.getZTreeObj("treeDemo"); zTree.cancelSelectedNode(); showNotice("ok",'已选中根目录'); } function OnClick(event, treeId, treeNode) { var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = zTree.getSelectedNodes(); var SelectedTreeNode = nodes[0]; if(SelectedTreeNode.isParent){ showNotice("ok",'已选中目录'); } } function OnRightClick(event, treeId, treeNode) { if (!treeNode && event.target.tagName.toLowerCase() != "button" && $(event.target).parents("a").length == 0) { zTree.cancelSelectedNode(); showRMenu("root", event.clientX, event.clientY); } else if (treeNode && !treeNode.noR) { zTree.selectNode(treeNode); if(treeNode.isParent){ showRMenu("folder", event.clientX, event.clientY); }else{ showRMenu("file", event.clientX, event.clientY); } } } function showRMenu(type, x, y) { $("#rMenu ul").show(); if (type=="file") { $("#m_del").show(); $("#m_edit").show(); $("#m_rename").show(); $("#m_add_folder").hide(); $("#m_add_file").hide(); } else if (type=="folder") { $("#m_del").show(); $("#m_edit").hide(); $("#m_rename").show(); $("#m_add_file").show(); $("#m_add_folder").show(); }else{ $("#m_del").hide(); $("#m_edit").hide(); $("#m_rename").hide(); $("#m_add_file").show(); $("#m_add_folder").show(); } y += document.body.scrollTop; x += document.body.scrollLeft; rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"}); $("body").bind("mousedown", onBodyMouseDown); } function hideRMenu() { if (rMenu) rMenu.css({"visibility": "hidden"}); $("body").unbind("mousedown", onBodyMouseDown); } function onBodyMouseDown(event){ if (!(event.target.id == "rMenu" || $(event.target).parents("#rMenu").length>0)) { rMenu.css({"visibility" : "hidden"}); } } function addTreeNode(fileType) { hideRMenu(); var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = zTree.getSelectedNodes(); var SelectedTreeNode = nodes[0]; zTree.reAsyncChildNodes(SelectedTreeNode, "refresh", false,function(){ handlerAddTreeNode(fileType); }); } function handlerAddTreeNode(fileType){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = zTree.getSelectedNodes(); var SelectedTreeNode = nodes[0]; var isParent = false; var tempName = "NewFile"; if(fileType == "folder"){ isParent = true; tempName = "NewFolder"; } tempName=MakeUniqueNodeName(SelectedTreeNode,fileType); if (SelectedTreeNode) { NewTreeNode = zTree.addNodes(SelectedTreeNode, {name: tempName, isParent: isParent}); } else { NewTreeNode = zTree.addNodes(null, {name: tempName, isParent: isParent} ); } ajaxAddNode(SelectedTreeNode,fileType,NewTreeNode[0].name); zTree.editName(NewTreeNode[0]); } function filter(Node){ return Node.level == 0; } function filterChildNode(Node){ return true; } function nameExistNode(NodeList,Name){ for(i=0;i<NodeList.length;i++){ if(NodeList[i].name == Name){ return true; } } return false; } function MakeUniqueNodeName(TreeNode,fileType){ var tempName = "NewFile"; if(fileType == "folder"){ tempName = "NewFolder"; } var zTree = $.fn.zTree.getZTreeObj("treeDemo"); if(TreeNode){ var NodesList=zTree.getNodesByFilter(filterChildNode,false,TreeNode); }else{ var NodesList=zTree.getNodesByFilter(filter); } var counter=1; var NewTempName=tempName; while(nameExistNode(NodesList,NewTempName)){ NewTempName=tempName+counter; counter++; } return NewTempName; } function ajaxAddNode(treeNode,fileType,newName){ var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); var treeNodePath=getTreeNodePath(treeNode); $.post('/blog/add_node/', { codeFolderPath: codeFolderPath, treeNodePath: treeNodePath, newName: newName ,fileType: fileType, csrfmiddlewaretoken: token}, function(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.hasOwnProperty('result')){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); console.log(obj.msg); if(obj.result == "ok"){ showNotice("ok","新建节点完成"); return true; } showNotice("error","新建节点请求失败"); return false; } }).fail(function(){ showNotice("error","新建节点请求异常,请联系管理员"); return false; }); } function removeTreeNode() { hideRMenu(); var nodes = zTree.getSelectedNodes(); if (nodes && nodes.length>0) { if (nodes[0].children && nodes[0].children.length > 0) { var msg = "要删除的节点是父节点,如果删除将连同子节点一起删掉。\n\n请确认!"; if (confirm(msg)==true){ ajaxRemoveNode(nodes[0].getParentNode(),nodes[0].name); zTree.reAsyncChildNodes(nodes[0],"refresh",false); } } else { ajaxRemoveNode(nodes[0].getParentNode(),nodes[0].name); zTree.reAsyncChildNodes(nodes[0],"refresh",false); } } } function ajaxRemoveNode(treeNode,name){ var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); var treeNodePath=getTreeNodePath(treeNode); $.post('/blog/remove_node/', { codeFolderPath: codeFolderPath, treeNodePath: treeNodePath, name: name , csrfmiddlewaretoken: token}, function(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.hasOwnProperty('result')){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); zTree.reAsyncChildNodes(treeNode,"refresh",false); console.log(obj.msg); if(obj.result == "ok"){ showNotice("ok","删除节点完成"); return true; } showNotice("error","删除节点失败"); return false; } }).fail(function(){ showNotice("error","删除异常异常,请联系管理员"); return false; }); } function beforeRename(treeId, treeNode, newName) { if (newName.length == 0) { showNotice("error","节点名称不能为空"); var zTree = $.fn.zTree.getZTreeObj("treeDemo"); setTimeout(function(){zTree.editName(treeNode)}, 10); return false; } ajaxRenameNode(treeNode,newName); return true; } function ajaxRenameNode(treeNode,newName){ var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); var treeNodePath=getTreeNodePath(treeNode); if(treeNode.name == newName ){ return true; } $.post('/blog/rename_node/', { codeFolderPath: codeFolderPath, treeNodePath: treeNodePath, newName: newName , csrfmiddlewaretoken: token}, function(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.hasOwnProperty('result')){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); zTree.reAsyncChildNodes(treeNode.getParentNode(),"refresh",false); if(obj.result == "ok"){ showNotice("ok",'重命名节点完成'); return true; } showNotice('error','重命名节点失败'); return false; } }).fail(function(){ showNotice('error','重命名节点异常,请联系管理员'); return false; }); } function renameTreeNode() { hideRMenu(); var zTree = $.fn.zTree.getZTreeObj("treeDemo"), nodes = zTree.getSelectedNodes(), treeNode = nodes[0]; if (nodes.length == 0) { showNotice("error","请先选择一个节点"); return; } zTree.editName(treeNode); }; function utf8_to_b64(str) { return window.btoa(unescape(encodeURIComponent( str ))); } function b64_to_utf8(str) { return decodeURIComponent(escape(window.atob( str ))); } function isImage(filename) { var flag = false; var arr = ["jpg","png","gif"]; var index = filename.lastIndexOf("."); var ext = filename.substr(index+1); for(var i=0;i<arr.length;i++) { if(ext == arr[i]) { flag = true; break; } } return flag; } function beforeClick(treeId, treeNode,clickFlag) { if(!treeNode.isParent){ if(isImage(treeNode.name)){ var codeFolderPath=$("#codeFolderPath").val(); var treeNodePath=getTreeNodePath(treeNode); var codeRootPath='upload/pygame/code_folder_path'; var image_url='/'+codeRootPath+'/'+codeFolderPath+'/'+treeNodePath; var elem = document.createElement("img"); elem.setAttribute("src", image_url); elem.setAttribute("style", "margin:auto;"); elem.setAttribute("alt", "Flower"); document.getElementById("yourcode_div").style.display="none"; document.getElementById("yourimg_div").innerHTML = ""; document.getElementById("yourimg_div").style.background="url("+image_url+") center no-repeat"; document.getElementById("yourimg_div").style.display=""; showNotice("ok","图片加载完成"); }else{ document.getElementById("yourcode_div").style.display=""; document.getElementById("yourimg_div").style.display="none"; ajaxReadNode(treeNode); } } return true; } function ajaxReadNode(treeNode){ var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); var treeNodePath=getTreeNodePath(treeNode); $.post('/blog/read_node/', { codeFolderPath: codeFolderPath, treeNodePath: treeNodePath, csrfmiddlewaretoken: token}, function(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.hasOwnProperty('result')){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); if(obj.result == "ok"){ var base64_enc_content=obj.data; if(base64_enc_content == ""){ $('#yourcode_div').text(""); }else{ $('#yourcode_div').text(b64_to_utf8(base64_enc_content)); } showNotice("ok","读取节点完成"); return true; } $('#yourcode_div').text(""); showNotice("error","读取节点失败,二进制格式无法读取"); return false; } }).fail(function(){ showNotice("error","读取节点异常,请联系管理员"); return false; }); } function saveNode(){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = zTree.getSelectedNodes(); var SelectedTreeNode = nodes[0]; if(nodes[0].isParent){ return; } ajaxsaveNode(SelectedTreeNode); } function ajaxsaveNode(treeNode){ var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); var treeNodePath=getTreeNodePath(treeNode); var yourcode=$('#yourcode_div').text(); var encodedFileContent=utf8_to_b64(yourcode); $.post('/blog/save_node/', { codeFolderPath: codeFolderPath, treeNodePath: treeNodePath,encodedFileContent:encodedFileContent, csrfmiddlewaretoken: token}, function(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.hasOwnProperty('result')){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); if(obj.result == "ok"){ showNotice("ok","节点保存完成"); return true; } showNotice("error","节点保存失败"); return false; } }).fail(function(){ showNotice("error","节点保存异常,请联系管理员"); return false; }); } function uploadFile(){ console.log('uploadFile'); var FileMaxSize=10*1024*1024; var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = zTree.getSelectedNodes(); zTree.reAsyncChildNodes(nodes[0], "refresh", false); if(nodes.length != 0 && !nodes[0].isParent){ showNotice("error","请选择目录"); return; } var treeNodePath=getTreeNodePath(nodes[0]); var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); var uploadFile=$('#uploadFile')[0].files[0]; if ( uploadFile.size > FileMaxSize){ showNotice('error','上传文件最大限制为'+(FileMaxSize/1024/1024).toFixed(2)+'M,'+uploadFile.name+':'+(uploadFile.size/1024/1024).toFixed(2)+'M'); return false; } var formData=new FormData(); formData.append("codeFolderPath",codeFolderPath); formData.append("treeNodePath",treeNodePath); formData.append("csrfmiddlewaretoken",token); formData.append("uploadFile",uploadFile); $.ajax({ url: '/blog/upload_file/', data: formData, type: 'POST', contentType: false, processData: false, success(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.result == "ok"){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = zTree.getSelectedNodes(); zTree.reAsyncChildNodes(nodes[0], "refresh", false,function(){ showNotice("ok",'文件上传完成'); var upload_node = zTree.getNodeByParam("id",obj.id,nodes[0]); zTree.selectNode(upload_node); ajaxReadNode(upload_node); }); }else{ showNotice('error',obj.msg); } document.getElementById("uploadFile").value = ""; }, error(err){ showNotice('error','文件上传失败'); console.log(err); } }); } function downloadFile(){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = zTree.getSelectedNodes(); var treeNodePath=getTreeNodePath(nodes[0]); var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); $.post('/blog/download_node/', { codeFolderPath: codeFolderPath, treeNodePath: treeNodePath, csrfmiddlewaretoken: token}, function(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.hasOwnProperty('result')){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); if(obj.result == "ok"){ showNotice("ok","下载链接获取成功"); var a = document.createElement('a'); var url = obj.url; a.href = window.location.protocol+'//'+window.location.host+'/'+url; a.download = obj.filename; a.click(); return true; } showNotice("error","下载节点失败"); return false; } }).fail(function(){ showNotice("error","下载节点异常,请联系管理员"); return false; }); } function uploadFolder(){ var FolderMaxNumber=1000; var FolderMaxSize=50*1024*1024; var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = zTree.getSelectedNodes(); zTree.reAsyncChildNodes(nodes[0], "refresh", false); if(nodes.length != 0 && !nodes[0].isParent){ showNotice("error","请选择目录"); return; } var treeNodePath=getTreeNodePath(nodes[0]); var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); var uploadFiles=$('#uploadFolder')[0].files; if(uploadFiles.length>FolderMaxNumber){ showNotice('error','上传文件夹包含的文件数限制为'+FolderMaxNumber+',当前文件数:'+uploadFiles.length); return; } var uploadFilesTotalSize=0; for(var i=0;i<uploadFiles.length;i++){ uploadFilesTotalSize+=uploadFiles[i].size } if (uploadFilesTotalSize>FolderMaxSize){ showNotice('error','上传文件夹包含的文件总大小为'+(FolderMaxSize/1024/1024).toFixed(2)+'M,当前文件总大小:'+(uploadFilesTotalSize/1024/1024).toFixed(2)+'M'); return; } var formData=new FormData(); for(var i=0;i<uploadFiles.length;i++){ formData.append("uploadFiles",uploadFiles[i]); formData.append("uploadFilePaths",uploadFiles[i]['webkitRelativePath']); } formData.append("codeFolderPath",codeFolderPath); formData.append("treeNodePath",treeNodePath); formData.append("csrfmiddlewaretoken",token); $.ajax({ url: '/blog/upload_folder/', data: formData, type: 'POST', contentType: false, processData: false, success(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.hasOwnProperty('id')){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var upload_node = zTree.getNodeByParam("id",obj.id,nodes[0]); if(obj.result == "ok"){ setTimeout(function(){ zTree.reAsyncChildNodes(upload_node, "refresh", false,function(){ zTree.selectNode(upload_node); showNotice("ok",obj.msg); }); },100); }else{ setTimeout(function(){ zTree.reAsyncChildNodes(upload_node, "refresh", false,function(){ zTree.selectNode(upload_node); showNotice('error',"警告,部分文件上传失败!"); setTimeout(function(){ alert(obj.msg); },300); }); },100); } }else{ showNotice('error',"警告,所有文件上传失败!"); setTimeout(function(){ alert(obj.msg); },300); } document.getElementById("uploadFolder").value = ""; }, error(err){ showNotice('error','警告,文件夹上传失败'); document.getElementById("uploadFolder").value = ""; } }); } function beforeUploadIsReady(){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = zTree.getSelectedNodes(); if(nodes.length != 0 && !nodes[0].isParent){ showNotice("error","请选择目录上传,选择根目录请双击空白处"); return false; } return true; } function beforeUploadFile(){ if(!beforeUploadIsReady()){ event.preventDefault(); } } function beforeUploadFolder(){ if(!beforeUploadIsReady()){ event.preventDefault(); } } ``` ### demo ![avatar](https://files.ynotes.cn/ztree_demo.png) ### 实例 https://ynotes.cn/blog/pygame_v2/
阅读 1571 评论 0 收藏 0
阅读 1571
评论 0
收藏 0


第 1 页 / 共 2 页