├── README.md ├── configuration.py ├── docker-registry-ui.py ├── docker-registry-ui.sh ├── log └── LOG ├── requirements.txt ├── start.sh ├── static ├── css │ ├── bootstrap-theme.min.css │ └── bootstrap.min.css ├── favicon.ico ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── img │ └── docker-logo-loggedin.png └── js │ ├── bootstrap.min.js │ ├── d3.min.js │ └── jquery-1.11.1.min.js ├── templates ├── footer.html ├── header.html ├── images.html ├── index.html ├── info.html ├── tags.html ├── test.html └── tree.html └── test ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── another.py ├── httpfunc.py └── test.py /README.md: -------------------------------------------------------------------------------- 1 | docker-registry-ui 2 | ================== 3 | 4 | v0.02 5 | 6 | ##Installation 7 | 8 | ###The normal way 9 | ```Bash 10 | git clone https://github.com/ARKII/docker-registry-ui.git 11 | cd docker-registry-ui 12 | sudo pip install -r requirements.txt 13 | vi configuration.py 14 | sudo python ./docker-registry-ui.py 15 | ``` 16 | 17 | ###The docker way 18 | ```Bash 19 | git clone https://github.com/ARKII/docker-registry-ui.git 20 | cd docker-registry-ui 21 | vi configuration.py 22 | sh docker-registry-ui.sh 23 | or 24 | 25 | SERVICE_DIR=/db/docker-registry-ui 26 | docker run \ 27 | -d \ 28 | -e $SERVICE_DIR \ 29 | -e SERVICE_ADDRESS=0.0.0.0 \ 30 | -e SERVICE_PORT=5000 \ 31 | -e GUNICORN_WORKERS=8 \ 32 | -p 5000:5000 \ 33 | -v ${SERVICE_DIR}:${SERVICE_DIR} \ 34 | arkii/gunicorn bash ${SERVICE_DIR}/start.sh 35 | 36 | ``` 37 | 38 | 39 | Visit http://ip:5000/ 40 | 41 | 42 | ##Screenshot 43 | ![image](https://github.com/ARKII/docker-registry-ui/blob/master/test/1.png) 44 | ![image](https://github.com/ARKII/docker-registry-ui/blob/master/test/2.png) 45 | ![image](https://github.com/ARKII/docker-registry-ui/blob/master/test/3.png) 46 | ![image](https://github.com/ARKII/docker-registry-ui/blob/master/test/4.png) 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | ##Read 55 | 56 | https://docs.docker.com/reference/api/registry_api/ 57 | 58 | ``` 59 | curl http://10.15.184.241:5000/v1/_ping 60 | ``` 61 | true 62 | ``` 63 | curl http://10.15.184.241:5000/v1/search?q=arkii 64 | ``` 65 | {"num_results": 1, "query": "arkii", "results": [{"description": null, "name": "arkii/centos65-httpd-2.0.65"}]} 66 | 67 | 68 | 69 | 删除tag 70 | ``` 71 | curl -X DELETE 10.15.184.241/v1/repositories/library/centos/tags/centos5 72 | ``` 73 | 删除repo 74 | ``` 75 | curl -X DELETE 10.15.184.241/v1/repositories/library/centos7/ 76 | ``` -------------------------------------------------------------------------------- /configuration.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | server = '10.15.184.241' #docker registry server ip address 4 | port = '80' #docker registry service port default 5000 5 | 6 | # server = ['docker-hub', 'docker-repo.alias.pch.net'] 7 | -------------------------------------------------------------------------------- /docker-registry-ui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'arkii' 3 | __email__ = 'sun.qingyun@zol.com.cn' 4 | __create__ = '10/30/14 19:48' 5 | 6 | 7 | import os, re, tarfile 8 | from urllib import urlencode 9 | 10 | import configuration 11 | 12 | from httplib import HTTPConnection 13 | 14 | from flask import Flask 15 | from flask import request 16 | from flask import json 17 | from flask import render_template as render 18 | from flask import make_response 19 | 20 | def add_http_header(object=None, key=None, value=None): 21 | _response = make_response(object) 22 | _response.headers[key] = value 23 | return _response 24 | 25 | 26 | class RegistryClass: 27 | def __init__(self, server, port): 28 | self.http_header = { 29 | "Cache-Control": "no-cache", 30 | "User-Agent": "DockerRegistryUI", 31 | "Content-type": "application/x-www-form-urlencoded", 32 | "Accept": "text/plain" 33 | } 34 | self.jsonde = json.JSONDecoder() 35 | self.jsonen = json.JSONEncoder() 36 | self.server = server 37 | self.port = port 38 | 39 | def ping(self, verbose=False): 40 | try: 41 | self.conn = HTTPConnection(self.server, port=self.port, timeout=3) 42 | self.conn.request(method='GET', url='/v1/_ping', headers=self.http_header) 43 | self.response = self.conn.getresponse() 44 | self.data = self.response.read() 45 | if verbose is True: 46 | self.data = {'result': self.data} 47 | self.status = self.response.status 48 | self.message = self.response.reason 49 | self.headers = dict(self.response.getheaders()) 50 | self.data['server_headers'] = self.headers 51 | self.data['server_message'] = self.message 52 | self.data['server_code'] = self.status 53 | except IOError: 54 | self.data = 'socket.error' 55 | finally: 56 | self.conn.close() 57 | return self.data 58 | 59 | 60 | def get(self, uri=None, verbose=False): 61 | try: 62 | self.conn = HTTPConnection(self.server, port=self.port, timeout=3) 63 | self.action = 'GET' 64 | self.conn.request(method=self.action, url=uri, headers=self.http_header) 65 | self.response = self.conn.getresponse() 66 | self.content = self.response.read() # here is str type 67 | self.data = self.jsonde.decode(self.content) # convert str to dict 68 | if verbose is True: 69 | self.status = self.response.status 70 | self.message = self.response.reason 71 | self.headers = dict(self.response.getheaders()) 72 | self.data['server_headers'] = self.headers 73 | self.data['server_message'] = self.message 74 | self.data['server_code'] = self.status 75 | except IOError: 76 | self.data = 'socket.error' 77 | finally: 78 | self.conn.close() 79 | return self.data 80 | 81 | def act(self, action='GET', uri=None, verbose=False): 82 | try: 83 | self.conn = HTTPConnection(self.server, port=self.port, timeout=3) 84 | self.method = action 85 | self.conn.request(method=self.method, url=uri, headers=self.http_header) 86 | self.response = self.conn.getresponse() 87 | self.content = self.response.read() # here is str type 88 | self.data = self.jsonde.decode(self.content) # convert str to dict 89 | if verbose is True: 90 | self.data = {'result': self.data} 91 | self.status = self.response.status 92 | self.message = self.response.reason 93 | self.headers = dict(self.response.getheaders()) 94 | self.data['server_headers'] = self.headers 95 | self.data['server_message'] = self.message 96 | self.data['server_code'] = self.status 97 | except IOError: 98 | self.data = 'socket.error' 99 | finally: 100 | self.conn.close() 101 | return self.data 102 | 103 | def delete(self, action='DELETE', uri=None, verbose=False): 104 | try: 105 | self.conn = HTTPConnection(self.server, port=self.port, timeout=3) 106 | self.method = action 107 | self.conn.request(method=self.method, url=uri, headers=self.http_header) 108 | self.response = self.conn.getresponse() 109 | self.content = self.response.read() # here is str type 110 | self.data = self.content 111 | if verbose is True: 112 | self.data = {'result': self.data} 113 | self.status = self.response.status 114 | self.message = self.response.reason 115 | self.headers = dict(self.response.getheaders()) 116 | self.data['server_headers'] = self.headers 117 | self.data['server_message'] = self.message 118 | self.data['server_code'] = self.status 119 | except IOError: 120 | self.data = 'socket.error' 121 | finally: 122 | self.conn.close() 123 | return self.data 124 | 125 | def close(self): 126 | self.conn.close() 127 | 128 | 129 | registry = RegistryClass(server=configuration.server, port=configuration.port) 130 | 131 | app = Flask(__name__) 132 | 133 | 134 | @app.route('/') 135 | def main_page(): 136 | _registryhost = configuration.server 137 | _status = ping_server() 138 | _t = registry.get('/v1/search?q=') 139 | _imagenumber = '' 140 | if isinstance(_t, dict): _imagenumber = _t['num_results'] 141 | 142 | return render('index.html', pagetitle='index', imagenumber=_imagenumber, hosts=_registryhost, status=_status) 143 | 144 | 145 | @app.route('/images') 146 | def images_page(): 147 | msg = registry.get('/v1/search?q=') 148 | _tableheader = ['Name', 'Description', 'Actions'] 149 | return render('images.html', pagetitle='images', msg=msg, tableheader=_tableheader) 150 | 151 | 152 | @app.route('/ping') 153 | def ping_server(): 154 | return registry.ping() 155 | 156 | 157 | @app.route('/find/', methods=['POST']) 158 | @app.route('/find/') 159 | def find_image(text=None): 160 | if request.method == 'POST': 161 | uri = '/v1/search?q=' + request.values['name'] 162 | if text: 163 | uri = '/v1/search?q=' + str(text) 164 | msg = registry.get(uri=uri, verbose=True) 165 | _tableheader = ['Name', 'Description', 'Actions'] 166 | return render('images.html', pagetitle='images', msg=msg, tableheader=_tableheader) 167 | 168 | 169 | @app.route('/info/') 170 | def show_info(id=None): 171 | _uri = '/v1/images/' + id + '/json' 172 | _msg = registry.act(uri=_uri, verbose=True) 173 | _msg['tableheader'] = ['Tag', 'ID'] 174 | _uri = '/v1/images/' + id + '/ancestry' 175 | _ancestry = {} 176 | _ancestry['tableheader'] = 'Ancestry' 177 | _ancestry['data'] = registry.act(uri=_uri) 178 | return render('info.html', pagetitle='images', msg=_msg, ancestry=_ancestry) 179 | 180 | 181 | @app.route('/tags/', methods=['POST']) 182 | @app.route('/tags//') 183 | def show_tags(namespace=None, repository=None): 184 | if repository is not None: _query = namespace + '/' + repository 185 | if request.method == 'POST': _query = str(request.values['name']) 186 | uri = '/v1/repositories/' + _query + '/tags' 187 | msg = registry.act(uri=uri, verbose=True) 188 | _tableheader = ['Name', 'ID', 'Actions'] 189 | _tree_json = '/json/' + _query 190 | return render('tags.html', pagetitle='tags', tree_json=_tree_json, msg=msg, tableheader=_tableheader, reponame=_query) 191 | 192 | 193 | @app.route('/rm/', methods=['POST']) 194 | @app.route('/rm//') 195 | @app.route('/rm///') 196 | def delete(namespace=None, repository=None, tag=None): 197 | if request.method == 'POST': 198 | uri = '/v1/repositories/' + str(request.values['name']) + '/' 199 | else: 200 | if namespace is not None: _query = namespace + '/' 201 | if repository is not None: _query = namespace + '/' + repository + '/' 202 | if tag is not None: _query = namespace + '/' + repository + '/tags/' + tag 203 | uri = '/v1/repositories/' + _query 204 | msg = registry.delete(action='DELETE', uri=uri) 205 | # msg['tableheader'] = ['Tag', 'ID'] 206 | # return render('tags.html', pagetitle='images', msg=msg) 207 | return str(msg) 208 | 209 | 210 | @app.route('/plain', methods=['GET', 'POST', 'PUT', 'DELETE']) 211 | def hello_world(): 212 | if request.method == 'GET': 213 | return 'YOU GET ME !' 214 | if request.method == 'POST': 215 | return 'YOU POST ME !' 216 | if request.method == 'PUT': 217 | return 'YOU PUT ME !' 218 | if request.method == 'DELETE': 219 | return 'YOU DELETE ME !' 220 | else: 221 | return 'YOU DID NOTHING !' 222 | 223 | 224 | @app.route('/test') 225 | def test(): 226 | return render('test.html') 227 | 228 | 229 | @app.route('/tree') 230 | def tree(): 231 | _url = '/json' 232 | return render('tree.html', tree_json=_url) 233 | 234 | @app.route('/json//') 235 | def output_json(namespace=None, repository=None): 236 | if repository is not None: _query = namespace + '/' + repository 237 | _uri = '/v1/repositories/' + _query + '/tags' 238 | _ancestry = {} 239 | _ancestry['tableheader'] = 'Ancestry' 240 | _ancestry['data'] = registry.act(uri=_uri) 241 | _tree_json = _ancestry['data'] 242 | _d = {'name':_query} 243 | _c = [] 244 | for k, v in _tree_json.iteritems(): 245 | _c.append({'name':k, 'children':[{'name':v}]}) 246 | _d['children'] = _c 247 | _data = json.JSONEncoder().encode(_d) 248 | return add_http_header(_data,'Content-Type', 'application/json') 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | if __name__ == '__main__': 259 | # Flask.debug = True 260 | app.run(host='0.0.0.0') 261 | app.run(debug=1) -------------------------------------------------------------------------------- /docker-registry-ui.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SERVICE_DIR=/db/docker-registry-ui 3 | docker run \ 4 | -d \ 5 | -e $SERVICE_DIR \ 6 | -e SERVICE_ADDRESS=0.0.0.0 \ 7 | -e SERVICE_PORT=5000 \ 8 | -e GUNICORN_WORKERS=8 \ 9 | -p 5000:5000 \ 10 | -v ${SERVICE_DIR}:${SERVICE_DIR} \ 11 | arkii/gunicorn bash ${SERVICE_DIR}/start.sh -------------------------------------------------------------------------------- /log/LOG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkii/docker-registry-ui/9f7bc5f7c2718130d10b165ca30d20ebe1fc00a5/log/LOG -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==1.1.1 2 | Jinja2==2.11.1 3 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | /usr/bin/gunicorn --chdir ${SERVICE_DIR:=/db/docker-registry-ui} \ 3 | --error-logfile ${SERVICE_DIR:=/db/docker-registry-ui}/log/registry-ui.log \ 4 | --access-logfile ${SERVICE_DIR:=/db/docker-registry-ui}/log/access.log \ 5 | --max-requests 100 --graceful-timeout 3600 -t 3600 -k gevent \ 6 | -b ${SERVICE_ADDRESS:=0.0.0.0}:${SERVICE_PORT:=5000} -w ${GUNICORN_WORKERS:=4} \ 7 | docker-registry-ui:app -------------------------------------------------------------------------------- /static/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.0 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-default .badge,.btn-primary .badge,.btn-success .badge,.btn-info .badge,.btn-warning .badge,.btn-danger .badge{text-shadow:none}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:-o-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#2d6ca2));background-image:linear-gradient(to bottom,#428bca 0,#2d6ca2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#2b669a}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-primary:disabled,.btn-primary[disabled]{background-color:#2d6ca2;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#357ebd;background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-o-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#357ebd));background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:-o-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#3071a9));background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:-o-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#3278b3));background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);background-repeat:repeat-x;border-color:#3278b3}.list-group-item.active .badge,.list-group-item.active:hover .badge,.list-group-item.active:focus .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-o-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#357ebd));background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkii/docker-registry-ui/9f7bc5f7c2718130d10b165ca30d20ebe1fc00a5/static/favicon.ico -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkii/docker-registry-ui/9f7bc5f7c2718130d10b165ca30d20ebe1fc00a5/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.svg: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkii/docker-registry-ui/9f7bc5f7c2718130d10b165ca30d20ebe1fc00a5/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkii/docker-registry-ui/9f7bc5f7c2718130d10b165ca30d20ebe1fc00a5/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /static/img/docker-logo-loggedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkii/docker-registry-ui/9f7bc5f7c2718130d10b165ca30d20ebe1fc00a5/static/img/docker-logo-loggedin.png -------------------------------------------------------------------------------- /static/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.0 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.0",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.0",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus","focus"==b.type)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.0",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c="prev"==a?-1:1,d=this.getItemIndex(b),e=(d+c)%this.$items.length;return this.$items.eq(e)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i="next"==b?"first":"last",j=this;if(!f.length){if(!this.options.wrap)return;f=this.$element.find(".item")[i]()}if(f.hasClass("active"))return this.sliding=!1;var k=f[0],l=a.Event("slide.bs.carousel",{relatedTarget:k,direction:h});if(this.$element.trigger(l),!l.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var m=a(this.$indicators.children()[this.getItemIndex(f)]);m&&m.addClass("active")}var n=a.Event("slid.bs.carousel",{relatedTarget:k,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),j.sliding=!1,setTimeout(function(){j.$element.trigger(n)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(n)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a(this.options.trigger).filter('[href="#'+b.id+'"], [data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.0",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0,trigger:'[data-toggle="collapse"]'},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.find("> .panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":a.extend({},e.data(),{trigger:this});c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.0",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(' 14 | 15 | -------------------------------------------------------------------------------- /templates/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ pagetitle }} 6 | 32 | 33 | 34 | 35 | 42 | 53 | 54 | 55 | 56 | 91 | 92 | -------------------------------------------------------------------------------- /templates/images.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 | 3 |
4 | 5 |
6 |

Images

7 | 8 | 9 | {% for i in tableheader %} 10 | 11 | {% endfor %} 12 | 13 | {% for i in msg['results'] %} 14 | 15 | 16 | 17 | 29 | 30 | {% endfor %} 31 |
{{ i }}
{{ i['name'] }}{{ i['description'] }} 18 |
19 | 20 | 23 | 24 | 27 |
28 |
32 | 33 |
34 |
35 | 36 | 57 | 58 | {# {{ msg }} #} 59 | 60 |
61 | {% include 'footer.html' %} -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 |
3 | 4 |
5 |

Docker Registry Web Console

6 |
7 |
8 |
9 |

Dashboard

10 | 11 |

12 | Registry Host: {{ hosts }}
13 | 14 | Ping Status: {{ status }}
15 | 16 | Image Number: {{ imagenumber }} 17 |

18 |
19 |
20 |
21 | 22 | {% include 'footer.html' %} 23 | -------------------------------------------------------------------------------- /templates/info.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 |
3 | 4 |
5 | 6 | 7 | {% for i in msg['tableheader'] %} 8 | 9 | {% endfor %} 10 | 11 | {% for k, v in msg['result'].iteritems() %} 12 | 13 | 14 | {# #} 15 | 16 | 17 | {% endfor %} 18 | {% if ancestry %} 19 | 20 | 21 | 27 | 28 | {% endif %} 29 |
{{ i }}
{{ k }}{{ v }}{{ v }}
{{ ancestry['tableheader'] }} 22 | {% for i in ancestry['data'] %} 23 | {{ i }}
24 | {% endfor %} 25 | 26 |
30 |
31 | {# {{ ancestry }} #} 32 | {# {{ msg }} #} 33 |
34 | 35 | {% include 'footer.html' %} -------------------------------------------------------------------------------- /templates/tags.html: -------------------------------------------------------------------------------- 1 | {% include 'header.html' %} 2 |
3 | 4 | {# {{ ancestry }} #} 5 | {# {{ msg }} #} 6 | 7 |
8 |

Tags

9 | 10 | 11 | {% for i in tableheader %} 12 | 13 | {% endfor %} 14 | 15 | {% for k, v in msg['result'].iteritems() %} 16 | 17 | 18 | 19 | 31 | 32 | {% endfor %} 33 | 34 |
{{ i }}
{{ k }}{{ v }} 20 |
21 | 22 | 25 | 26 | 29 |
30 |
35 | 36 |
37 |
38 | 39 | 60 | 61 |
62 |
63 | 82 | 83 | 139 |

Tags

140 |
141 | 142 | 143 | {% include 'footer.html' %} -------------------------------------------------------------------------------- /templates/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | test 6 | 7 | 8 | 9 | 10 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 32 | 52 | 53 |
54 | 55 | 56 | -------------------------------------------------------------------------------- /templates/tree.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 22 | 23 | 24 | 80 | 81 | -------------------------------------------------------------------------------- /test/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkii/docker-registry-ui/9f7bc5f7c2718130d10b165ca30d20ebe1fc00a5/test/1.png -------------------------------------------------------------------------------- /test/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkii/docker-registry-ui/9f7bc5f7c2718130d10b165ca30d20ebe1fc00a5/test/2.png -------------------------------------------------------------------------------- /test/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkii/docker-registry-ui/9f7bc5f7c2718130d10b165ca30d20ebe1fc00a5/test/3.png -------------------------------------------------------------------------------- /test/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkii/docker-registry-ui/9f7bc5f7c2718130d10b165ca30d20ebe1fc00a5/test/4.png -------------------------------------------------------------------------------- /test/another.py: -------------------------------------------------------------------------------- 1 | import urllib2 2 | import json 3 | import tarfile 4 | import re 5 | import os 6 | from datetime import datetime 7 | 8 | from flask import Flask 9 | from flask import render_template 10 | from flask import request 11 | 12 | app = Flask(__name__) 13 | app.debug = True 14 | 15 | registry_url = "localhost" 16 | if "REGURL" in os.environ: 17 | registry_url = os.environ['REGURL'] 18 | 19 | print "Registry reside on http://" + str(registry_url) + "/v1" 20 | 21 | FILE_TYPES = { 22 | 'f':'file', 23 | 'l':'hardlink', 24 | 's':'symlink', 25 | 'c':'char', 26 | 'b':'block', 27 | 'd':'directory', 28 | 'i':'fifo', 29 | 't':'cont', 30 | 'L':'longname', 31 | 'K':'longlink', 32 | 'S':'sparse', 33 | } 34 | 35 | def _query(path): 36 | response = urllib2.urlopen("http://" + str(registry_url) + "/v1" + str(path)) 37 | result = json.loads(response.read()) 38 | return result 39 | 40 | def _build_file_dict(files): 41 | res = [] 42 | for file in files: 43 | res.append({ 44 | 'name': file[0], 45 | 'type': FILE_TYPES.get(file[1], 'unknown'), 46 | 'deleted': file[2], 47 | 'size': file[3], 48 | 'mtime': file[4], 49 | 'mode': file[5], 50 | 'owner': file[6], 51 | 'group': file[7] 52 | }) 53 | return res 54 | 55 | def _build_image_tree(images): 56 | all_images = [] 57 | for image in images: 58 | d = _query("/images/%s/json" % image['id']) 59 | all_images.append(d) 60 | exists = set(map(lambda x : x['id'], all_images)) 61 | top = [x for x in all_images if 'parent' not in x.keys()][0] 62 | children = {} 63 | for image in all_images: 64 | if 'parent' not in image.keys(): 65 | continue 66 | parent = image['parent'] 67 | if not parent: 68 | continue 69 | if parent not in exists: 70 | continue 71 | if parent in children: 72 | children[parent].append(image) 73 | else: 74 | children[parent] = [ image ] 75 | return _sort_image_list(children, top) 76 | 77 | def _sort_image_list(children, top): 78 | res = [ top ] 79 | if top['id'] in children: 80 | for child in children[top['id']]: 81 | res += _sort_image_list(children, child) 82 | return res 83 | 84 | @app.route("/") 85 | @app.route("/home", methods=['GET','POST']) 86 | def index(): 87 | query = '' 88 | images = [] 89 | if request.method == 'POST': 90 | query = request.form['query'] 91 | result = _query('/search?q=' + query) 92 | for repo in result['results']: 93 | repo_images = _query("/repositories/%s/images" % repo['name']) 94 | images.append({'container': repo['name'], 'images': repo_images}) 95 | return render_template('index.html', results=result['results'], images=images) 96 | 97 | @app.route("/images/") 98 | @app.route("/images//") 99 | def images(image_id, repo_name=None): 100 | result = _query("/images/%s/json" % image_id) 101 | files_raw = _query("/images/%s/files" % image_id) 102 | files = _build_file_dict(files_raw) 103 | return render_template('image.html', results=result, files=files, repo=repo_name) 104 | 105 | @app.route("/repo//") 106 | def repo(repo_name, image_id): 107 | result = _query("/repositories/%s/%s/json" % (repo_name,image_id)) 108 | images = _query("/repositories/%s/%s/images" % (repo_name,image_id)) 109 | tags = _query("/repositories/%s/%s/tags" % (repo_name,image_id)) 110 | properties = _query("/repositories/%s/%s/properties" % (repo_name,image_id)) 111 | sorted_images = _build_image_tree(images) 112 | return render_template('repo.html', name=repo_name+"/"+image_id, 113 | results=result, images=sorted_images, tags=tags, properties=properties) 114 | 115 | @app.template_filter() 116 | def datetimefilter(value, format='%Y/%m/%d %H:%M'): 117 | value = re.sub(r'[0-9]{2}Z$','', value) 118 | d = datetime(*map(int, re.split('[^\d]', value)[:-1])) 119 | return d.strftime(format) 120 | 121 | @app.template_filter() 122 | def joinifarray(value): 123 | if type(value) == list: 124 | res = ' '.join(value) 125 | else: 126 | res = value 127 | return res 128 | 129 | app.jinja_env.filters['datetimefilter'] = datetimefilter 130 | app.jinja_env.filters['joinifarray'] = joinifarray 131 | 132 | if __name__ == "__main__": 133 | app.run(host='0.0.0.0',port=8080) -------------------------------------------------------------------------------- /test/httpfunc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'arkii' 3 | __email__ = 'sun.qingyun@zol.com.cn' 4 | __create__ = '10/31/14 17:57' 5 | 6 | from urllib import request 7 | from urllib.parse import urlencode 8 | from http.client import HTTPConnection 9 | 10 | HTTP_HEADER = { 11 | "Cache-Control": "no-cache", 12 | "User-Agent": "SquidClient", 13 | "Content-type": "application/x-www-form-urlencoded", 14 | "Accept": "text/plain" 15 | } 16 | 17 | 18 | def cdn_push(node, url): 19 | _url = [] 20 | if isinstance(url, list): 21 | _url = ','.join(url) 22 | else: 23 | _url = url 24 | _params = urlencode({'username': 'lb_fresh', 'password': 'ct557k3O6', 'url': _url, 'type': 1}) 25 | _conn = HTTPConnection(node) 26 | _conn.request('POST', '/cdnUrlPush.do', _params, headers=HTTP_HEADER) 27 | _data = _conn.getresponse() 28 | _header = _data.headers 29 | _rk = RK_MAP[int(_header['rk'])] 30 | _code = _data.status 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /test/test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'arkii' 3 | __email__ = 'sun.qingyun@zol.com.cn' 4 | __create__ = '11/11/14 17:21' 5 | 6 | 7 | 8 | def extract(d): 9 | if isinstance(d, dict): 10 | if d.has_key('result') : print 'result', ' -is:- ', d['result'] 11 | if d.has_key('query') : print 'query', ' -is:- ', d['query'] 12 | if d.has_key('num_results') : print 'num_results', ' -is:- ', d['num_results'] 13 | if d.has_key('server_headers') : print 'server_headers', ' -is:- ', d['server_headers'] 14 | if d.has_key('server_message') : print 'server_message', ' -is:- ', d['server_message'] 15 | if d.has_key('server_code') : print 'server_code', ' -is:- ', d['server_code'] 16 | if d.has_key('results'): 17 | for i in d['results']: 18 | for k,v in i.iteritems(): 19 | print k, ' -is:- ', v 20 | else: 21 | print 'aaa' 22 | 23 | 24 | @app.route('/') 25 | def main_page(): 26 | msg = registry.get(server, '/v1/search?q=', verbose=True) 27 | return render('test.html', msg=all, extract=lambda d: extract(d)) --------------------------------------------------------------------------------