首页 文章 精选 留言 我的

精选列表

搜索[开发者常备桌面小工具],共10002篇文章
优秀的个人博客,低调大师

【python】openstack管理小工具(增删查改,批量ping)

工具介绍: 创建虚拟机会使用多线程队列的方式去控制同时创建的虚拟机数量 如果在创建的虚拟机比较多,会2次创建。第一次会在各个宿主机上创建一台,其余的会再第一次创建成功后创建。 因为第一次创建需要传输镜像会比较慢,在宿主机上已经有对应的镜像后,再次创建相同镜像的虚拟机,不再需要传输相应的镜像(所有相同镜像的虚拟机共用同一个后端镜像,openstack对镜像管理使用copy on write技术) 查询和更改主要都是通过查询数据库实现。 其它功能自测。 使用帮助: python openstack_manager.py --help 创建虚拟机: python openstack_manager.py -c -f vmhost -t nslm -c, 指定是要进行创建虚拟机 , -f 指定要创建的虚拟机列表文件 , -t 指定要给那个项目创建虚拟机 删除虚拟机: python openstack_manager.py -d ip1 ip2 -d 指定要删除的ip ,可以跟多个 查看某个虚拟机信息 python openstack_manager.py -i ip1 ip2 -i 指定ip,后面可以跟上多个值 查看宿主机下的虚拟机信息 python openstack_manager.py -n node1 node2 -n 指定node,可以跟上多个值 更改虚拟机配置: python openstack_manager.py -u ip -F flavor -u指定某个ip, -F 指定某个配置 批量ping: python openstack_manager.py -p -f vmhost -p指定进行批量ping,-f指定文件 从文件中获取创建虚拟机的命令 (创建过程中某个虚拟机出错,可以进行查看创建虚拟机命令进行手动创建) python openstack_manager.py -g -f vmhost -t -g指定要进行获取命令,-f指定文件,-t指定项目 -f 指定文件的文件格式: 批量ping是检查ip虚拟机是否存在,指定的文件也是参照此格式 nodename instance_ip flavor 一行一个虚拟机 其它使用信息: 脚本中的flavor对应的flavor id,项目帐号密码和镜像ID,数据库帐号等替换成自己生产环境中的信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 #!/usr/bin/python #coding:utf8 #Auth by lust import sys,commands,os,MySQLdb import Queue, threading,time from fabric.colors import * from optparse import OptionParser class vmListAnaly(): def __init__( self ,vmlist_file): self .f_handle = open (vmlist_file) self .vmlist = {} while True : self .file_tmp = self .f_handle.readline() if self .file_tmp: if self .vmlist.has_key( self .file_tmp.split()[ 0 ]): self .vmlist[ self .file_tmp.split()[ 0 ]].append(( self .file_tmp.split()[ 1 ], self .file_tmp.split()[ 2 ])) else : self .vmlist[ self .file_tmp.split()[ 0 ]] = [] self .vmlist[ self .file_tmp.split()[ 0 ]].append(( self .file_tmp.split()[ 1 ], self .file_tmp.split()[ 2 ])) else : break def getAllIps( self ): ips = [] for __host in self .vmlist: for i in self .vmlist[__host]: ips.append(i[ 0 ]) return ips def getFirstIps( self ): ips = [] for __host in self .vmlist: ips.append( self .vmlist[__host][ 0 ][ 0 ]) return ips def getSecondIps( self ): ips = [] for __host in self .vmlist: del self .vmlist[__host][ 0 ] for __host in self .vmlist: for i in self .vmlist[__host]: ips.append(i[ 0 ]) return ips def getAllCreInfo( self ): return self .vmlist def getFirstCreInfo( self ): results = [] for __host in self .vmlist: self .vmlist[__host] = [ self .vmlist[__host][ 0 ]] return self .vmlist def getSecondCreInfo( self ): for __host in self .vmlist: del self .vmlist[__host][ 0 ] return self .vmlist def __del__( self ): self .f_handle.close() class getCmd(): def __init__( self ,project,vminfo,flavor,mysql_host,mysql_user,mysql_passwd): self .mysql_user = mysql_user self .mysql_host = mysql_host self .mysql_passwd = mysql_passwd self .project = project self .vminfo = vminfo self .flavor = flavor self .create_cmd = [] def get_cmd( self ): quantum_obj = mysqlEng(host = self .mysql_host,user = self .mysql_user,passwd = self .mysql_passwd) for __host in self .vminfo: if self .vminfo[__host]: for i in self .vminfo[__host]: __ip = i[ 0 ] __flavor = i[ 1 ] __network_id = quantum_obj.getNetworkID(__ip) __tmp = "nova --os-username %s --os-password %s --os-tenant-name %s boot --security-groups %s --flavor %s --image %s --nic net-id=%s,v4-fixed-ip=%s --meta lan=%s --availability-zone nova:%s %s" % ( self .project[ 'OS_USERNAME' ], self .project[ 'OS_PASSWORD' ], self .project[ 'OS_TENANT_NAME' ], self .project[ 'SECURITY_GROUPS' ],flavor[__flavor], self .project[ 'IMAGE_ID' ],__network_id,__ip,__ip,__host,__ip) self .create_cmd.append(__tmp) return self .create_cmd class mysqlEng(): def __init__( self ,host = '127.0.0.1' ,user = 'root' ,passwd = ' ',port=' 3306 '): self .conn = MySQLdb.connect(host = host,user = user,passwd = passwd,port = 3306 ) self .cur = self .conn.cursor() def getNetworkID( self ,ip): self .conn.select_db( 'quantum' ) net = "%s.%s.%s.0/24" % (ip.split( '.' )[ 0 ],ip.split( '.' )[ 1 ],ip.split( '.' )[ 2 ]) __sql = "select network_id from subnets where name='%s'" % net self .cur.execute(__sql) ret = self .cur.fetchall() return ret[ 0 ][ 0 ] def getVmstate( self ,ip): self .conn.select_db( 'nova' ) __sql = "select instances.uuid,instance_metadata.value,instances.host,instances.vm_state from instance_metadata,instances where instances.uuid = instance_metadata.instance_uuid and instance_metadata.value = %s and instances.vm_state not in ('error','deleted');" self .cur.execute(__sql,ip) ret = self .cur.fetchall() return ret[ 0 ][ 3 ] def getActNum( self ,ips): self .conn.select_db( 'nova' ) __sql = "select count(*) from instance_metadata,instances where instances.uuid = instance_metadata.instance_uuid and instance_metadata.value in ('%s') and instances.vm_state = 'active';" % "','" .join(ips) self .cur.execute(__sql) ret = self .cur.fetchall() return ret[ 0 ][ 0 ] def updateInstanceFlavor( self ,ip,flavor): self .conn.select_db( 'nova' ) __get_flavor_info_sql = "select id,memory_mb,vcpus from instance_types where flavorid = '%s' and deleted = 0;" % flavor self .cur.execute(__get_flavor_info_sql) __flavor_info = self .cur.fetchall() __flavor_id = __flavor_info[ 0 ][ 0 ] __mem = __flavor_info[ 0 ][ 1 ] __vcpus = __flavor_info[ 0 ][ 2 ] __update_flavor_info_sql = "update instances set instance_type_id='%d',vcpus='%d',memory_mb='%d' where hostname='%s' AND vm_state != 'deleted' AND vm_state != 'error';" % (__flavor_id,__vcpus,__mem,ip) self .cur.execute(__update_flavor_info_sql) self .conn.commit() def getInstanceFlavor( self ,ip): self .conn.select_db( 'nova' ) __get_instance_flavor_sql = "select vcpus,memory_mb from instances where hostname='%s' AND vm_state != 'deleted' AND vm_state != 'error';" % ip self .cur.execute(__get_instance_flavor_sql) ret = self .cur.fetchall() results = (ip,ret[ 0 ][ 0 ],ret[ 0 ][ 1 ]) return results def getInstanceInfo( self ,ips): self .conn.select_db( 'nova' ) __get_instance_info_sql = "select instances.uuid,instance_metadata.value,instances.host,instances.vm_state,memory_mb from instance_metadata,instances where instances.uuid = instance_metadata.instance_uuid and instance_metadata.value in ('%s') and instances.vm_state !='error' and instances.vm_state != 'deleted';" % "','" .join(ips) self .cur.execute(__get_instance_info_sql) ret = self .cur.fetchall() return ret def getNodeInfo( self ,nodes): self .conn.select_db( 'nova' ) __get_node_info_sql = "select instances.uuid,instance_metadata.value,instances.host,instances.vm_state,memory_mb from instance_metadata,instances where instances.uuid = instance_metadata.instance_uuid and instances.host in ('%s') and instances.vm_state !='error' and instances.vm_state != 'deleted';" % "','" .join(nodes) self .cur.execute(__get_node_info_sql) ret = self .cur.fetchall() return ret def __del__( self ): self .cur.close() self .conn.close() class checkIps(): def __init__( self ): self .ping_queue = Queue.Queue() self .up_ip = [] self .down_ip = [] def __check( self ,q): while q.qsize(): __ip = q.get() __cmd = "ping -c 2 %s" % __ip if commands.getstatusoutput(__cmd)[ 0 ] = = 0 : self .up_ip.append(__ip) else : self .down_ip.append(__ip) q.task_done() def put2q( self ,ips): for __ip in ips: self .ping_queue.put(__ip) def checking( self ): print yellow( 'Checking ips ...' ) for i in xrange ( 100 ): __pingd = threading.Thread(target = self .__check,args = ( self .ping_queue)) __pingd.setDaemon __pingd.start() self .ping_queue.join() return { 'up' : self .up_ip, 'down' : self .down_ip} class createVm(): def __init__( self ,mysql_host,mysql_user,mysql_passwd): self .create_queue = Queue.Queue() self .mysql_user = mysql_user self .mysql_host = mysql_host self .mysql_passwd = mysql_passwd def __put2q( self ,cmd_list,q): while len (cmd_list) > 0 : while q.qsize() < = 3 : q.put(cmd_list.pop()) time.sleep( 1 ) if not cmd_list: break time.sleep( 1 ) def __create( self ,q): time.sleep( 5 ) while q.qsize() > 0 : __cmd = q.get() __ip = __cmd.split()[ - 1 ] print "Building %s" % __ip ret = commands.getstatusoutput(__cmd) if ret[ 0 ] = = 0 : time.sleep( 2 ) __vm_state = 'building' while __vm_state ! = 'active' : time.sleep( 5 ) __vm_state = mysqlEng(host = self .mysql_host,user = self .mysql_user,passwd = self .mysql_passwd).getVmstate(__ip) if __vm_state = = 'error' : self .error_vms + = 1 print red( "虚拟机创建出错,IP:%s" ) % __ip break self .ok_vms + = 1 print green( "%s 创建成功,此次创建虚拟机总数:%s , 成功的虚拟机:%s ,出错的虚拟机:%s" ) % (__ip, self .all_vms, self .ok_vms, self .error_vms) else : self .error_vms + = 1 print red( "执行命令出错,IP:%s,出错信息: %s \n" ) % (__ip,ret[ 1 ]) def creating( self ,cmds,ths): self .all_vms = len (cmds) self .ok_vms = 0 self .error_vms = 0 __put_thread = threading.Thread(target = self .__put2q, args = (cmds, self .create_queue)) __put_thread.setDaemon __put_thread.start() for i in xrange (ths): __created = threading.Thread(target = self .__create, args = ( self .create_queue,)) __created.setDaemon __created.start() time.sleep( 10 ) class createAllvm(): def __init__( self ,vmlist,flavor,mysql_host,mysql_user,mysql_passwd): self .vmlist = vmlist self .mysql_host = mysql_host self .mysql_user = mysql_user self .mysql_passwd = mysql_passwd self .flavor = flavor def start( self ): create_obj = createVm(mysql_host = self .mysql_host,mysql_user = self .mysql_user,mysql_passwd = self .mysql_passwd) first_create_cmd = getCmd(project_info,vmListAnaly( self .vmlist).getFirstCreInfo(), self .flavor,mysql_host = self .mysql_host,mysql_user = self .mysql_user,mysql_passwd = self .mysql_passwd).get_cmd() create_obj.creating(first_create_cmd, 2 ) first_ips = vmListAnaly( 'vmhost' ).getFirstIps() while True : time.sleep( 10 ) if mysqlEng(host = self .mysql_host,user = self .mysql_user,passwd = self .mysql_passwd).getActNum(first_ips) = = len (first_ips): break print '第一波创建完毕' second_create_cmd = getCmd(project_info,vmListAnaly( self .vmlist).getSecondCreInfo(), self .flavor,mysql_host = self .mysql_host,mysql_user = self .mysql_user,mysql_passwd = self .mysql_passwd).get_cmd() if not second_create_cmd: print green( '只有一波了..创建完毕' ) sys.exit() create_obj.creating(second_create_cmd, 3 ) second_ips = vmListAnaly( 'vmhost' ).getSecondIps() while True : time.sleep( 10 ) if mysqlEng(host = self .mysql_host,user = self .mysql_user,passwd = self .mysql_passwd).getActNum(second_ips) = = len (second_ips): break print '第二波创建完毕' if __name__ = = '__main__' : #project_name = sys.argv[1] #vmlist = sys.argv[2] mysql_host = '127.0.0.1' mysql_user = 'root' mysql_passwd = '123456' parser = OptionParser() parser.add_option( "-c" , "--create" ,dest = "create" ,action = "store_true" ,default = False , help = "create vm in filename" ) parser.add_option( "-u" , "--update" ,dest = "update" , help = "update ip to a flavor" ) parser.add_option( "-p" , "--ping" ,dest = "ping" ,action = "store_true" ,default = False , help = "multil threading to ping" ) parser.add_option( "-i" , "--instances" ,dest = "instances" , help = "query a ip's info" ) parser.add_option( "-n" , "--nodes" ,dest = "nodes" , help = "query a host's info" ) parser.add_option( "-g" , "--getcmd" ,dest = "getcmd" ,action = "store_true" ,default = False , help = "get build vm commands from a file" ) parser.add_option( "-d" , "--delete" ,dest = "delete" , help = "delete a instance" ) parser.add_option( "-f" , "--filename" ,dest = "filename" , help = "Specify a file" ) parser.add_option( "-F" , "--flavor" ,dest = "flavor" , help = "Specify a flavor" ) parser.add_option( "-t" , "--tenant" ,dest = "tenant" , help = "Specify a tenant" ) (options,args) = parser.parse_args() project_conf = { 'test' :{ 'OS_USERNAME' : 'test' , 'OS_PASSWORD' : 'test' , 'OS_TENANT_NAME' : 'test' , 'SECURITY_GROUPS' : 'default' , 'IMAGE_ID' : '25b349f2-a105-4d2d-aabe-89253e8b4eb1' , }, 'test2' :{ 'OS_USERNAME' : 'test2' , 'OS_PASSWORD' : 'test2' , 'OS_TENANT_NAME' : 'test2' , 'SECURITY_GROUPS' : 'default' , 'IMAGE_ID' : 'a6955dde-7794-45cc-a468-65ce7d2473c1' , }, } flavor = { '1' : '1' , '2' : '2' , '4' : '4' , '6' : '6' , '8' : '8' , '12' : '12' , '16' : '16' , } the_mysql_obj = mysqlEng(host = mysql_host,user = mysql_user,passwd = mysql_passwd) if options.create: project_info = project_conf[options.tenant] vmfile = options.filename the_create_obj = createAllvm(vmlist = vmfile,flavor = flavor,mysql_host = mysql_host,mysql_user = mysql_user,mysql_passwd = mysql_passwd) the_create_obj.start() if options.update: ip = options.update flavor = options.flavor the_mysql_obj.updateInstanceFlavor(ip,flavor) print "update ok,now the vm %s is %s cups and %sMB memory" % the_mysql_obj.getInstanceFlavor(ip) instance_id = the_mysql_obj.getInstanceInfo([ip])[ 0 ][ 0 ] print red( 'please command == nova reboot --hard %s == to effect' % instance_id) if options.ping: filename = options.filename allip = vmListAnaly(filename).getAllIps() check_obj = checkIps() check_obj.put2q(allip) check_ret = check_obj.checking() if check_ret[ 'up' ]: for ip in check_ret[ 'up' ]: print red( "%s is up,please check..." ) % ip sys.exit( 9 ) else : print green( 'All ip is check ok...,you can create these vms' ) if options.instances: args.append(options.instances) ips = args instances_info = the_mysql_obj.getInstanceInfo(ips) print "id\t\t\t\t\tip\t\tnode\t\t\tstate\tmemory" for i in instances_info: print "%s\t%s\t%s\t%s\t%s" % i if options.nodes: args.append(options.nodes) nodes = args nodes_info = the_mysql_obj.getNodeInfo(nodes) print "id\t\t\t\t\tip\t\tnode\t\t\tstate\tmemory" for i in nodes_info: print "%s\t%s\t%s\t%s\t%s" % i if options.getcmd: filename = options.filename tenant = options.tenant project_info = project_conf[tenant] result = getCmd(project_info,vmListAnaly(filename).getAllCreInfo(),flavor,mysql_host = mysql_host,mysql_user = mysql_user,mysql_passwd = mysql_passwd).get_cmd() for i in result: print i if options.delete: args.append(options.delete) ips = args instances_ids = [] instances_info = the_mysql_obj.getInstanceInfo(ips) for i in instances_info: instances_ids.append(i[ 0 ]) if not instances_ids: print red( "no instance found" ) sys.exit( 8 ) delete_cmd = "nova delete " + ' ' .join(instances_ids) result = commands.getstatusoutput(delete_cmd) if result[ 0 ] = = 0 : print green( 'OK' ) else : print red( "error info: %s" % result[ 1 ]) 本文转自lustlost 51CTO博客,原文链接:http://blog.51cto.com/lustlost/1375779,如需转载请自行联系原作者

优秀的个人博客,低调大师

一位android开发程序猿的小工具集合

工欲善其事,必先利其器 以下是我开发中或生活中用的个人觉得比较好的工具软件,一款好用的工具,往往能事半功倍 PostMan(ChromeApp) 一个可以模拟实现HTTP get和post的请求工具,方面设置请求参数和查看响应结果。可在Chrome应用商店搜索下载,目前已经出了PC版,界面美观,功能强大,良心工具 马克飞象(ChromeApp) 我试用过很多MD编辑器,唯独此款最合我心意,主要是它颜值高,易用 ,它还支持同步印象笔记功能,不过好像要收费 马克飞象使用截图 Vysor(ChromeApp) 做安卓调试的时候,我习惯用真机而不用模拟器调试,因为电脑不行,而且速度没有真机快,但是用真机的话就要在真机上操作,电脑上演示,操作都不方便。有了Vysor就很方便了,直接利用Vysor同步真机上的屏幕,画质流畅,鼠标就能操作真机,完美!当然收费版本有更高级的功能。 Vysor效果 qiniu upload files(ChromeApp) 七牛云空间可以存文件,图片文件还可以加水印上传,并且可以生成外联直接使用,我的博客很多媒体文件都是存在七牛上的,不过每次都要到网页控制台手动添加,要是可以直接拖拽上传就好了。有了qiniu upload files 完美解决。当然会写脚本的,自己弄个方便的命令行工具也是极好的,以后有机会可以搞下。 七牛云空间插件 LICEcap 一款电脑上录制GIF的软件,配合Vysor,傻瓜式操作,完美生成GIF演示文件。省得自己要录成mp4,再转gif。 gif录制效果 Visio 画UML图很好,当然Visio是专业的,各种图都能画 效果图 Gliffy Diagrams 也是画图的好工具,但就是不能调整箭头线的位置 image.png Mobipocket Reader Mobi电子书阅读器,反正我用着挺好的,功能强大 image.png image.png 静读天下(手机APP) 也是款mobi电子书阅读器,功能强大,就是免费版有广告 Screenshot_2017-05-30-15-50-02-1974671738.png Screenshot_2017-05-30-15-50-16-0673376779.png PotPlayer 如果你追求低内存占用、无广告、功能强大又唯美的本地播放器,Potplayer 是最好的选择。 最大的优点——不流氓! 解码强大 PotPlayer

优秀的个人博客,低调大师

桌面支持

Help Desk 14.9.1.Office 基础设施及周边设备管理 14.9.1.1.固定资产管理 14.9.1.2.电脑设备 电脑设备 14.9.1.3.打印机设备 打印机设备 14.9.1.4.电脑设备采购 14.9.1.5.电脑升级 14.9.1.6.报修流程 14.9.1.7.报废流程 14.9.2.网络用户管理 14.9.2.1.用户分配 14.9.2.2.邮箱分配 14.9.2.2.1.邮件模板 14.9.2.2.2.邮件签名规范 14.9.2.3.权限分配 14.9.3.网络安全保密管理 14.9.3.1.系统杀毒 14.9.3.2.系统帐号 14.9.4.信息存储备份管理 14.9.4.1.邮件备份 14.9.4.2.图片备份 14.9.4.3.文档备份 14.9.5.网络操作系统管理 14.9.5.1.域控制器 14.9.5.2.文件存储服务器 14.9.6.网络应用系统管理 14.9.6.1.MIS 系统 14.9.6.2.ERP 系统 14.9.6.3.OA/CRM... 系统 14.9.7.网络机房管理 14.9.7.1.网络拓扑图 14.9.7.2.机柜机架 机架图 14.9.7.3.Router 14.9.7.4.Switch 端口分配 Please enable JavaScript to view the &lt;a href="http://disqus.com/?ref_noscript"&gt;comments powered by Disqus.&lt;/a&gt;comments powered by Disqus 原文出处:Netkiller 系列 手札 本文作者:陈景峯 转载请与作者联系,同时请务必标明文章原始出处和作者信息及本声明。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册