├── .classpath ├── .gitignore ├── .project ├── .settings ├── .jsdtscope ├── org.eclipse.jdt.core.prefs ├── org.eclipse.wst.common.component ├── org.eclipse.wst.common.project.facet.core.xml ├── org.eclipse.wst.jsdt.ui.superType.container └── org.eclipse.wst.jsdt.ui.superType.name ├── README.md ├── build.xml ├── conf └── log4j.properties ├── frame └── com │ └── ulricqin │ ├── frame │ ├── exception │ │ └── RenderJsonMsgException.java │ └── kit │ │ ├── Checker.java │ │ └── StringKit.java │ └── jfinal │ └── ext │ └── plugin │ └── MemcachePlugin.java ├── lib └── jetty-server-8.1.8.jar ├── scripts └── schema.sql ├── src └── com │ └── ulricqin │ └── dash │ ├── api │ ├── ServerApi.java │ └── UicApi.java │ ├── config │ └── Config.java │ ├── controller │ ├── ApiController.java │ ├── AppController.java │ ├── AuthController.java │ ├── CacheController.java │ ├── DomainController.java │ ├── EnvController.java │ ├── HealthController.java │ ├── HistoryController.java │ └── MainController.java │ ├── interceptor │ ├── AppFromUrlInterceptor.java │ ├── DomainFromUrlInterceptor.java │ ├── GlobalInterceptor.java │ └── LoginRequiredInterceptor.java │ └── model │ ├── App.java │ ├── Domain.java │ ├── Env.java │ ├── History.java │ ├── Team.java │ └── User.java └── web ├── META-INF └── MANIFEST.MF ├── WEB-INF ├── config.txt.example ├── lib │ ├── c3p0-0.9.1.2.jar │ ├── commons-codec-1.6.jar │ ├── commons-lang3-3.3.2.jar │ ├── commons-logging-1.1.3.jar │ ├── commons-pool-1.5.6.jar │ ├── fastjson-1.1.43.jar │ ├── fluent-hc-4.3.5.jar │ ├── freemarker-2.3.20.jar │ ├── httpclient-4.3.5.jar │ ├── httpclient-cache-4.3.5.jar │ ├── httpcore-4.3.2.jar │ ├── httpmime-4.3.5.jar │ ├── java_memcached-release_2.6.6.jar │ ├── jfinal-1.8-bin-with-src.jar │ ├── log4j-1.2.16.jar │ ├── mysql-connector-java-5.1.20-bin.jar │ ├── slf4j-api-1.6.1.jar │ └── slf4j-log4j12-1.6.1.jar ├── tpl │ ├── app │ │ ├── create.html │ │ ├── deploy.html │ │ ├── edit.html │ │ ├── env.html │ │ ├── history.html │ │ ├── instance.html │ │ └── scale.html │ ├── common │ │ ├── _blank.html │ │ ├── _layout.html │ │ └── _paginate.html │ ├── domain │ │ ├── bind.html │ │ ├── create.html │ │ ├── edit.html │ │ └── index.html │ └── index.html └── web.xml ├── css ├── bootstrap.css.map ├── bootstrap.min.css ├── g.css └── select2-bootstrap.css ├── favicon.ico ├── fonts ├── glyphicons-halflings-regular.eot ├── glyphicons-halflings-regular.svg ├── glyphicons-halflings-regular.ttf └── glyphicons-halflings-regular.woff ├── img └── guy.jpg ├── js ├── bootstrap.min.js ├── g.js ├── jquery.min.js ├── jquery.min.map └── md5.js ├── layer ├── extend │ └── layer.ext.js ├── layer.min.js └── skin │ ├── default │ ├── icon_ext.png │ ├── textbg.png │ ├── xubox_ico0.png │ ├── xubox_loading0.gif │ ├── xubox_loading1.gif │ ├── xubox_loading2.gif │ ├── xubox_loading3.gif │ └── xubox_title0.png │ ├── layer.css │ └── layer.ext.css └── select2 ├── select2-spinner.gif ├── select2.css ├── select2.min.js ├── select2.png └── select2x2.png /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.swp 3 | *.swo 4 | /var 5 | /web/WEB-INF/classes 6 | /web/WEB-INF/config.txt 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | dash 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.wst.jsdt.core.javascriptValidator 10 | 11 | 12 | 13 | 14 | org.eclipse.jdt.core.javabuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.wst.common.project.facet.core.builder 20 | 21 | 22 | 23 | 24 | org.eclipse.wst.validation.validationbuilder 25 | 26 | 27 | 28 | 29 | 30 | org.eclipse.jem.workbench.JavaEMFNature 31 | org.eclipse.wst.common.modulecore.ModuleCoreNature 32 | org.eclipse.wst.common.project.facet.core.nature 33 | org.eclipse.jdt.core.javanature 34 | org.eclipse.wst.jsdt.core.jsNature 35 | 36 | 37 | -------------------------------------------------------------------------------- /.settings/.jsdtscope: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 4 | org.eclipse.jdt.core.compiler.compliance=1.8 5 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 6 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 7 | org.eclipse.jdt.core.compiler.source=1.8 8 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.common.component: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.common.project.facet.core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.container: -------------------------------------------------------------------------------- 1 | org.eclipse.wst.jsdt.launching.baseBrowserLibrary -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.name: -------------------------------------------------------------------------------- 1 | Window -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Dashboard 2 | ========= 3 | 4 | 这是DINP中的dashboard,是用户操作的入口。我们没有提供命令行的入口,只提供了web的入口,有三个原因: 5 | 6 | - 我们发现很多用户还是习惯web界面 7 | - 命令行工具需要安装,而浏览器无需安装 8 | - 人力问题 9 | 10 | dashboard采用 [UIC](http://ulricqin.com/project/uic/) 作为单点登录系统,故而,要run dashboard需要提前部署UIC。 11 | 12 | # 部署 13 | 14 | 和UIC一样,这也是一个java项目,也是采用JFinal框架,也是使用Ant来打包编译,也是采用JDK7+Tomcat7来run,请参考前面UIC的链接,里边介绍了UIC的部署方式 15 | 16 | # 手册 17 | 18 | http://dinp.qiniudn.com/manual.pdf 19 | 20 | # 传送门 21 | 22 | 国内用户使用github可能比较慢,我也push了一份到gitcafe: https://gitcafe.com/dinp/dash ,不用谢 23 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /conf/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=info, stdout 2 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 3 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 4 | log4j.appender.stdout.layout.ConversionPattern=%n%-d{yyyy-MM-dd HH:mm:ss}%n[%p]-[Thread: %t]-[%C.%M()]: %m%n 5 | 6 | # Output to the File 7 | log4j.appender.file=org.apache.log4j.FileAppender 8 | log4j.appender.file.File=/home/work/logs/paas-dash.log 9 | log4j.appender.file.layout=org.apache.log4j.PatternLayout 10 | log4j.appender.file.layout.ConversionPattern=%n%-d{yyyy-MM-dd HH:mm:ss}%n[%p]-[Thread: %t]-[%C.%M()]: %m%n 11 | -------------------------------------------------------------------------------- /frame/com/ulricqin/frame/exception/RenderJsonMsgException.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.frame.exception; 2 | 3 | public class RenderJsonMsgException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 8534760341910402946L; 6 | 7 | public RenderJsonMsgException(String msg) { 8 | super(msg); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frame/com/ulricqin/frame/kit/Checker.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.frame.kit; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | 6 | public class Checker { 7 | 8 | public static boolean isIdentifier(String name) { 9 | if (StringKit.isBlank(name)) { 10 | throw new IllegalArgumentException("argument is blank"); 11 | } 12 | 13 | Pattern pattern = Pattern.compile("[a-zA-Z0-9\\-\\_]+"); 14 | Matcher matcher = pattern.matcher(name); 15 | return matcher.matches(); 16 | } 17 | 18 | public static boolean isDomain(String domain) { 19 | if (StringKit.isBlank(domain)) { 20 | throw new IllegalArgumentException("argument is blank"); 21 | } 22 | 23 | Pattern pattern = Pattern.compile("^[a-z0-9\\-\\_][a-z0-9\\-\\_\\.]+[a-z]$"); 24 | Matcher matcher = pattern.matcher(domain); 25 | return matcher.matches(); 26 | } 27 | 28 | public static void main(String[] args) { 29 | System.out.println(isDomain("_a.com")); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /frame/com/ulricqin/frame/kit/StringKit.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.frame.kit; 2 | 3 | import java.util.UUID; 4 | 5 | public class StringKit { 6 | 7 | public static boolean isBlank(String val) { 8 | if (val == null || val.equals("") || val.trim().equals("")) { 9 | return true; 10 | } else { 11 | return false; 12 | } 13 | } 14 | 15 | public static boolean isNotBlank(String val) { 16 | return !isBlank(val); 17 | } 18 | 19 | public static String firstCharToLowerCase(String str) { 20 | Character firstChar = str.charAt(0); 21 | String tail = str.substring(1); 22 | str = Character.toLowerCase(firstChar) + tail; 23 | return str; 24 | } 25 | 26 | public static String firstCharToUpperCase(String str) { 27 | Character firstChar = str.charAt(0); 28 | String tail = str.substring(1); 29 | str = Character.toUpperCase(firstChar) + tail; 30 | return str; 31 | } 32 | 33 | public static String randomStr32() { 34 | return UUID.randomUUID().toString().replace("-", ""); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /frame/com/ulricqin/jfinal/ext/plugin/MemcachePlugin.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.jfinal.ext.plugin; 2 | 3 | import com.danga.MemCached.MemCachedClient; 4 | import com.danga.MemCached.SockIOPool; 5 | import com.jfinal.plugin.IPlugin; 6 | 7 | public class MemcachePlugin implements IPlugin { 8 | 9 | private static MemCachedClient client = new MemCachedClient(); 10 | private static String prefix = ""; 11 | 12 | private String[] addr; 13 | private Integer[] weights; 14 | 15 | public MemcachePlugin(String[] addr, Integer[] weights) { 16 | this.addr = addr; 17 | this.weights = weights; 18 | } 19 | 20 | @Override 21 | public boolean start() { 22 | SockIOPool pool = SockIOPool.getInstance(); 23 | pool.setServers(addr); 24 | pool.setWeights(weights); 25 | pool.setInitConn(5); 26 | pool.setMinConn(5); 27 | pool.setMaxConn(200); 28 | pool.setMaxIdle(1000 * 30 * 30); 29 | pool.setMaintSleep(30); 30 | pool.setNagle(false); 31 | pool.setSocketTO(30); 32 | pool.setSocketConnectTO(0); 33 | pool.initialize(); 34 | return true; 35 | } 36 | 37 | @Override 38 | public boolean stop() { 39 | SockIOPool pool = SockIOPool.getInstance(); 40 | pool.shutDown(); 41 | return true; 42 | } 43 | 44 | public static void setPrefix(String prefix) { 45 | MemcachePlugin.prefix = prefix; 46 | } 47 | 48 | public static Object get(String key) { 49 | return client.get(prefix + key); 50 | } 51 | 52 | public static boolean set(String key, Object val) { 53 | return client.set(prefix + key, val); 54 | } 55 | 56 | public static boolean flushAll() { 57 | return client.flushAll(); 58 | } 59 | 60 | public static void main(String[] args) { 61 | String[] addr = { "127.0.0.1:11211" }; 62 | Integer[] weights = { 3 }; 63 | MemcachePlugin plugin = new MemcachePlugin(addr, weights); 64 | plugin.start(); 65 | 66 | // 将数据放入缓存 67 | client.set("name", "Rain"); 68 | String name = (String) client.get("name"); 69 | System.out.println(name); 70 | 71 | client.set("name", "Flame"); 72 | System.out.println(client.get("name")); 73 | 74 | // 删除缓存数据 75 | client.delete("name"); 76 | System.out.println(client.get("name")); 77 | 78 | plugin.stop(); 79 | 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /lib/jetty-server-8.1.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/lib/jetty-server-8.1.8.jar -------------------------------------------------------------------------------- /scripts/schema.sql: -------------------------------------------------------------------------------- 1 | USE dash; 2 | 3 | SET NAMES utf8; 4 | 5 | /** 6 | * app name要作为域名的一部分,所以全局唯一 7 | * app有个创建者creator 8 | * app一般有多个人来维护,所以对应一个team 9 | * memory的单位是MB 10 | * instance是实例数目 11 | * status: 表示app的状态,running、flapping之类的 12 | */ 13 | DROP TABLE IF EXISTS app; 14 | CREATE TABLE app ( 15 | id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 16 | name VARCHAR(64) NOT NULL UNIQUE, 17 | creator INT UNSIGNED NOT NULL, 18 | team_id INT UNSIGNED NOT NULL default 0, 19 | team_name varchar(64) NOT NULL default '', 20 | image VARCHAR(255) NOT NULL DEFAULT '', 21 | memory INT NOT NULL DEFAULT 256, 22 | instance INT NOT NULL DEFAULT 0 23 | COMMENT 'instance count', 24 | status TINYINT NOT NULL DEFAULT 0, 25 | KEY idx_app_team(team_id) 26 | ) 27 | ENGINE =innodb 28 | DEFAULT CHARSET =utf8 29 | COLLATE =utf8_general_ci; 30 | 31 | DROP TABLE IF EXISTS domain; 32 | CREATE TABLE domain ( 33 | id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 34 | domain VARCHAR(255) NOT NULL UNIQUE, 35 | team_id INT UNSIGNED NOT NULL default 0, 36 | team_name VARCHAR(64) NOT NULL default '', 37 | app_id INT UNSIGNED NOT NULL default 0, 38 | app_name VARCHAR(64) NOT NULL default '', 39 | creator INT UNSIGNED NOT NULL, 40 | creator_name VARCHAR(64) NOT NULL, 41 | bind_user_id VARCHAR(64) NOT NULL, 42 | bind_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 43 | KEY idx_domain_app_id(app_id) 44 | ) 45 | ENGINE =innodb 46 | DEFAULT CHARSET =utf8 47 | COLLATE =utf8_general_ci; 48 | 49 | /* 50 | * 对于某个app,可以设置一些环境变量,比如DB连接地址,先期可以手填,之后可以结合rds使用 51 | */ 52 | DROP TABLE IF EXISTS env; 53 | CREATE TABLE env ( 54 | id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 55 | k VARCHAR(128) NOT NULL, 56 | v VARCHAR(1024) NOT NULL, 57 | app_id INT UNSIGNED NOT NULL, 58 | app_name VARCHAR(64) NOT NULL, 59 | KEY idx_env_app_id(app_id) 60 | ) 61 | ENGINE =innodb 62 | DEFAULT CHARSET =utf8 63 | COLLATE =utf8_general_ci; 64 | 65 | DROP TABLE IF EXISTS history; 66 | CREATE TABLE history ( 67 | id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 68 | app_id INT UNSIGNED NOT NULL, 69 | app_name VARCHAR(64) NOT NULL, 70 | resume VARCHAR(255) NOT NULL DEFAULT '', 71 | image VARCHAR(1024) NOT NULL DEFAULT '', 72 | create_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 73 | KEY idx_history_app_id(app_id) 74 | ) 75 | ENGINE =innodb 76 | DEFAULT CHARSET =utf8 77 | COLLATE =utf8_general_ci; 78 | 79 | alter table app add column health varchar(128) not null default ''; -------------------------------------------------------------------------------- /src/com/ulricqin/dash/api/ServerApi.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.api; 2 | 3 | import java.io.IOException; 4 | 5 | import org.apache.http.HttpResponse; 6 | import org.apache.http.StatusLine; 7 | import org.apache.http.client.fluent.Request; 8 | import org.apache.http.util.EntityUtils; 9 | 10 | import com.ulricqin.dash.config.Config; 11 | 12 | public class ServerApi { 13 | 14 | public static String containersOf(String appName) { 15 | HttpResponse resp; 16 | try { 17 | resp = Request.Get(Config.serverUrl + "/app/" + appName).execute() 18 | .returnResponse(); 19 | } catch (IOException e) { 20 | return null; 21 | } 22 | StatusLine statusLine = resp.getStatusLine(); 23 | if (statusLine.getStatusCode() != 200) { 24 | return null; 25 | } 26 | 27 | try { 28 | return EntityUtils.toString(resp.getEntity()); 29 | } catch (Exception e) { 30 | return null; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/api/UicApi.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.api; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | import org.apache.commons.logging.Log; 9 | import org.apache.commons.logging.LogFactory; 10 | import org.apache.http.HttpResponse; 11 | import org.apache.http.StatusLine; 12 | import org.apache.http.client.fluent.Request; 13 | import org.apache.http.util.EntityUtils; 14 | 15 | import com.alibaba.fastjson.JSON; 16 | import com.alibaba.fastjson.JSONArray; 17 | import com.alibaba.fastjson.JSONObject; 18 | import com.ulricqin.dash.config.Config; 19 | import com.ulricqin.dash.model.Team; 20 | import com.ulricqin.dash.model.User; 21 | import com.ulricqin.jfinal.ext.plugin.MemcachePlugin; 22 | 23 | public class UicApi { 24 | 25 | private static final Log log = LogFactory.getLog(UicApi.class); 26 | 27 | public static String genSig() { 28 | try { 29 | HttpResponse resp = Request.Get(Config.uicInternal + "/sso/sig").execute() 30 | .returnResponse(); 31 | StatusLine statusLine = resp.getStatusLine(); 32 | if (statusLine.getStatusCode() != 200) { 33 | return null; 34 | } 35 | return EntityUtils.toString(resp.getEntity()); 36 | } catch (IOException e) { 37 | log.error(e); 38 | return null; 39 | } 40 | } 41 | 42 | public static User getUser(String sig) { 43 | String key = "u:"+sig; 44 | Object val = MemcachePlugin.get(key); 45 | if (val == null) { 46 | User u = getUserFromSSO(sig); 47 | if (u == null) { 48 | return null; 49 | } 50 | 51 | MemcachePlugin.set(key, u); 52 | return u; 53 | } 54 | 55 | return (User)val; 56 | } 57 | 58 | public static User getUserFromSSO(String sig) { 59 | try { 60 | HttpResponse resp = Request.Get(Config.uicInternal + "/sso/user/" + sig + "?token=" + Config.token).execute() 61 | .returnResponse(); 62 | StatusLine statusLine = resp.getStatusLine(); 63 | if (statusLine.getStatusCode() != 200) { 64 | return null; 65 | } 66 | String content = EntityUtils.toString(resp.getEntity()); 67 | JSONObject jsonObject = JSON.parseObject(content); 68 | if (!jsonObject.containsKey("user")) { 69 | return null; 70 | } 71 | 72 | JSONObject userJson = jsonObject.getJSONObject("user"); 73 | if (userJson == null) { 74 | return null; 75 | } 76 | 77 | User u = JSON.toJavaObject(userJson, User.class); 78 | return u; 79 | } catch (IOException e) { 80 | return null; 81 | } 82 | } 83 | 84 | public static List myTeams(int uid) { 85 | try { 86 | HttpResponse resp = Request.Get(Config.uicInternal + "/team/mine/" + uid + "?token=" + Config.token).execute() 87 | .returnResponse(); 88 | StatusLine statusLine = resp.getStatusLine(); 89 | if (statusLine.getStatusCode() != 200) { 90 | return Collections.emptyList(); 91 | } 92 | 93 | String content = EntityUtils.toString(resp.getEntity()); 94 | JSONObject jsonObject = JSON.parseObject(content); 95 | if (!jsonObject.containsKey("teams")) { 96 | return Collections.emptyList(); 97 | } 98 | 99 | JSONArray jsonArray = jsonObject.getJSONArray("teams"); 100 | if (jsonArray == null || jsonArray.size() == 0) { 101 | return Collections.emptyList(); 102 | } 103 | 104 | List list = new ArrayList(); 105 | for (int i = 0; i < jsonArray.size(); i++) { 106 | JSONObject jsonTeam = jsonArray.getJSONObject(i); 107 | list.add(new Team(jsonTeam.getIntValue("id"), jsonTeam.getString("name"))); 108 | } 109 | 110 | return list; 111 | } catch (IOException e) { 112 | return Collections.emptyList(); 113 | } 114 | } 115 | 116 | public static void main(String[] args) { 117 | // sig: de2b8faabdfd4ff58f35e209a13473d4 118 | System.out.println(getUserFromSSO("de2b8faabdfd4ff58f35e209a13473d4")); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/config/Config.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.config; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import com.jfinal.config.Constants; 6 | import com.jfinal.config.Handlers; 7 | import com.jfinal.config.Interceptors; 8 | import com.jfinal.config.JFinalConfig; 9 | import com.jfinal.config.Plugins; 10 | import com.jfinal.config.Routes; 11 | import com.jfinal.core.JFinal; 12 | import com.jfinal.plugin.activerecord.ActiveRecordPlugin; 13 | import com.jfinal.plugin.c3p0.C3p0Plugin; 14 | import com.ulricqin.dash.controller.ApiController; 15 | import com.ulricqin.dash.controller.AppController; 16 | import com.ulricqin.dash.controller.AuthController; 17 | import com.ulricqin.dash.controller.CacheController; 18 | import com.ulricqin.dash.controller.DomainController; 19 | import com.ulricqin.dash.controller.EnvController; 20 | import com.ulricqin.dash.controller.HealthController; 21 | import com.ulricqin.dash.controller.HistoryController; 22 | import com.ulricqin.dash.controller.MainController; 23 | import com.ulricqin.dash.interceptor.GlobalInterceptor; 24 | import com.ulricqin.dash.model.App; 25 | import com.ulricqin.dash.model.Domain; 26 | import com.ulricqin.dash.model.Env; 27 | import com.ulricqin.dash.model.History; 28 | import com.ulricqin.jfinal.ext.plugin.MemcachePlugin; 29 | 30 | public class Config extends JFinalConfig { 31 | 32 | public static String uicExternal; 33 | public static String uicInternal; 34 | public static String paasDomain; 35 | public static String serverUrl; 36 | public static String token; 37 | public static String builder; 38 | 39 | @Override 40 | public void configConstant(Constants me) { 41 | loadPropertyFile("config.txt"); 42 | me.setDevMode(getPropertyToBoolean("devMode", false)); 43 | me.setBaseViewPath("/WEB-INF/tpl"); 44 | 45 | uicExternal = getProperty("uicExternal"); 46 | uicInternal = getProperty("uicInternal"); 47 | paasDomain = getProperty("paasDomain"); 48 | serverUrl = getProperty("server"); 49 | token = getProperty("token"); 50 | builder = getProperty("builder"); 51 | } 52 | 53 | @Override 54 | public void configRoute(Routes me) { 55 | me.add("/", MainController.class); 56 | me.add("/health", HealthController.class); 57 | me.add("/cache", CacheController.class); 58 | me.add("/auth", AuthController.class); 59 | me.add("/app", AppController.class); 60 | me.add("/env", EnvController.class); 61 | me.add("/domain", DomainController.class); 62 | me.add("/history", HistoryController.class); 63 | me.add("/api", ApiController.class); 64 | } 65 | 66 | @Override 67 | public void configPlugin(Plugins me) { 68 | String jdbcUrl = getProperty("jdbcUrl").trim(); 69 | String jdbcUser = getProperty("user").trim(); 70 | String jdbcPasswd = getProperty("password").trim(); 71 | 72 | C3p0Plugin c3p0Plugin = new C3p0Plugin(jdbcUrl, jdbcUser, jdbcPasswd); 73 | me.add(c3p0Plugin); 74 | 75 | ActiveRecordPlugin arp = new ActiveRecordPlugin(c3p0Plugin); 76 | arp.setShowSql(getPropertyToBoolean("devMode", false)); 77 | me.add(arp); 78 | 79 | arp.addMapping("app", App.class); 80 | arp.addMapping("history", History.class); 81 | arp.addMapping("domain", Domain.class); 82 | arp.addMapping("env", Env.class); 83 | 84 | String memcacheAddrs = getProperty("memcacheAddrs").trim(); 85 | String[] addrs = StringUtils.split(memcacheAddrs, ','); 86 | Integer[] weights = new Integer[addrs.length]; 87 | for (int i = 0; i < weights.length; i++) { 88 | weights[i] = new Integer(10); 89 | } 90 | String prefix = getProperty("memcachePrefix").trim(); 91 | MemcachePlugin memcachePlugin = new MemcachePlugin(addrs, weights); 92 | MemcachePlugin.setPrefix(prefix); 93 | me.add(memcachePlugin); 94 | } 95 | 96 | @Override 97 | public void configInterceptor(Interceptors me) { 98 | me.add(new GlobalInterceptor()); 99 | } 100 | 101 | @Override 102 | public void configHandler(Handlers me) { 103 | 104 | } 105 | 106 | public static void main(String[] args) { 107 | JFinal.start("web", 8088, "/", 5); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/controller/ApiController.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.controller; 2 | 3 | import com.jfinal.aop.Before; 4 | import com.jfinal.core.Controller; 5 | import com.ulricqin.dash.api.ServerApi; 6 | import com.ulricqin.dash.interceptor.AppFromUrlInterceptor; 7 | import com.ulricqin.dash.model.App; 8 | import com.ulricqin.frame.exception.RenderJsonMsgException; 9 | import com.ulricqin.frame.kit.StringKit; 10 | 11 | public class ApiController extends Controller { 12 | 13 | @Before(AppFromUrlInterceptor.class) 14 | public void instances() { 15 | App app = getAttr("app"); 16 | String jsonString = ServerApi.containersOf(app.getStr("name")); 17 | if (StringKit.isBlank(jsonString)) { 18 | throw new RenderJsonMsgException("curl server fail"); 19 | } 20 | 21 | renderJson(jsonString); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/controller/AppController.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.controller; 2 | 3 | import java.util.List; 4 | 5 | import com.jfinal.aop.Before; 6 | import com.jfinal.core.Controller; 7 | import com.ulricqin.dash.api.UicApi; 8 | import com.ulricqin.dash.config.Config; 9 | import com.ulricqin.dash.interceptor.AppFromUrlInterceptor; 10 | import com.ulricqin.dash.interceptor.LoginRequiredInterceptor; 11 | import com.ulricqin.dash.model.App; 12 | import com.ulricqin.dash.model.Domain; 13 | import com.ulricqin.dash.model.Env; 14 | import com.ulricqin.dash.model.History; 15 | import com.ulricqin.dash.model.Team; 16 | import com.ulricqin.dash.model.User; 17 | import com.ulricqin.frame.exception.RenderJsonMsgException; 18 | import com.ulricqin.frame.kit.Checker; 19 | import com.ulricqin.frame.kit.StringKit; 20 | 21 | @Before(LoginRequiredInterceptor.class) 22 | public class AppController extends Controller { 23 | 24 | public void create() { 25 | if (getRequest().getMethod().equalsIgnoreCase("GET")) { 26 | createGet(); 27 | } else { 28 | createPost(); 29 | } 30 | } 31 | 32 | private void createGet() { 33 | User me = getAttr("me"); 34 | List teams = UicApi.myTeams(me.getId()); 35 | setAttr("ts", teams); 36 | setAttr("uic", Config.uicExternal); 37 | setAttr("paasDomain", Config.paasDomain); 38 | setAttr("title", "create app"); 39 | render("create.html"); 40 | } 41 | 42 | private void createPost() { 43 | String health = getPara("health", "").trim(); 44 | if (StringKit.isNotBlank(health) && !health.startsWith("/")) { 45 | throw new RenderJsonMsgException("health must starts with: /"); 46 | } 47 | 48 | String name = getPara("name", ""); 49 | if (StringKit.isBlank(name)) { 50 | throw new RenderJsonMsgException("parameter name is blank"); 51 | } 52 | 53 | if (!Checker.isIdentifier(name)) { 54 | throw new RenderJsonMsgException("name should be an identifier: a-zA-Z0-9-_"); 55 | } 56 | 57 | int tid = 0; 58 | String tname = ""; 59 | String team_id_name = getPara("team", ""); 60 | if (StringKit.isNotBlank(team_id_name)) { 61 | int index = team_id_name.indexOf("-"); 62 | if (index < 0) { 63 | throw new RenderJsonMsgException("parameter team is invalid"); 64 | } 65 | 66 | String team_id = team_id_name.substring(0, index); 67 | tname = team_id_name.substring(index+1); 68 | tid = Integer.parseInt(team_id); 69 | } 70 | 71 | User me = getAttr("me"); 72 | if (!App.save(name, tid, tname, me.getId(), health)) { 73 | throw new RenderJsonMsgException("create app fail"); 74 | } 75 | 76 | renderJson(); 77 | } 78 | 79 | @Before(AppFromUrlInterceptor.class) 80 | public void delete() { 81 | App app = getAttr("app"); 82 | 83 | User me = getAttr("me"); 84 | if (!app.delete(me)) { 85 | throw new RenderJsonMsgException("delete app fail"); 86 | } 87 | 88 | renderJson(); 89 | } 90 | 91 | @Before(AppFromUrlInterceptor.class) 92 | public void env() { 93 | App app = getAttr("app"); 94 | 95 | List envs = Env.dao.findByAppId(app.getLong("id")); 96 | setAttr("envs", envs); 97 | setAttr("app", app); 98 | render("env.html"); 99 | } 100 | 101 | @Before(AppFromUrlInterceptor.class) 102 | public void domain() { 103 | App app = getAttr("app"); 104 | 105 | List domains = Domain.dao.findByAppId(app.getLong("id")); 106 | setAttr("domains", domains); 107 | setAttr("app", app); 108 | render("domain.html"); 109 | } 110 | 111 | @Before(AppFromUrlInterceptor.class) 112 | public void scale() { 113 | if (getRequest().getMethod().equalsIgnoreCase("GET")) { 114 | scaleGet(); 115 | } else { 116 | scalePost(); 117 | } 118 | } 119 | 120 | private void scalePost() { 121 | App app = getAttr("app"); 122 | 123 | int instance = getParaToInt("instance", 0); 124 | if (!app.scale(instance)) { 125 | throw new RenderJsonMsgException("occur unknown error"); 126 | } 127 | 128 | renderJson(); 129 | } 130 | 131 | private void scaleGet() { 132 | App app = getAttr("app"); 133 | setAttr("app", app); 134 | render("scale.html"); 135 | } 136 | 137 | @Before(AppFromUrlInterceptor.class) 138 | public void edit() { 139 | if (getRequest().getMethod().equalsIgnoreCase("GET")) { 140 | editGet(); 141 | } else { 142 | editPost(); 143 | } 144 | } 145 | 146 | private void editPost() { 147 | App app = getAttr("app"); 148 | String health = getPara("health", "").trim(); 149 | if (StringKit.isNotBlank(health) && !health.startsWith("/")) { 150 | throw new RenderJsonMsgException("health must starts with: /"); 151 | } 152 | 153 | int tid = 0; 154 | String tname = ""; 155 | String team_id_name = getPara("team", ""); 156 | if (StringKit.isNotBlank(team_id_name)) { 157 | int index = team_id_name.indexOf("-"); 158 | if (index < 0) { 159 | throw new RenderJsonMsgException("parameter team is invalid"); 160 | } 161 | 162 | String team_id = team_id_name.substring(0, index); 163 | tname = team_id_name.substring(index+1); 164 | tid = Integer.parseInt(team_id); 165 | } 166 | 167 | if (!app.updateProfile(tid, tname, health)) { 168 | throw new RenderJsonMsgException("update team fail"); 169 | } 170 | 171 | renderJson(); 172 | } 173 | 174 | private void editGet() { 175 | App app = getAttr("app"); 176 | User me = getAttr("me"); 177 | 178 | List teams = UicApi.myTeams(me.getId()); 179 | setAttr("ts", teams); 180 | setAttr("uic", Config.uicExternal); 181 | setAttr("app", app); 182 | render("edit.html"); 183 | } 184 | 185 | @Before(AppFromUrlInterceptor.class) 186 | public void history() { 187 | App app = getAttr("app"); 188 | 189 | List list = History.getByAppId(app.getLong("id")); 190 | setAttr("list", list); 191 | setAttr("app", app); 192 | setAttr("title", "build history"); 193 | render("history.html"); 194 | } 195 | 196 | @Before(AppFromUrlInterceptor.class) 197 | public void instance() { 198 | App app = getAttr("app"); 199 | setAttr("app", app); 200 | setAttr("paasDomain", Config.paasDomain); 201 | setAttr("title", "instances"); 202 | render("instance.html"); 203 | } 204 | 205 | @Before(AppFromUrlInterceptor.class) 206 | public void deploy() { 207 | if (getRequest().getMethod().equalsIgnoreCase("GET")) { 208 | deployGet(); 209 | } else { 210 | deployPost(); 211 | } 212 | } 213 | 214 | private void deployGet() { 215 | App app = getAttr("app"); 216 | setAttr("app", app); 217 | 218 | int historyId = getParaToInt("history", 0); 219 | if (historyId > 0) { 220 | setAttr("history", History.dao.findById(historyId)); 221 | } 222 | 223 | setAttr("builder", Config.builder); 224 | setAttr("title", "deploy"); 225 | render("deploy.html"); 226 | } 227 | 228 | private void deployPost() { 229 | App app = getAttr("app"); 230 | int memory = getParaToInt("memory", 0); 231 | if (memory < 128 || memory > 10240) { 232 | throw new RenderJsonMsgException("memory should >=128 or <= 10240"); 233 | } 234 | 235 | int instance = getParaToInt("instance", 0); 236 | String image = getPara("image", ""); 237 | if (StringKit.isBlank(image)) { 238 | throw new RenderJsonMsgException("image is blank"); 239 | } 240 | 241 | String resume = getPara("resume", ""); 242 | if (!app.deploy(memory, instance, image, resume)) { 243 | throw new RenderJsonMsgException("occur unknown error"); 244 | } 245 | 246 | renderJson(); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/controller/AuthController.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.controller; 2 | 3 | import java.io.IOException; 4 | 5 | import org.apache.http.HttpResponse; 6 | import org.apache.http.StatusLine; 7 | import org.apache.http.client.fluent.Request; 8 | 9 | import com.jfinal.core.Controller; 10 | import com.ulricqin.dash.config.Config; 11 | import com.ulricqin.frame.exception.RenderJsonMsgException; 12 | import com.ulricqin.frame.kit.StringKit; 13 | 14 | public class AuthController extends Controller { 15 | 16 | public void logout() { 17 | String sig = getCookie("sig"); 18 | if (StringKit.isBlank(sig)) { 19 | throw new RenderJsonMsgException("u'r not login"); 20 | } 21 | 22 | HttpResponse resp = null; 23 | try { 24 | resp = Request.Get(Config.uicInternal + "/sso/logout/" + sig + "?token=" + Config.token).execute() 25 | .returnResponse(); 26 | } catch (IOException e) { 27 | throw new RenderJsonMsgException(e.getMessage()); 28 | } 29 | 30 | StatusLine statusLine = resp.getStatusLine(); 31 | if (statusLine.getStatusCode() != 200 && statusLine.getStatusCode() != 404) { 32 | throw new RenderJsonMsgException("curl " + Config.uicInternal + "/sso/logout/" + sig + " fail"); 33 | } 34 | 35 | setCookie("sig", "", 0, "/"); 36 | redirect("/"); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/controller/CacheController.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.controller; 2 | 3 | import com.jfinal.aop.Before; 4 | import com.jfinal.core.Controller; 5 | import com.ulricqin.dash.interceptor.LoginRequiredInterceptor; 6 | import com.ulricqin.dash.model.User; 7 | import com.ulricqin.jfinal.ext.plugin.MemcachePlugin; 8 | 9 | @Before(LoginRequiredInterceptor.class) 10 | public class CacheController extends Controller { 11 | 12 | public void flush() { 13 | User me = getAttr("me"); 14 | if (me.getRole() > 0) { 15 | renderText("" + MemcachePlugin.flushAll()); 16 | } else { 17 | renderText("no privilege"); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/controller/DomainController.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.controller; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.jfinal.aop.Before; 7 | import com.jfinal.core.Controller; 8 | import com.ulricqin.dash.api.UicApi; 9 | import com.ulricqin.dash.config.Config; 10 | import com.ulricqin.dash.interceptor.DomainFromUrlInterceptor; 11 | import com.ulricqin.dash.interceptor.LoginRequiredInterceptor; 12 | import com.ulricqin.dash.model.App; 13 | import com.ulricqin.dash.model.Domain; 14 | import com.ulricqin.dash.model.Team; 15 | import com.ulricqin.dash.model.User; 16 | import com.ulricqin.frame.exception.RenderJsonMsgException; 17 | import com.ulricqin.frame.kit.Checker; 18 | import com.ulricqin.frame.kit.StringKit; 19 | 20 | @Before(LoginRequiredInterceptor.class) 21 | public class DomainController extends Controller { 22 | 23 | public void index() { 24 | User me = getAttr("me"); 25 | List ts = UicApi.myTeams(me.getId()); 26 | List tids = new ArrayList(); 27 | if (ts != null && ts.size() > 0) { 28 | for (Team t : ts) { 29 | tids.add(t.getId()); 30 | } 31 | } 32 | 33 | List ds = Domain.mine(me.getId(), tids); 34 | setAttr("ds", ds); 35 | 36 | setAttr("title", "Domains"); 37 | render("index.html"); 38 | } 39 | 40 | public void create() { 41 | if (getRequest().getMethod().equalsIgnoreCase("GET")) { 42 | createGet(); 43 | } else { 44 | createPost(); 45 | } 46 | } 47 | 48 | private void createGet() { 49 | User me = getAttr("me"); 50 | List teams = UicApi.myTeams(me.getId()); 51 | setAttr("ts", teams); 52 | setAttr("uic", Config.uicExternal); 53 | setAttr("title", "create app"); 54 | render("create.html"); 55 | } 56 | 57 | private void createPost() { 58 | String domain = getPara("domain", ""); 59 | if (StringKit.isBlank(domain)) { 60 | throw new RenderJsonMsgException("parameter domain is blank"); 61 | } 62 | 63 | Domain dObj = Domain.dao.findByName(domain); 64 | if (dObj != null) { 65 | throw new RenderJsonMsgException("domain has already existent. creator: " + dObj.getStr("creator_name")); 66 | } 67 | 68 | if (!Checker.isDomain(domain)) { 69 | throw new RenderJsonMsgException("domain format error"); 70 | } 71 | 72 | int tid = 0; 73 | String tname = ""; 74 | String team_id_name = getPara("team", ""); 75 | if (StringKit.isNotBlank(team_id_name)) { 76 | int index = team_id_name.indexOf("-"); 77 | if (index < 0) { 78 | throw new RenderJsonMsgException("parameter team is invalid"); 79 | } 80 | 81 | String team_id = team_id_name.substring(0, index); 82 | tname = team_id_name.substring(index+1); 83 | tid = Integer.parseInt(team_id); 84 | } 85 | 86 | User me = getAttr("me"); 87 | if (!Domain.save(domain, tid, tname, me)) { 88 | throw new RenderJsonMsgException("create domain fail"); 89 | } 90 | 91 | renderJson(); 92 | } 93 | 94 | @Before(DomainFromUrlInterceptor.class) 95 | public void edit() { 96 | if (getRequest().getMethod().equalsIgnoreCase("GET")) { 97 | editGet(); 98 | } else { 99 | editPost(); 100 | } 101 | } 102 | 103 | private void editGet() { 104 | Domain domain = getAttr("domain"); 105 | User me = getAttr("me"); 106 | 107 | List teams = UicApi.myTeams(me.getId()); 108 | setAttr("ts", teams); 109 | setAttr("uic", Config.uicExternal); 110 | setAttr("d", domain); 111 | render("edit.html"); 112 | } 113 | 114 | private void editPost() { 115 | Domain domain = getAttr("domain"); 116 | 117 | int tid = 0; 118 | String tname = ""; 119 | String team_id_name = getPara("team", ""); 120 | if (StringKit.isNotBlank(team_id_name)) { 121 | int index = team_id_name.indexOf("-"); 122 | if (index < 0) { 123 | throw new RenderJsonMsgException("parameter team is invalid"); 124 | } 125 | 126 | String team_id = team_id_name.substring(0, index); 127 | tname = team_id_name.substring(index+1); 128 | tid = Integer.parseInt(team_id); 129 | } 130 | 131 | if (!domain.updateTeam(tid, tname)) { 132 | throw new RenderJsonMsgException("update team fail"); 133 | } 134 | 135 | renderJson(); 136 | } 137 | 138 | @Before(DomainFromUrlInterceptor.class) 139 | public void bind() { 140 | if (getRequest().getMethod().equalsIgnoreCase("GET")) { 141 | bindGet(); 142 | } else { 143 | bindPost(); 144 | } 145 | } 146 | 147 | private void bindGet() { 148 | Domain domain = getAttr("domain"); 149 | setAttr("d", domain); 150 | 151 | User me = getAttr("me"); 152 | List ts = UicApi.myTeams(me.getId()); 153 | List tids = new ArrayList(); 154 | if (ts != null && ts.size() > 0) { 155 | for (Team t : ts) { 156 | tids.add(t.getId()); 157 | } 158 | } 159 | 160 | List apps = App.mine(me.getId(), tids); 161 | setAttr("apps", apps); 162 | render("bind.html"); 163 | } 164 | 165 | private void bindPost() { 166 | Domain d = getAttr("domain"); 167 | 168 | long appId = getParaToInt("app_id", 0); 169 | if (appId < 1) { 170 | throw new RenderJsonMsgException("parameter app_id is invalid"); 171 | } 172 | 173 | App app = App.dao.findById(appId); 174 | if (app == null) { 175 | throw new RenderJsonMsgException("no such app"); 176 | } 177 | 178 | User me = getAttr("me"); 179 | 180 | d.set("app_id", appId); 181 | d.set("app_name", app.getStr("name")); 182 | d.set("bind_user_id", me.getId()); 183 | 184 | if (!d.update()) { 185 | throw new RenderJsonMsgException("occur unknown error"); 186 | } 187 | 188 | renderJson(); 189 | } 190 | 191 | @Before(DomainFromUrlInterceptor.class) 192 | public void delete() { 193 | Domain d = getAttr("domain"); 194 | 195 | if (!d.delete()) { 196 | throw new RenderJsonMsgException("occur unknown error"); 197 | } 198 | 199 | renderJson(); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/controller/EnvController.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.controller; 2 | 3 | import com.jfinal.aop.Before; 4 | import com.jfinal.core.Controller; 5 | import com.ulricqin.dash.interceptor.LoginRequiredInterceptor; 6 | import com.ulricqin.dash.model.App; 7 | import com.ulricqin.dash.model.Env; 8 | import com.ulricqin.frame.exception.RenderJsonMsgException; 9 | import com.ulricqin.frame.kit.StringKit; 10 | 11 | @Before(LoginRequiredInterceptor.class) 12 | public class EnvController extends Controller { 13 | 14 | public void add() { 15 | String k = getPara("k", ""); 16 | String v = getPara("v", ""); 17 | long appId = getParaToInt("app_id", 0); 18 | if (appId < 1) { 19 | throw new RenderJsonMsgException("parameter app_id is invalid"); 20 | } 21 | 22 | App app = App.dao.findById(appId); 23 | if (app == null) { 24 | throw new RenderJsonMsgException("no such app"); 25 | } 26 | 27 | if (StringKit.isBlank(k)) { 28 | throw new RenderJsonMsgException("parameter k is blank"); 29 | } 30 | 31 | Env env = new Env(); 32 | env.set("k", k); 33 | env.set("v", v); 34 | env.set("app_id", appId); 35 | env.set("app_name", app.getStr("name")); 36 | if (!env.save()) { 37 | throw new RenderJsonMsgException("occur unknown error"); 38 | } 39 | 40 | renderJson(); 41 | } 42 | 43 | public void delete() { 44 | int id = getParaToInt("id", 0); 45 | if (id < 1) { 46 | throw new RenderJsonMsgException("parameter id is invalid"); 47 | } 48 | 49 | if (!Env.dao.deleteById(id)) { 50 | throw new RenderJsonMsgException("occur unknown error"); 51 | } 52 | 53 | renderJson(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/controller/HealthController.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.controller; 2 | 3 | import com.jfinal.core.Controller; 4 | 5 | public class HealthController extends Controller { 6 | 7 | public void index() { 8 | renderText("ok"); 9 | } 10 | 11 | public void json() { 12 | setAttr("name", "Ulric"); 13 | renderJson(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/controller/HistoryController.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.controller; 2 | 3 | import com.jfinal.aop.Before; 4 | import com.jfinal.core.Controller; 5 | import com.ulricqin.dash.interceptor.LoginRequiredInterceptor; 6 | import com.ulricqin.dash.model.History; 7 | import com.ulricqin.frame.exception.RenderJsonMsgException; 8 | 9 | @Before(LoginRequiredInterceptor.class) 10 | public class HistoryController extends Controller { 11 | 12 | public void delete() { 13 | int historyId = getParaToInt(); 14 | if (historyId < 1) { 15 | throw new RenderJsonMsgException("parameter id is invalid"); 16 | } 17 | 18 | History.dao.deleteById(historyId); 19 | 20 | renderJson(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/controller/MainController.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.controller; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.jfinal.aop.Before; 7 | import com.jfinal.core.Controller; 8 | import com.ulricqin.dash.api.UicApi; 9 | import com.ulricqin.dash.config.Config; 10 | import com.ulricqin.dash.interceptor.LoginRequiredInterceptor; 11 | import com.ulricqin.dash.model.App; 12 | import com.ulricqin.dash.model.Team; 13 | import com.ulricqin.dash.model.User; 14 | 15 | @Before(LoginRequiredInterceptor.class) 16 | public class MainController extends Controller { 17 | 18 | public void index() { 19 | User me = getAttr("me"); 20 | List ts = UicApi.myTeams(me.getId()); 21 | List tids = new ArrayList(); 22 | if (ts != null && ts.size() > 0) { 23 | for (Team t : ts) { 24 | tids.add(t.getId()); 25 | } 26 | } 27 | 28 | List apps = App.mine(me.getId(), tids); 29 | setAttr("apps", apps); 30 | 31 | setAttr("paasDomain", Config.paasDomain); 32 | setAttr("title", "PaaS-Dashboard"); 33 | render("index.html"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/interceptor/AppFromUrlInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.interceptor; 2 | 3 | import com.jfinal.aop.Interceptor; 4 | import com.jfinal.core.ActionInvocation; 5 | import com.jfinal.core.Controller; 6 | import com.ulricqin.dash.model.App; 7 | import com.ulricqin.frame.exception.RenderJsonMsgException; 8 | 9 | public class AppFromUrlInterceptor implements Interceptor { 10 | 11 | @Override 12 | public void intercept(ActionInvocation ai) { 13 | Controller controller = ai.getController(); 14 | int appId = controller.getParaToInt(); 15 | if (appId < 1) { 16 | throw new RenderJsonMsgException("parameter id is invalid"); 17 | } 18 | 19 | App app = App.dao.findById(appId); 20 | if (app == null) { 21 | throw new RenderJsonMsgException("no such app"); 22 | } 23 | 24 | controller.setAttr("app", app); 25 | ai.invoke(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/interceptor/DomainFromUrlInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.interceptor; 2 | 3 | import com.jfinal.aop.Interceptor; 4 | import com.jfinal.core.ActionInvocation; 5 | import com.jfinal.core.Controller; 6 | import com.ulricqin.dash.model.Domain; 7 | import com.ulricqin.frame.exception.RenderJsonMsgException; 8 | 9 | public class DomainFromUrlInterceptor implements Interceptor { 10 | 11 | @Override 12 | public void intercept(ActionInvocation ai) { 13 | Controller controller = ai.getController(); 14 | int domainId = controller.getParaToInt(); 15 | if (domainId < 1) { 16 | throw new RenderJsonMsgException("parameter id is invalid"); 17 | } 18 | 19 | Domain d = Domain.dao.findById(domainId); 20 | if (d == null) { 21 | throw new RenderJsonMsgException("no such domain"); 22 | } 23 | 24 | controller.setAttr("domain", d); 25 | ai.invoke(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/interceptor/GlobalInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.interceptor; 2 | 3 | import org.apache.commons.lang3.exception.ExceptionUtils; 4 | 5 | import com.jfinal.aop.Interceptor; 6 | import com.jfinal.core.ActionInvocation; 7 | import com.jfinal.core.Controller; 8 | import com.ulricqin.frame.exception.RenderJsonMsgException; 9 | import com.ulricqin.frame.kit.StringKit; 10 | 11 | public class GlobalInterceptor implements Interceptor { 12 | 13 | @Override 14 | public void intercept(ActionInvocation ai) { 15 | Controller controller = ai.getController(); 16 | try { 17 | ai.invoke(); 18 | String msg = controller.getAttr("msg"); 19 | if (StringKit.isBlank(msg)) { 20 | controller.setAttr("msg", ""); 21 | } 22 | } catch (RenderJsonMsgException e) { 23 | controller.renderJson("msg", e.getMessage()); 24 | } catch (Exception e) { 25 | String msg = ExceptionUtils.getMessage(e); 26 | System.out.println(msg); 27 | System.out.println(ExceptionUtils.getStackTrace(e)); 28 | controller.renderJson("msg", msg); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/interceptor/LoginRequiredInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.interceptor; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.net.URLEncoder; 5 | 6 | import javax.servlet.http.Cookie; 7 | import javax.servlet.http.HttpServletRequest; 8 | 9 | import com.jfinal.aop.Interceptor; 10 | import com.jfinal.core.ActionInvocation; 11 | import com.jfinal.core.Controller; 12 | import com.ulricqin.dash.api.UicApi; 13 | import com.ulricqin.dash.config.Config; 14 | import com.ulricqin.dash.model.User; 15 | import com.ulricqin.frame.exception.RenderJsonMsgException; 16 | import com.ulricqin.frame.kit.StringKit; 17 | 18 | public class LoginRequiredInterceptor implements Interceptor { 19 | 20 | @Override 21 | public void intercept(ActionInvocation ai) { 22 | Controller controller = ai.getController(); 23 | String sig = controller.getCookie("sig"); 24 | if (sig == null || sig.equals("")) { 25 | redirectToSSO(controller); 26 | return; 27 | } 28 | 29 | User user = UicApi.getUser(sig); 30 | if (user == null) { 31 | redirectToSSO(controller); 32 | return; 33 | } 34 | 35 | if (user.getRole() < 0) { 36 | redirectToSSO(controller); 37 | return; 38 | } 39 | 40 | controller.setAttr("me", user); 41 | ai.invoke(); 42 | } 43 | 44 | private void redirectToSSO(Controller controller) { 45 | String sig = UicApi.genSig(); 46 | if (StringKit.isBlank(sig)) { 47 | throw new RenderJsonMsgException("gen sig fail"); 48 | } 49 | 50 | HttpServletRequest req = controller.getRequest(); 51 | String callback = req.getRequestURL().toString(); 52 | String queryString = req.getQueryString(); 53 | if (StringKit.isNotBlank(queryString)) { 54 | callback += "?" + queryString; 55 | } 56 | 57 | String cb; 58 | try { 59 | cb = URLEncoder.encode(callback, "UTF-8"); 60 | } catch (UnsupportedEncodingException e) { 61 | throw new RenderJsonMsgException("url encoding fail:" 62 | + e.getMessage()); 63 | } 64 | 65 | controller.setCookie(genCookie(sig)); 66 | 67 | controller.redirect(Config.uicExternal + "/auth/login?sig=" + sig 68 | + "&callback=" + cb); 69 | } 70 | 71 | private Cookie genCookie(String sig) { 72 | int maxAge = 3600 * 24 * 30; 73 | Cookie cookie = new Cookie("sig", sig); 74 | cookie.setPath("/"); 75 | cookie.setMaxAge(maxAge); 76 | cookie.setHttpOnly(true); 77 | return cookie; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/model/App.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.model; 2 | 3 | import java.util.List; 4 | 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import com.jfinal.aop.Before; 8 | import com.jfinal.plugin.activerecord.Model; 9 | import com.jfinal.plugin.activerecord.tx.Tx; 10 | import com.ulricqin.dash.api.UicApi; 11 | 12 | public class App extends Model{ 13 | 14 | private static final long serialVersionUID = -7308337862247316589L; 15 | public static final App dao = new App(); 16 | 17 | public static boolean save(String name, int team_id, String team_name, int user_id, String health) { 18 | App a = new App(); 19 | a.set("name", name); 20 | a.set("team_id", team_id); 21 | a.set("team_name", team_name); 22 | a.set("creator", user_id); 23 | a.set("health", health); 24 | return a.save(); 25 | } 26 | 27 | public static List mine(int creator, List tids) { 28 | StringBuilder buf = new StringBuilder("select * from `app` where `creator` = ?"); 29 | if (tids.size() > 0) { 30 | buf.append(" or `team_id` in (").append(StringUtils.join(tids, ',')).append(")"); 31 | } 32 | buf.append(" order by name"); 33 | 34 | return dao.find(buf.toString(), creator); 35 | } 36 | 37 | public boolean delete(User creator) { 38 | if (!hasPrivilege(creator)) { 39 | return false; 40 | } 41 | 42 | return deleteInTx(creator); 43 | } 44 | 45 | @Before(Tx.class) 46 | private boolean deleteInTx(User creator) { 47 | boolean success = this.delete(); 48 | if (!success) { 49 | return false; 50 | } 51 | 52 | Domain.unbindApp(this.getLong("id")); 53 | Env.deleteByAppId(this.getLong("id")); 54 | 55 | return true; 56 | } 57 | 58 | private boolean hasPrivilege(User creator) { 59 | boolean hasPrivilege = false; 60 | if (this.getLong("creator").intValue() == creator.getId()) { 61 | hasPrivilege = true; 62 | } else { 63 | List teams = UicApi.myTeams(creator.getId()); 64 | for (Team team : teams) { 65 | if (team.getId() == this.getLong("team_id").intValue() && team.getName().equals(this.getStr("team_name"))) { 66 | hasPrivilege = true; 67 | } 68 | } 69 | } 70 | return hasPrivilege; 71 | } 72 | 73 | public boolean scale(int instance) { 74 | this.set("instance", instance); 75 | this.set("status", 0); 76 | return this.update(); 77 | } 78 | 79 | public boolean updateProfile(int tid, String tname, String health) { 80 | this.set("team_id", tid); 81 | this.set("team_name", tname); 82 | this.set("health", health); 83 | return this.update(); 84 | } 85 | 86 | @Before(Tx.class) 87 | public boolean deploy(int memory, int instance, String image, 88 | String resume) { 89 | History his = new History(); 90 | his.set("image", image); 91 | his.set("resume", resume); 92 | his.set("app_id", this.getLong("id")); 93 | his.set("app_name", this.getStr("name")); 94 | 95 | this.set("memory", memory); 96 | this.set("instance", instance); 97 | this.set("image", image); 98 | this.set("status", 0); 99 | 100 | return his.save() && this.update(); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/model/Domain.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.model; 2 | 3 | import java.util.List; 4 | 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import com.jfinal.plugin.activerecord.Db; 8 | import com.jfinal.plugin.activerecord.Model; 9 | 10 | public class Domain extends Model{ 11 | 12 | private static final long serialVersionUID = -9021991906913012024L; 13 | public static final Domain dao = new Domain(); 14 | 15 | public static void deleteByAppId(Long appId) { 16 | Db.update("delete from domain where app_id = ?", appId); 17 | } 18 | 19 | public static void unbindApp(Long appId) { 20 | Db.update("update domain set app_id = 0, app_name = '' where app_id = ?", appId); 21 | } 22 | 23 | public List findByAppId(long appId) { 24 | return dao.find("select * from `domain` where app_id = ?", appId); 25 | } 26 | 27 | public Domain findByName(String domain) { 28 | return dao.findFirst("select * from `domain` where `domain` = ?", domain); 29 | } 30 | 31 | public static List mine(int creator, List tids) { 32 | StringBuilder buf = new StringBuilder("select * from `domain` where `creator` = ?"); 33 | if (tids.size() > 0) { 34 | buf.append(" or `team_id` in (").append(StringUtils.join(tids, ',')).append(")"); 35 | } 36 | buf.append(" order by domain"); 37 | 38 | return dao.find(buf.toString(), creator); 39 | } 40 | 41 | public static boolean save(String domain, int tid, String tname, User u) { 42 | Domain d = new Domain(); 43 | d.set("domain", domain); 44 | d.set("team_id", tid); 45 | d.set("team_name", tname); 46 | d.set("creator", u.getId()); 47 | d.set("creator_name", u.getName()); 48 | d.set("bind_user_id", u.getId()); 49 | return d.save(); 50 | } 51 | 52 | public boolean updateTeam(int tid, String tname) { 53 | this.set("team_id", tid); 54 | this.set("team_name", tname); 55 | return this.update(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/model/Env.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.model; 2 | 3 | import java.util.List; 4 | 5 | import com.jfinal.plugin.activerecord.Db; 6 | import com.jfinal.plugin.activerecord.Model; 7 | 8 | public class Env extends Model{ 9 | 10 | private static final long serialVersionUID = -2098540094986122367L; 11 | public static final Env dao = new Env(); 12 | 13 | public static void deleteByAppId(Long appId) { 14 | Db.update("delete from `env` where app_id = ?", appId); 15 | } 16 | 17 | public List findByAppId(long appId) { 18 | return dao.find("select * from `env` where app_id = ?", appId); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/model/History.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.model; 2 | 3 | import java.util.List; 4 | 5 | import com.jfinal.plugin.activerecord.Model; 6 | 7 | public class History extends Model{ 8 | 9 | private static final long serialVersionUID = 2247813999236765493L; 10 | public static final History dao = new History(); 11 | 12 | public static List getByAppId(long appId) { 13 | return dao.find("select * from `history` where app_id = ? order by id desc", appId); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/model/Team.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.model; 2 | 3 | public class Team implements java.io.Serializable { 4 | 5 | private static final long serialVersionUID = -8072209407169886500L; 6 | 7 | private int id; 8 | private String name; 9 | 10 | public Team() { 11 | 12 | } 13 | 14 | public Team(int id, String name) { 15 | this.id = id; 16 | this.name = name; 17 | } 18 | 19 | public int getId() { 20 | return id; 21 | } 22 | 23 | public void setId(int id) { 24 | this.id = id; 25 | } 26 | 27 | public String getName() { 28 | return name; 29 | } 30 | 31 | public void setName(String name) { 32 | this.name = name; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return "Team [id=" + id + ", name=" + name + "]"; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/com/ulricqin/dash/model/User.java: -------------------------------------------------------------------------------- 1 | package com.ulricqin.dash.model; 2 | 3 | public class User implements java.io.Serializable { 4 | 5 | private static final long serialVersionUID = 1674809083412836887L; 6 | 7 | private int id; 8 | private String name; 9 | private String cnname; 10 | private String email; 11 | private String phone; 12 | private String im; 13 | private String qq; 14 | private int role; 15 | private int creator; 16 | private String created; 17 | 18 | public int getId() { 19 | return id; 20 | } 21 | 22 | public void setId(int id) { 23 | this.id = id; 24 | } 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | public void setName(String name) { 31 | this.name = name; 32 | } 33 | 34 | public String getCnname() { 35 | return cnname; 36 | } 37 | 38 | public void setCnname(String cnname) { 39 | this.cnname = cnname; 40 | } 41 | 42 | public String getEmail() { 43 | return email; 44 | } 45 | 46 | public void setEmail(String email) { 47 | this.email = email; 48 | } 49 | 50 | public String getPhone() { 51 | return phone; 52 | } 53 | 54 | public void setPhone(String phone) { 55 | this.phone = phone; 56 | } 57 | 58 | public String getIm() { 59 | return im; 60 | } 61 | 62 | public void setIm(String im) { 63 | this.im = im; 64 | } 65 | 66 | public String getQq() { 67 | return qq; 68 | } 69 | 70 | public void setQq(String qq) { 71 | this.qq = qq; 72 | } 73 | 74 | public int getRole() { 75 | return role; 76 | } 77 | 78 | public void setRole(int role) { 79 | this.role = role; 80 | } 81 | 82 | public int getCreator() { 83 | return creator; 84 | } 85 | 86 | public void setCreator(int creator) { 87 | this.creator = creator; 88 | } 89 | 90 | public String getCreated() { 91 | return created; 92 | } 93 | 94 | public void setCreated(String created) { 95 | this.created = created; 96 | } 97 | 98 | @Override 99 | public String toString() { 100 | return "User [id=" + id + ", name=" + name + ", cnname=" + cnname 101 | + ", email=" + email + ", phone=" + phone + ", im=" + im 102 | + ", qq=" + qq + ", role=" + role + ", creator=" + creator 103 | + ", created=" + created + "]"; 104 | } 105 | 106 | 107 | 108 | } 109 | -------------------------------------------------------------------------------- /web/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Class-Path: 3 | 4 | -------------------------------------------------------------------------------- /web/WEB-INF/config.txt.example: -------------------------------------------------------------------------------- 1 | jdbcUrl = jdbc:mysql://127.0.0.1/dash?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull 2 | user = dinp 3 | password = 4 | devMode = false 5 | memcacheAddrs = 127.0.0.1:11211 6 | memcachePrefix = dash: 7 | uicInternal = http://uic.com 8 | uicExternal = http://uic.com 9 | builder = http://build.com 10 | server = http://127.0.0.1:1980 11 | paasDomain = apps.io 12 | token = same-to-uic 13 | -------------------------------------------------------------------------------- /web/WEB-INF/lib/c3p0-0.9.1.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/WEB-INF/lib/c3p0-0.9.1.2.jar -------------------------------------------------------------------------------- /web/WEB-INF/lib/commons-codec-1.6.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/WEB-INF/lib/commons-codec-1.6.jar -------------------------------------------------------------------------------- /web/WEB-INF/lib/commons-lang3-3.3.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/WEB-INF/lib/commons-lang3-3.3.2.jar -------------------------------------------------------------------------------- /web/WEB-INF/lib/commons-logging-1.1.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/WEB-INF/lib/commons-logging-1.1.3.jar -------------------------------------------------------------------------------- /web/WEB-INF/lib/commons-pool-1.5.6.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/WEB-INF/lib/commons-pool-1.5.6.jar -------------------------------------------------------------------------------- /web/WEB-INF/lib/fastjson-1.1.43.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/WEB-INF/lib/fastjson-1.1.43.jar -------------------------------------------------------------------------------- /web/WEB-INF/lib/fluent-hc-4.3.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/WEB-INF/lib/fluent-hc-4.3.5.jar -------------------------------------------------------------------------------- /web/WEB-INF/lib/freemarker-2.3.20.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/WEB-INF/lib/freemarker-2.3.20.jar -------------------------------------------------------------------------------- /web/WEB-INF/lib/httpclient-4.3.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/WEB-INF/lib/httpclient-4.3.5.jar -------------------------------------------------------------------------------- /web/WEB-INF/lib/httpclient-cache-4.3.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/WEB-INF/lib/httpclient-cache-4.3.5.jar -------------------------------------------------------------------------------- /web/WEB-INF/lib/httpcore-4.3.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/WEB-INF/lib/httpcore-4.3.2.jar -------------------------------------------------------------------------------- /web/WEB-INF/lib/httpmime-4.3.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/WEB-INF/lib/httpmime-4.3.5.jar -------------------------------------------------------------------------------- /web/WEB-INF/lib/java_memcached-release_2.6.6.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/WEB-INF/lib/java_memcached-release_2.6.6.jar -------------------------------------------------------------------------------- /web/WEB-INF/lib/jfinal-1.8-bin-with-src.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/WEB-INF/lib/jfinal-1.8-bin-with-src.jar -------------------------------------------------------------------------------- /web/WEB-INF/lib/log4j-1.2.16.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/WEB-INF/lib/log4j-1.2.16.jar -------------------------------------------------------------------------------- /web/WEB-INF/lib/mysql-connector-java-5.1.20-bin.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/WEB-INF/lib/mysql-connector-java-5.1.20-bin.jar -------------------------------------------------------------------------------- /web/WEB-INF/lib/slf4j-api-1.6.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/WEB-INF/lib/slf4j-api-1.6.1.jar -------------------------------------------------------------------------------- /web/WEB-INF/lib/slf4j-log4j12-1.6.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/WEB-INF/lib/slf4j-log4j12-1.6.1.jar -------------------------------------------------------------------------------- /web/WEB-INF/tpl/app/create.html: -------------------------------------------------------------------------------- 1 | <#include "../common/_layout.html"/> <@layout> 2 | 3 |
4 |
5 |

create app

6 |
7 |
8 |
9 | 11 |
12 |
13 | 18 |
19 |
20 | 21 | 22 |
23 | 25 | Cancel 26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /web/WEB-INF/tpl/app/deploy.html: -------------------------------------------------------------------------------- 1 | <#include "../common/_layout.html"/> <@layout> 2 | 3 |
4 |
5 |

deploy ${app.name}:

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 | 32 | Cancel 33 | 34 | 35 |
36 |
37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /web/WEB-INF/tpl/app/edit.html: -------------------------------------------------------------------------------- 1 | <#include "../common/_layout.html"/> <@layout> 2 | 3 |
4 |
5 |

edit app: ${app.name}

6 |
7 |
8 |
9 | 12 | 21 |
22 |
23 | 24 | 25 |
26 | 27 | 29 | Cancel 30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /web/WEB-INF/tpl/app/env.html: -------------------------------------------------------------------------------- 1 | <#include "../common/_layout.html"/> <@layout> 2 | 3 |
4 |
5 |

app ${app.name} env variables:

6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | <#list envs as e> 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
KeyValueOperation
${e.k}${e.v}delete
26 |
27 |
28 | 30 |
31 |
32 | 34 |
35 | 36 | 37 |
38 |
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /web/WEB-INF/tpl/app/history.html: -------------------------------------------------------------------------------- 1 | <#include "../common/_layout.html"/> <@layout> 2 | 3 |
4 |
5 |

build history of ${app.name}:

6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | <#list list as x> 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 |
ImageResumeCreateAtOperation
${x.image}${x.resume}${x.create_at}use 24 | ¦ 25 | delete
30 |
31 |
32 | 33 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /web/WEB-INF/tpl/app/instance.html: -------------------------------------------------------------------------------- 1 | <#include "../common/_layout.html"/> <@layout> 2 | 22 |
23 |
24 |

running instances of ${app.name}:

25 |
26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
#IDIPPortStatus
41 |
42 |
43 | 44 | refresh 45 | 46 | 47 | -------------------------------------------------------------------------------- /web/WEB-INF/tpl/app/scale.html: -------------------------------------------------------------------------------- 1 | <#include "../common/_layout.html"/> <@layout> 2 | 3 |
4 |
5 |

Scale Application ${app.name}:

6 |
7 |
8 | 9 |
10 | 12 |
13 | 14 | 15 | Cancel 16 | 17 |
18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /web/WEB-INF/tpl/common/_blank.html: -------------------------------------------------------------------------------- 1 | <#macro layout> 2 | 3 | 4 | 5 | 6 | 7 | 8 | ${title!} 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <#nested> 26 | 27 | 28 | -------------------------------------------------------------------------------- /web/WEB-INF/tpl/common/_layout.html: -------------------------------------------------------------------------------- 1 | <#macro layout> 2 | 3 | 4 | 5 | 6 | 7 | 8 | ${title!} 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |
27 |
28 |
29 |
30 |
31 | 33 |
34 |
35 |

PaaS Dashboard

36 |
37 |
38 | welcome, ${(me.name)!} ¦ logout 40 |
41 |
42 |
43 |
44 | Domains 45 | Apps 46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | <#nested> 54 |
55 |
56 |
57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /web/WEB-INF/tpl/common/_paginate.html: -------------------------------------------------------------------------------- 1 | <#macro paginate currentPage totalPage actionUrl urlParas=""> 2 | <#if (totalPage <= 0) || (currentPage > totalPage)><#return> 3 | <#local startPage = currentPage - 4> 4 | <#if (startPage < 1)><#local startPage = 1> 5 | 6 | <#local endPage = currentPage + 4> 7 | <#if (endPage > totalPage)><#local endPage = totalPage> 8 | 9 |
    10 | <#if (currentPage <= 8)> 11 | <#local startPage = 1> 12 | 13 | <#if ((totalPage - currentPage) < 8)> 14 | <#local endPage = totalPage> 15 | 16 | 17 | <#if (currentPage == 1)> 18 |
  • 上页
  • 19 | <#else> 20 |
  • 上页
  • 21 | 22 | 23 | <#if (currentPage > 8)> 24 |
  • #{1}
  • 25 |
  • #{2}
  • 26 |
  • 27 | 28 | 29 | <#list startPage..endPage as i> 30 | <#if currentPage == i> 31 |
  • #{i}
  • 32 | <#else> 33 |
  • #{i}
  • 34 | 35 | 36 | 37 | <#if ((totalPage - currentPage) >= 8)> 38 |
  • 39 |
  • #{totalPage - 1}
  • 40 |
  • #{totalPage}
  • 41 | 42 | 43 | <#if (currentPage == totalPage)> 44 |
  • 下页
  • 45 | <#else> 46 |
  • 下页
  • 47 | 48 |
49 | -------------------------------------------------------------------------------- /web/WEB-INF/tpl/domain/bind.html: -------------------------------------------------------------------------------- 1 | <#include "../common/_layout.html"/> <@layout> 2 | 3 |
4 |
5 |

${d.domain} bind app:

6 |
7 |
8 |
9 | 10 | 19 |
20 | 21 | 23 | Cancel 24 |
25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /web/WEB-INF/tpl/domain/create.html: -------------------------------------------------------------------------------- 1 | <#include "../common/_layout.html"/> <@layout> 2 | 3 |
4 |
5 |

create domain

6 |
7 |
8 |
9 | 10 |
11 |
12 | 17 |
18 | 20 | Cancel 21 |
22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /web/WEB-INF/tpl/domain/edit.html: -------------------------------------------------------------------------------- 1 | <#include "../common/_layout.html"/> <@layout> 2 | 3 |
4 |
5 |

edit domain: ${d.domain}

6 |
7 |
8 |
9 | 12 | 21 |
22 | 23 | 25 | Cancel 26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /web/WEB-INF/tpl/domain/index.html: -------------------------------------------------------------------------------- 1 | <#include "../common/_layout.html"/> <@layout> 2 | 3 |
4 |
5 |

Domains

6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | <#list ds as o> 20 | 21 | 22 | 28 | 36 | 39 | 40 | 41 | 42 |
DomainTeamAppNameOperation
${o.domain} 23 | ${o.team_name} 24 | <#if (me.id == o.creator)> 25 | 26 | 27 | 29 | <#if o.app_id == 0> 30 | bind app 31 | <#else> 32 | ${o.app_name} 33 | 34 | 35 | 37 | 38 |
43 | 44 |
45 |
46 | 47 |
48 | Create Domain 49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /web/WEB-INF/tpl/index.html: -------------------------------------------------------------------------------- 1 | <#include "common/_layout.html"/> <@layout> 2 | 3 |
4 |
5 |

Applications

6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | <#list apps as a> 21 | 22 | 23 | 24 | 25 | 28 | 42 | 43 | 44 | 45 |
NameMemory(in MB)InstanceTeamOperation
${a.name}${a.memory}${a.instance} total 26 | ${a.team_name} 27 | 29 | deploy 30 | ¦ 31 | scale 32 | ¦ 33 | instances 34 | ¦ 35 | env 36 | ¦ 37 | history 38 | ¦ 39 | 40 | 41 |
46 | 47 |
48 |
49 | 50 |
51 | create app 52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /web/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | dash 8 | 9 | 10 | JFinal 11 | com.jfinal.core.JFinalFilter 12 | 13 | configClass 14 | com.ulricqin.dash.config.Config 15 | 16 | 17 | 18 | 19 | JFinal 20 | /* 21 | 22 | 23 | 24 | index.html 25 | index.htm 26 | index.jsp 27 | default.html 28 | default.htm 29 | default.jsp 30 | 31 | -------------------------------------------------------------------------------- /web/css/g.css: -------------------------------------------------------------------------------- 1 | body, input, div, span, h1, h2, h3, h4, h5, table, th, td, ul, li, dl, 2 | dt, dd, a { 3 | font-family: 'verdana', 'Microsoft YaHei', 'Consolas', 4 | 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono'; 5 | } 6 | 7 | .cut-line { 8 | margin: 0 5px; 9 | color: #D6D6D6; 10 | } 11 | 12 | .red { 13 | color: red; 14 | } 15 | 16 | .orange { 17 | color: orange; 18 | } 19 | 20 | .green { 21 | color: green; 22 | } 23 | 24 | .blue { 25 | color: blue; 26 | } 27 | 28 | .gray { 29 | color: #999 !important; 30 | } 31 | 32 | .mt0 { 33 | margin-top: 0px; 34 | } 35 | 36 | .mt10 { 37 | margin-top: 10px !important; 38 | } 39 | 40 | .mt20 { 41 | margin-top: 20px !important; 42 | } 43 | 44 | .thin-border { 45 | padding: 10px; 46 | line-height: 1; 47 | border: 1px solid #ddd; 48 | -webkit-border-radius: 4px; 49 | -moz-border-radius: 4px; 50 | border-radius: 4px; 51 | -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); 52 | -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); 53 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); 54 | background-color: #fff; 55 | } 56 | 57 | .line .tit { 58 | font-weight: bold; 59 | } 60 | 61 | .groups .group p{ 62 | line-height: 24px; 63 | } 64 | .groups .group p code { 65 | color: #F60!important; 66 | } -------------------------------------------------------------------------------- /web/css/select2-bootstrap.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Select2 Bootstrap CSS 3 | * Compatible with Select2 3.3.2, 3.4.1, 3.4.2 and Twitter Bootstrap 3.0.0 4 | * MIT License 5 | */ 6 | /** 7 | * Reset Bootstrap 3 .form-control styles which - if applied to the 8 | * original ',yes:function(c){var e=d.prompt.val();""===e?d.prompt.focus():e.replace(/\s/g,"").length>(a.length||1e3)?layer.tips("最多输入"+(a.length||1e3)+"个字数","#xubox_prompt",2):b&&b(e,c,d.prompt)},no:c},success:function(){d.prompt=$("#xubox_prompt"),d.prompt.focus()}};return 3===a.type&&(e.dialog.msg='"),$.layer(e)},layer.tab=function(a){var a=a||{},c=a.data||[],d={type:1,border:[0],area:["auto","auto"],bgcolor:"",title:!1,shade:a.shade,offset:a.offset,move:".xubox_tabmove",closeBtn:!1,page:{html:'
'}()+''+'
'+function(){var a=c.length,b=1,d="";if(a>0)for(d=''+c[0].title+"";a>b;b++)d+=""+c[b].title+"";return d}()+"
"+'
    '+function(){var a=c.length,b=1,d="";if(a>0)for(d='
  • '+(c[0].content||"content未传入")+"
  • ";a>b;b++)d+='
  • '+(c[b].content||"content未传入")+"
  • ";return d}()+"
"+'X'+"
"},success:function(a){var b=$(".xubox_tabtit").children(),c=$(".xubox_tab_main").children(),d=$(".xubox_tabclose");b.on("click",function(){var a=$(this),b=a.index();a.addClass("xubox_tabnow").siblings().removeClass("xubox_tabnow"),c.eq(b).show().siblings().hide()}),d.on("click",function(){layer.close(a.attr("times"))})}};return $.layer(d)},layer.photos=function(a){var b,c,d,e,f,g,h,i;if(a=a||{},b={imgIndex:1,end:null,html:$("html")},c=$(window),d=a.json,e=a.page,d){if(f=d.data,1!==d.status)return layer.msg("未请求到数据",2,8),void 0;if(b.imgLen=f.length,!(f.length>0))return layer.msg("没有任何图片",2,8),void 0;b.thissrc=f[d.start].src,b.pid=f[d.start].pid,b.imgsname=d.title||"",b.name=f[d.start].name,b.imgIndex=d.start+1}else g=$(e.parent).find("img"),h=g.eq(e.start),b.thissrc=h.attr("layer-img")||h.attr("src"),b.pid=h.attr("pid"),b.imgLen=g.length,b.imgsname=e.title||"",b.name=h.attr("alt"),b.imgIndex=e.start+1;return i={type:1,border:[0],area:[(a.html?915:600)+"px","auto"],title:!1,shade:[.9,"#000",!0],shadeClose:!0,offset:["25px",""],bgcolor:"",page:{html:'
'+(b.name||
'+function(){return b.imgLen>1?'':""}()+'
'+b.imgsname+" "+b.imgIndex+"/"+b.imgLen+"
"+function(){return a.html?'
'+a.html+"
":""}()},success:function(a){b.bigimg=a.find(".xubox_bigimg"),b.imgsee=b.bigimg.find(".xubox_imgsee"),b.imgbar=b.imgsee.find(".xubox_imgbar"),b.imgtit=b.imgbar.find(".xubox_imgtit"),b.layero=a;var c=b.imgs=b.bigimg.find("img");clearTimeout(b.timerr),b.timerr=setTimeout(function(){$("html").css("overflow","hidden").attr("layer-full",b.index)},10),c.load(function(){b.imgarea=[c.outerWidth(),c.outerHeight()],b.resize(a)}),b.event()},end:function(){layer.closeAll(),b.end=!0}},b.event=function(){b.bigimg.hover(function(){b.imgsee.show()},function(){b.imgsee.hide()}),i.imgprev=function(){b.imgIndex--,b.imgIndex<1&&(b.imgIndex=b.imgLen),b.tabimg()},b.bigimg.find(".xubox_prev").on("click",function(a){a.preventDefault(),i.imgprev()}),i.imgnext=function(){b.imgIndex++,b.imgIndex>b.imgLen&&(b.imgIndex=1),b.tabimg()},b.bigimg.find(".xubox_next").on("click",function(a){a.preventDefault(),i.imgnext()}),$(document).keyup(function(a){if(!b.end){var c=a.keyCode;a.preventDefault(),37===c?i.imgprev():39===c?i.imgnext():27===c&&layer.close(b.index)}}),b.tabimg=function(){var e,h,i,j,k;b.imgs.removeAttr("style"),d?(j=f[b.imgIndex-1],e=j.src,h=j.pid,i=j.name):(k=g.eq(b.imgIndex-1),e=k.attr("layer-img")||k.attr("src"),h=k.attr("layer-pid")||"",i=k.attr("alt")||""),b.imgs.attr({src:e,"layer-pid":h,alt:i}),b.imgtit.find("em").text(b.imgIndex+"/"+b.imgLen),b.imgsee.show(),a.tab&&a.tab({pid:h,name:i})}},b.resize=function(d){var g,e={},f=[c.width(),c.height()];e.limit=f[0]-f[0]/f[1]*(60*f[0]/f[1]),e.limit<600&&(e.limit=600),g=[e.limit,f[1]>400?f[1]-50:400],g[0]=a.html?g[0]:g[0]-300,layer.area(b.index,{width:g[0]+(a.html?15:0),height:g[1]}),e.flwidth=g[0]-(a.html?300:0),b.imgarea[0]>e.flwidth?b.imgs.css({width:e.flwidth}):b.imgs.css({width:b.imgarea[0]}),b.imgs.outerHeight()',h=['
'+f+''+e.msg+"
",'
'+a+"
",'','','
'+d.tips.msg+'
'],i="",j="",k=d.zIndex+c,l="z-index:"+k+"; background-color:"+d.shade[1]+"; opacity:"+d.shade[0]+"; filter:alpha(opacity="+100*d.shade[0]+");",d.shade[0]&&(i='
'),d.zIndex=k,m="",n="",o="z-index:"+(k-1)+"; background-color: "+d.border[2]+"; opacity:"+d.border[1]+"; filter:alpha(opacity="+100*d.border[1]+"); top:-"+d.border[0]+"px; left:-"+d.border[0]+"px;",d.border[0]&&(j='
'),!d.maxmin||1!==d.type&&2!==d.type||/^\d+%$/.test(d.area[0])&&/^\d+%$/.test(d.area[1])||(n=''),d.closeBtn[1]&&(n+=''),p="object"==typeof d.title,d.title&&(m='
'+(p?d.title[0]:d.title)+"
"),[i,'
'+'
'+h[d.type]+m+''+n+""+''+"
"+j+"
"]},h.pt.creat=function(){var k,l,m,a=this,b="",c=a.config,e=c.dialog,f=a.index,h=c.page,i=d("body"),j=function(c){var c=c||"";b=a.space(c),i.append(d(b[0]))};switch(c.type){case 0:c.title||(c.area=["auto","auto"]),d(".xubox_dialog")[0]&&layer.close(d(".xubox_dialog").parents("."+g[0]).attr("times"));break;case 1:if(""!==h.html)j('
'+h.html+"
"),i.append(d(b[1]));else if(""!==h.url)j('
'+h.html+"
"),i.append(d(b[1])),d.get(h.url,function(a){d("#xuboxPageHtml"+f).html(a.toString()),h.ok&&h.ok(a)});else{if(0!=d(h.dom).parents(g[4]).length)return;j(),d(h.dom).show().wrap(d(b[1]))}break;case 3:c.title=!1,c.area=["auto","auto"],c.closeBtn=["",!1],d(".xubox_loading")[0]&&layer.closeLoad();break;case 4:c.title=!1,c.area=["auto","auto"],c.fix=!1,c.border=[0],c.tips.more||layer.closeTips()}if(1!==c.type&&(j(),i.append(d(b[1]))),k=a.layerE=d("#"+g[0]+f),k.css({width:c.area[0],height:c.area[1]}),c.fix||k.css({position:"absolute"}),c.title&&(3!==c.type||4!==c.type))switch(l=0===c.type?e:c,m=k.find(".xubox_botton"),l.btn=c.btn||e.btn,l.btns){case 0:m.html("").hide();break;case 1:m.html(''+l.btn[0]+"");break;case 2:m.html(''+l.btn[0]+""+''+l.btn[1]+"")}"auto"===k.css("left")?(k.hide(),setTimeout(function(){k.show(),a.set(f)},500)):a.set(f),c.time<=0||a.autoclose(),a.callback()},f.fade=function(a,b,c){a.css({opacity:0}).animate({opacity:c},b)},h.pt.offset=function(){var a=this,b=a.config,c=a.layerE,d=c.outerHeight();a.offsetTop=""===b.offset[0]&&dc.maxWidth&&k.width(c.maxWidth),q.tipColor=c.tips.style[1],o[0]=k.outerWidth(),q.autoLeft=function(){q.left+o[0]-e.width()>0?(q.tipLeft=q.left+q.width-o[0],r.css({right:12,left:"auto"})):q.tipLeft=q.left},q.where=[function(){q.autoLeft(),q.tipTop=q.top-o[1]-10,r.removeClass("layerTipsB").addClass("layerTipsT").css({"border-right-color":q.tipColor})},function(){q.tipLeft=q.left+q.width+10,q.tipTop=q.top,r.removeClass("layerTipsL").addClass("layerTipsR").css({"border-bottom-color":q.tipColor})},function(){q.autoLeft(),q.tipTop=q.top+q.height+10,r.removeClass("layerTipsT").addClass("layerTipsB").css({"border-right-color":q.tipColor})},function(){q.tipLeft=q.left-o[0]+10,q.tipTop=q.top,r.removeClass("layerTipsR").addClass("layerTipsL").css({"border-bottom-color":q.tipColor})}],q.where[c.tips.guide](),0===c.tips.guide?q.top-(e.scrollTop()+o[1]+16)<0&&q.where[2]():1===c.tips.guide?e.width()-(q.left+q.width+o[0]+16)>0||q.where[3]():2===c.tips.guide?q.top-e.scrollTop()+q.height+o[1]+16-e.height()>0&&q.where[0]():3===c.tips.guide?o[0]+16-q.left>0&&q.where[1]():4===c.tips.guide,k.css({left:q.tipLeft,top:q.tipTop})}c.fadeIn&&(f.fade(k,c.fadeIn,1),f.fade(d("#xubox_shade"+a),c.fadeIn,c.shade[0])),c.fix&&""===c.offset[0]&&!c.shift&&e.on("resize",function(){k.css({top:(e.height()-k.outerHeight())/2})}),b.move()},h.pt.shift=function(a,b,c){var k,d=this,f=d.config,g=d.layerE,h=0,i=e.width(),j=e.height()+(f.fix?0:e.scrollTop());switch(h="50%"==f.offset[1]||""==f.offset[1]?g.outerWidth()/2:g.outerWidth(),k={t:{top:d.offsetTop},b:{top:j-g.outerHeight()-f.border[0]},cl:h+f.border[0],ct:-g.outerHeight(),cr:i-h-f.border[0]},a){case"left-top":g.css({left:k.cl,top:k.ct}).animate(k.t,b);break;case"top":g.css({top:k.ct}).animate(k.t,b);break;case"right-top":g.css({left:k.cr,top:k.ct}).animate(k.t,b);break;case"right-bottom":g.css({left:k.cr,top:j}).animate(c?k.t:k.b,b);break;case"bottom":g.css({top:j}).animate(c?k.t:k.b,b);break;case"left-bottom":g.css({left:k.cl,top:j}).animate(c?k.t:k.b,b);break;case"left":g.css({left:-g.outerWidth()}).animate({left:d.offsetLeft},b)}},h.pt.autoArea=function(a){var c,e,f,h,i,k,j,l,m,n,o,b=this;switch(a=a||b.index,c=b.config,e=c.page,f=d("#"+g[0]+a),h=f.find(g[2]),i=f.find(g[5]),j=c.title?h.innerHeight():0,l=0,"auto"===c.area[0]&&i.outerWidth()>=c.maxWidth&&f.css({width:c.maxWidth}),c.type){case 0:m=f.find(".xubox_botton>a"),k=f.find(g[3]).outerHeight()+20,m.length>0&&(l=m.outerHeight()+20);break;case 1:n=f.find(g[4]),k=d(e.dom).outerHeight(),"auto"===c.area[0]&&f.css({width:n.outerWidth()}),(""!==e.html||""!==e.url)&&(k=n.outerHeight());break;case 2:f.find("iframe").css({width:f.outerWidth(),height:f.outerHeight()-(c.title?h.innerHeight():0)});break;case 3:o=f.find(".xubox_loading"),k=o.outerHeight(),i.css({width:o.width()})}"auto"===c.area[1]&&i.css({height:j+k+l}),d("#xubox_border"+a).css({width:f.outerWidth()+2*c.border[0],height:f.outerHeight()+2*c.border[0]}),layer.ie6&&"auto"!==c.area[0]&&i.css({width:f.outerWidth()}),"50%"!==c.offset[1]&&""!=c.offset[1]||4===c.type?f.css({marginLeft:0}):f.css({marginLeft:-f.outerWidth()/2})},h.pt.move=function(){var a=this,b=a.config,c={setY:0,moveLayer:function(){var a;a=0==parseInt(c.layerE.css("margin-left"))?parseInt(c.move.css("left")):parseInt(c.move.css("left"))+-parseInt(c.layerE.css("margin-left")),"fixed"!==c.layerE.css("position")&&(a-=c.layerE.parent().offset().left,c.setY=0),c.layerE.css({left:a,top:parseInt(c.move.css("top"))-c.setY})}},f=a.layerE.find(b.move);b.move&&f.attr("move","ok"),b.move?f.css({cursor:"move"}):f.css({cursor:"auto"}),d(b.move).on("mousedown",function(a){if(a.preventDefault(),"ok"===d(this).attr("move")){c.ismove=!0,c.layerE=d(this).parents("."+g[0]);var f=c.layerE.offset().left,h=c.layerE.offset().top,i=c.layerE.width()-6,j=c.layerE.height()-6;d("#xubox_moves")[0]||d("body").append('
'),c.move=d("#xubox_moves"),b.moveType&&c.move.css({opacity:0}),c.moveX=a.pageX-c.move.position().left,c.moveY=a.pageY-c.move.position().top,"fixed"!==c.layerE.css("position")||(c.setY=e.scrollTop())}}),d(document).mousemove(function(a){var d,f,g,h;c.ismove&&(d=a.pageX-c.moveX,f=a.pageY-c.moveY,a.preventDefault(),b.moveOut||(c.setY=e.scrollTop(),g=e.width()-c.move.outerWidth()-b.border[0],h=b.border[0]+c.setY,dg&&(d=g),h>f&&(f=h),f>e.height()-c.move.outerHeight()-b.border[0]+c.setY&&(f=e.height()-c.move.outerHeight()-b.border[0]+c.setY)),c.move.css({left:d,top:f}),b.moveType&&c.moveLayer(),d=null,f=null,g=null,h=null)}).mouseup(function(){try{c.ismove&&(c.moveLayer(),c.move.remove()),c.ismove=!1}catch(a){c.ismove=!1}b.moveEnd&&b.moveEnd()})},h.pt.autoclose=function(){var a=this,b=a.config.time,c=function(){b--,0===b&&(layer.close(a.index),clearInterval(a.autotime))};a.autotime=setInterval(c,1e3)},f.config={end:{}},h.pt.callback=function(){var a=this,b=a.layerE,c=a.config,e=c.dialog;a.openLayer(),a.config.success(b),layer.ie6&&a.IE6(b),b.find(".xubox_close").on("click",function(){c.close(a.index),layer.close(a.index)}),b.find(".xubox_yes").on("click",function(){c.yes?c.yes(a.index):e.yes(a.index)}),b.find(".xubox_no").on("click",function(){c.no?c.no(a.index):e.no(a.index),layer.close(a.index)}),a.config.shadeClose&&d("#xubox_shade"+a.index).on("click",function(){layer.close(a.index)}),b.find(".xubox_min").on("click",function(){layer.min(a.index,c),c.min&&c.min(b)}),b.find(".xubox_max").on("click",function(){d(this).hasClass("xubox_maxmin")?(layer.restore(a.index),c.restore&&c.restore(b)):(layer.full(a.index,c),c.full&&c.full(b))}),f.config.end[a.index]=c.end},f.reselect=function(){d.each(d("select"),function(){var c=d(this);c.parents("."+g[0])[0]||1==c.attr("layer")&&d("."+g[0]).length<1&&c.removeAttr("layer").show(),c=null})},h.pt.IE6=function(a){var f,b=this,c=a.offset().top;f=b.config.fix?function(){a.css({top:e.scrollTop()+c})}:function(){a.css({top:c})},f(),e.scroll(f),d.each(d("select"),function(){var c=d(this);c.parents("."+g[0])[0]||"none"==c.css("display")||c.attr({layer:"1"}).hide(),c=null})},h.pt.openLayer=function(){var a=this;a.layerE,layer.autoArea=function(b){return a.autoArea(b)},layer.shift=function(b,c,d){a.shift(b,c,d)},layer.setMove=function(){return a.move()},layer.zIndex=a.config.zIndex,layer.setTop=function(a){var b=function(){layer.zIndex++,a.css("z-index",layer.zIndex+1)};return layer.zIndex=parseInt(a[0].style.zIndex),a.on("mousedown",b),layer.zIndex}},f.isauto=function(a,b,c){"auto"===b.area[0]&&(b.area[0]=a.outerWidth()),"auto"===b.area[1]&&(b.area[1]=a.outerHeight()),a.attr({area:b.area+","+c}),a.find(".xubox_max").addClass("xubox_maxmin")},f.rescollbar=function(a){g.html.attr("layer-full")==a&&(g.html[0].style.removeProperty?g.html[0].style.removeProperty("overflow"):g.html[0].style.removeAttribute("overflow"),g.html.removeAttr("layer-full"))},layer.getIndex=function(a){return d(a).parents("."+g[0]).attr("times")},layer.getChildFrame=function(a,b){return b=b||d("."+g[1]).parents("."+g[0]).attr("times"),d("#"+g[0]+b).find("."+g[1]).contents().find(a)},layer.getFrameIndex=function(a){return d(a?"#"+a:"."+g[1]).parents("."+g[0]).attr("times")},layer.iframeAuto=function(a){var b,c,e,f,h;a=a||d("."+g[1]).parents("."+g[0]).attr("times"),b=layer.getChildFrame("body",a).outerHeight(),c=d("#"+g[0]+a),e=c.find(g[2]),f=0,e&&(f=e.height()),c.css({height:b+f}),h=-parseInt(d("#xubox_border"+a).css("top")),d("#xubox_border"+a).css({height:b+2*h+f}),d("#"+g[1]+a).css({height:b})},layer.iframeSrc=function(a,b){d("#"+g[0]+a).find("iframe").attr("src",b)},layer.area=function(a,b){var j,c=[d("#"+g[0]+a),d("#xubox_border"+a)],e=c[0].attr("type"),h=c[0].find(g[5]),i=c[0].find(g[2]);(e===f.type[1]||e===f.type[2])&&(c[0].css(b),h.css({width:b.width,height:b.height}),e===f.type[2]&&(j=c[0].find("iframe"),j.css({width:b.width,height:i?b.height-i.innerHeight():b.height})),"0px"!==c[0].css("margin-left")&&(b.hasOwnProperty("top")&&c[0].css({top:b.top-(c[1][0]?parseFloat(c[1].css("top")):0)}),b.hasOwnProperty("left")&&c[0].css({left:b.left+c[0].outerWidth()/2-(c[1][0]?parseFloat(c[1].css("left")):0)}),c[0].css({marginLeft:-c[0].outerWidth()/2})),c[1][0]&&c[1].css({width:parseFloat(b.width)-2*parseFloat(c[1].css("left")),height:parseFloat(b.height)-2*parseFloat(c[1].css("top"))}))},layer.min=function(a,b){var c=d("#"+g[0]+a),e=[c.position().top,c.position().left+parseFloat(c.css("margin-left"))];f.isauto(c,b,e),layer.area(a,{width:180,height:35}),c.find(".xubox_min").hide(),"page"===c.attr("type")&&c.find(g[4]).hide(),f.rescollbar(a)},layer.restore=function(a){var b=d("#"+g[0]+a),c=b.attr("area").split(",");b.attr("type"),layer.area(a,{width:parseFloat(c[0]),height:parseFloat(c[1]),top:parseFloat(c[2]),left:parseFloat(c[3])}),b.find(".xubox_max").removeClass("xubox_maxmin"),b.find(".xubox_min").show(),"page"===b.attr("type")&&b.find(g[4]).show(),f.rescollbar(a)},layer.full=function(a,b){var i,c=d("#"+g[0]+a),h=2*b.border[0]||6,j=[c.position().top,c.position().left+parseFloat(c.css("margin-left"))];f.isauto(c,b,j),g.html.attr("layer-full")||g.html.css("overflow","hidden").attr("layer-full",a),clearTimeout(i),i=setTimeout(function(){layer.area(a,{top:"fixed"===c.css("position")?0:e.scrollTop(),left:"fixed"===c.css("position")?0:e.scrollLeft(),width:e.width()-h,height:e.height()-h})},100)},layer.title=function(a,b){var c=d("#"+g[0]+(b||layer.index)).find(".xubox_title>em");c.html(a)},layer.close=function(a){var h,b=d("#"+g[0]+a),c=b.attr("type"),e=d("#xubox_moves, #xubox_shade"+a);if(b[0]){if(c==f.type[1])if(b.find(".xuboxPageHtml")[0])b[0].innerHTML="",b.remove();else for(b.find(".xubox_setwin,.xubox_close,.xubox_botton,.xubox_title,.xubox_border").remove(),h=0;3>h;h++)b.find(".layer_pageContent").unwrap().hide();else b[0].innerHTML="",b.remove();e.remove(),layer.ie6&&f.reselect(),f.rescollbar(a),"function"==typeof f.config.end[a]&&f.config.end[a](),delete f.config.end[a]}},layer.closeLoad=function(){layer.close(d(".xubox_loading").parents("."+g[0]).attr("times"))},layer.closeTips=function(){layer.closeAll("tips")},layer.closeAll=function(a){d.each(d("."+g[0]),function(){var b=d(this),c=a?b.attr("type")===a:1;c&&layer.close(b.attr("times")),c=null})},f.run=function(){d=jQuery,e=d(a),g.html=d("html"),layer.use("skin/layer.css"),d.layer=function(a){var b=new h(a);return b.index},(new Image).src=layer.path+"skin/default/xubox_ico0.png"},i="../../init/jquery",a.seajs?define([i],function(a,b,c){f.run(),c.exports=layer}):f.run()}(window); -------------------------------------------------------------------------------- /web/layer/skin/default/icon_ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/layer/skin/default/icon_ext.png -------------------------------------------------------------------------------- /web/layer/skin/default/textbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/layer/skin/default/textbg.png -------------------------------------------------------------------------------- /web/layer/skin/default/xubox_ico0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/layer/skin/default/xubox_ico0.png -------------------------------------------------------------------------------- /web/layer/skin/default/xubox_loading0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/layer/skin/default/xubox_loading0.gif -------------------------------------------------------------------------------- /web/layer/skin/default/xubox_loading1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/layer/skin/default/xubox_loading1.gif -------------------------------------------------------------------------------- /web/layer/skin/default/xubox_loading2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/layer/skin/default/xubox_loading2.gif -------------------------------------------------------------------------------- /web/layer/skin/default/xubox_loading3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/layer/skin/default/xubox_loading3.gif -------------------------------------------------------------------------------- /web/layer/skin/default/xubox_title0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/layer/skin/default/xubox_title0.png -------------------------------------------------------------------------------- /web/layer/skin/layer.css: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | @Name: layer's style 4 | @Date: 2012.09.15 5 | @Author: 贤心 6 | @blog: sentsin.com 7 | 8 | **/ 9 | 10 | *html{background-image:url(about:blank); background-attachment:fixed;} 11 | 12 | /** common **/ 13 | .xubox_shade, .xubox_layer{position:fixed; _position:absolute;} 14 | .xubox_shade{top:0; left:0; width:100%; height:100%; _height:expression(document.body.offsetHeight+"px");} 15 | .xubox_layer{top:150px; left:50%; height:auto; width:310px; margin-left:-155px;} 16 | .xubox_border, .xubox_title, .xubox_title i, .xubox_page, .xubox_iframe, .xubox_title em, .xubox_close, .xubox_msgico, .xubox_moves{position:absolute;} 17 | .xubox_border{border-radius: 5px;} 18 | .xubox_title{left:0; top:0;} 19 | .xubox_main{position:relative; height:100%; _float:left;} 20 | .xubox_page{top:0; left:0;} 21 | .xubox_load{background:url(default/xubox_loading0.gif) #fff center center no-repeat;} 22 | .xubox_loading{display:block; float:left; text-decoration:none; color:#FFF; _float:none; } 23 | .xulayer_png32{background:url(default/xubox_ico0.png) no-repeat;} 24 | .xubox_moves{border:3px solid #666; cursor:move; background-color:rgba(255,255,255,.3); background-color:#fff\9; filter:alpha(opacity=50);} 25 | 26 | .xubox_msgico{width:32px; height:32px; top:52px; left:15px; background:url(default/xubox_ico0.png) no-repeat;} 27 | .xubox_text{ padding-left:55px; float:left; line-height:25px; word-break:break-all; padding-right:20px; overflow:hidden; font-size:14px;} 28 | .xubox_msgtype0{background-position:-91px -38px;} 29 | .xubox_msgtype1{background-position:-128px -38px } 30 | .xubox_msgtype2{background-position:-163px -38px;} 31 | .xubox_msgtype3{background-position:-91px -75px;} 32 | .xubox_msgtype4{background-position:-163px -75px;} 33 | .xubox_msgtype5{background-position:-163px -112px;} 34 | .xubox_msgtype6{background-position:-163px -148px;} 35 | .xubox_msgtype7{background-position:-128px -75px;} 36 | .xubox_msgtype8{background-position:-91px -6px;} 37 | .xubox_msgtype9{background-position:-129px -6px;} 38 | .xubox_msgtype10{background-position:-163px -6px;} 39 | .xubox_msgtype11{background-position:-206px -6px;} 40 | .xubox_msgtype12{background-position:-206px -44px;} 41 | .xubox_msgtype13{background-position:-206px -81px;} 42 | .xubox_msgtype14{background-position:-206px -122px;} 43 | .xubox_msgtype15{background-position:-206px -157px;} 44 | .xubox_loading_0{width:60px; height:24px; background:url(default/xubox_loading0.gif) no-repeat;} 45 | .xubox_loading_1{width:37px; height:37px; background:url(default/xubox_loading1.gif) no-repeat;} 46 | .xubox_loading_2, .xubox_msgtype16{width:32px; height:32px; background:url(default/xubox_loading2.gif) no-repeat;} 47 | .xubox_loading_3{width:126px; height:22px; background:url(default/xubox_loading3.gif) no-repeat;} 48 | 49 | .xubox_setwin{position:absolute; right:10px; *right:0; top:10px; font-size:0;} 50 | .xubox_setwin a{position:relative; display:inline-block; *display:inline; *zoom:1; vertical-align:top; width: 14px; height:14px; margin-left:10px; font-size:12px; _overflow:hidden;} 51 | .xubox_setwin .xubox_min cite{position:absolute; width:14px; height:2px; left:0; top:50%; margin-top:-1px; background-color:#919191; cursor:pointer; _overflow:hidden;} 52 | .xubox_setwin .xubox_min:hover cite{background-color:#2D93CA; } 53 | .xubox_setwin .xubox_max{background-position:-6px -189px;} 54 | .xubox_setwin .xubox_max:hover{background-position:-6px -206px;} 55 | .xubox_setwin .xubox_maxmin{background-position:-29px -189px;} 56 | .xubox_setwin .xubox_maxmin:hover{background-position:-29px -206px;} 57 | .xubox_setwin .xubox_close0{ width:14px; height:14px; background-position: -31px -7px; cursor:pointer;} 58 | .xubox_setwin .xubox_close0:hover{background-position:-51px -7px;} 59 | .xubox_setwin .xubox_close1{position:absolute; right:-28px; top:-28px; width:30px; height:30px; margin-left:0; background-position:-60px -195px; *right:-18px; _right:-15px; _top:-23px; _width:14px; _height:14px; _background-position:-31px -7px;} 60 | .xubox_setwin .xubox_close1:hover{ background-position:-91px -195px; _background-position:-51px -7px;} 61 | 62 | .xubox_title{width:100%; height:35px; line-height:35px; border-bottom:1px solid #D5D5D5; background:url(default/xubox_title0.png) #EBEBEB repeat-x; font-size:14px; color:#333;} 63 | .xubox_title em{height:20px; line-height:20px; width:60%; top:7px; left:10px; font-style:normal; text-overflow: ellipsis; overflow: hidden; white-space: nowrap;} 64 | 65 | .xubox_botton a{position:absolute; bottom:10px; left:50%; background:url(default/xubox_ico0.png) repeat; text-decoration:none; color:#FFF; font-size:14px; text-align:center; font-weight:bold; overflow:hidden; } 66 | .xubox_botton a:hover{text-decoration:none; color:#FFF; } 67 | .xubox_botton .xubox_botton1{ width:79px; height:32px; line-height:32px; margin-left:-39px; background-position:-6px -34px;} 68 | .xubox_botton1:hover{background-position:-6px -72px;} 69 | .xubox_botton .xubox_botton2{margin-left:-76px; width:71px; height:29px; line-height:29px; background-position:-5px -114px;} 70 | .xubox_botton2:hover{ background-position:-5px -146px;} 71 | .xubox_botton .xubox_botton3{width:71px; height:29px; line-height:29px; margin-left:10px; background-position:-81px -114px;} 72 | .xubox_botton3:hover{background-position:-81px -146px;} 73 | .xubox_tips{position:relative; line-height:20px; min-width: 12px; padding:3px 30px 3px 10px; font-size:12px; _float:left; border-radius:3px; box-shadow: 1px 1px 3px rgba(0,0,0,.3);} 74 | .xubox_tips i.layerTipsG{ position:absolute; width:0; height:0; border-width:8px; border-color:transparent; border-style:dashed; *overflow:hidden;} 75 | .xubox_tips i.layerTipsT, .xubox_tips i.layerTipsB{left:5px; border-right-style:solid;} 76 | .xubox_tips i.layerTipsT{bottom:-8px;} 77 | .xubox_tips i.layerTipsB{top:-8px;} 78 | .xubox_tips i.layerTipsR, .xubox_tips i.layerTipsL{top:1px; border-bottom-style:solid;} 79 | .xubox_tips i.layerTipsR{left:-8px;} 80 | .xubox_tips i.layerTipsL{right:-8px;} 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /web/layer/skin/layer.ext.css: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | @Name: layer拓展样式 4 | @Date: 2012.12.13 5 | @Author: 贤心 6 | @blog: sentsin.com 7 | 8 | **/ 9 | 10 | .xubox_iconext{background:url(default/icon_ext.png) no-repeat;} 11 | 12 | /* prompt模式 */ 13 | .xubox_layer .xubox_form{width:240px; height:30px; line-height:30px; padding: 0 5px; border: 1px solid #ccc; background: url(default/textbg.png) #fff repeat-x; color:#333;} 14 | .xubox_layer .xubox_formArea{width:300px; height:100px; line-height:20px;} 15 | 16 | /* tab模式 */ 17 | .xubox_layer .xubox_tab{position:relative; background-color:#fff; box-shadow:1px 1px 50px rgba(0,0,0,.4)} 18 | .xubox_layer .xubox_tabmove{position:absolute; width:600px; height:30px; top:0; left:0;} 19 | .xubox_layer .xubox_tabtit{ display:block; height:34px; border-bottom:1px solid #ccc; background-color:#eee;} 20 | .xubox_layer .xubox_tabtit span{position:relative; float:left; width:120px; height:34px; line-height:34px; text-align:center; cursor:default;} 21 | .xubox_layer .xubox_tabtit span.xubox_tabnow{left:-1px; _top:1px; height:35px; border-left:1px solid #ccc; border-right:1px solid #ccc; background-color:#fff; z-index:10;} 22 | .xubox_layer .xubox_tab_main{line-height:24px; clear:both;} 23 | .xubox_layer .xubox_tab_main .xubox_tabli{display:none;} 24 | .xubox_layer .xubox_tab_main .xubox_tabli.xubox_tab_layer{display:block;} 25 | .xubox_layer .xubox_tabclose{position:absolute; right:10px; top:5px; cursor:pointer;} 26 | 27 | /* photo模式 */ 28 | .xubox_bigimg, .xubox_intro{height:300px} 29 | .xubox_bigimg{position:relative; display:block; width:600px; text-align:center; background:url(default/xubox_loading1.gif) center center no-repeat #000; overflow:hidden; } 30 | .xubox_bigimg img{position:relative; display:inline-block; visibility: hidden;} 31 | .xubox_intro{position:absolute; right:-315px; top:0; width:300px; background-color:#fff; overflow-x:hidden; overflow-y:auto;} 32 | .xubox_imgsee{display:none;} 33 | .xubox_prev, .xubox_next{position:absolute; top:50%; width:27px; _width:44px; height:44px; margin-top:-22px; outline:none;blr:expression(this.onFocus=this.blur());} 34 | .xubox_prev{left:10px; background-position:-5px -5px; _background-position:-70px -5px;} 35 | .xubox_prev:hover{background-position:-33px -5px; _background-position:-120px -5px;} 36 | .xubox_next{right:10px; _right:8px; background-position:-5px -50px; _background-position:-70px -50px;} 37 | .xubox_next:hover{background-position:-33px -50px; _background-position:-120px -50px;} 38 | .xubox_imgbar{position:absolute; left:0; bottom:0; width:100%; height:32px; line-height:32px; background-color:rgba(0,0,0,.8); background-color:#000\9; filter:Alpha(opacity=80); color:#fff; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; font-size:0;} 39 | .xubox_imgtit{/*position:absolute; left:20px;*/} 40 | .xubox_imgtit *{display:inline-block; *display:inline; *zoom:1; vertical-align:top; font-size:12px;} 41 | .xubox_imgtit a{max-width:65%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; color:#fff;} 42 | .xubox_imgtit a:hover{color:#fff; text-decoration:underline;} 43 | .xubox_imgtit em{padding-left:10px;} 44 | 45 | 46 | -------------------------------------------------------------------------------- /web/select2/select2-spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/select2/select2-spinner.gif -------------------------------------------------------------------------------- /web/select2/select2.css: -------------------------------------------------------------------------------- 1 | /* 2 | Version: 3.5.0 Timestamp: Mon Jun 16 19:29:44 EDT 2014 3 | */ 4 | .select2-container { 5 | margin: 0; 6 | position: relative; 7 | display: inline-block; 8 | /* inline-block for ie7 */ 9 | zoom: 1; 10 | *display: inline; 11 | vertical-align: middle; 12 | } 13 | 14 | .select2-container, 15 | .select2-drop, 16 | .select2-search, 17 | .select2-search input { 18 | /* 19 | Force border-box so that % widths fit the parent 20 | container without overlap because of margin/padding. 21 | More Info : http://www.quirksmode.org/css/box.html 22 | */ 23 | -webkit-box-sizing: border-box; /* webkit */ 24 | -moz-box-sizing: border-box; /* firefox */ 25 | box-sizing: border-box; /* css3 */ 26 | } 27 | 28 | .select2-container .select2-choice { 29 | display: block; 30 | height: 26px; 31 | padding: 0 0 0 8px; 32 | overflow: hidden; 33 | position: relative; 34 | 35 | border: 1px solid #aaa; 36 | white-space: nowrap; 37 | line-height: 26px; 38 | color: #444; 39 | text-decoration: none; 40 | 41 | border-radius: 4px; 42 | 43 | background-clip: padding-box; 44 | 45 | -webkit-touch-callout: none; 46 | -webkit-user-select: none; 47 | -moz-user-select: none; 48 | -ms-user-select: none; 49 | user-select: none; 50 | 51 | background-color: #fff; 52 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.5, #fff)); 53 | background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 50%); 54 | background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 50%); 55 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0); 56 | background-image: linear-gradient(to top, #eee 0%, #fff 50%); 57 | } 58 | 59 | html[dir="rtl"] .select2-container .select2-choice { 60 | padding: 0 8px 0 0; 61 | } 62 | 63 | .select2-container.select2-drop-above .select2-choice { 64 | border-bottom-color: #aaa; 65 | 66 | border-radius: 0 0 4px 4px; 67 | 68 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.9, #fff)); 69 | background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 90%); 70 | background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 90%); 71 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0); 72 | background-image: linear-gradient(to bottom, #eee 0%, #fff 90%); 73 | } 74 | 75 | .select2-container.select2-allowclear .select2-choice .select2-chosen { 76 | margin-right: 42px; 77 | } 78 | 79 | .select2-container .select2-choice > .select2-chosen { 80 | margin-right: 26px; 81 | display: block; 82 | overflow: hidden; 83 | 84 | white-space: nowrap; 85 | 86 | text-overflow: ellipsis; 87 | float: none; 88 | width: auto; 89 | } 90 | 91 | html[dir="rtl"] .select2-container .select2-choice > .select2-chosen { 92 | margin-left: 26px; 93 | margin-right: 0; 94 | } 95 | 96 | .select2-container .select2-choice abbr { 97 | display: none; 98 | width: 12px; 99 | height: 12px; 100 | position: absolute; 101 | right: 24px; 102 | top: 8px; 103 | 104 | font-size: 1px; 105 | text-decoration: none; 106 | 107 | border: 0; 108 | background: url('select2.png') right top no-repeat; 109 | cursor: pointer; 110 | outline: 0; 111 | } 112 | 113 | .select2-container.select2-allowclear .select2-choice abbr { 114 | display: inline-block; 115 | } 116 | 117 | .select2-container .select2-choice abbr:hover { 118 | background-position: right -11px; 119 | cursor: pointer; 120 | } 121 | 122 | .select2-drop-mask { 123 | border: 0; 124 | margin: 0; 125 | padding: 0; 126 | position: fixed; 127 | left: 0; 128 | top: 0; 129 | min-height: 100%; 130 | min-width: 100%; 131 | height: auto; 132 | width: auto; 133 | opacity: 0; 134 | z-index: 9998; 135 | /* styles required for IE to work */ 136 | background-color: #fff; 137 | filter: alpha(opacity=0); 138 | } 139 | 140 | .select2-drop { 141 | width: 100%; 142 | margin-top: -1px; 143 | position: absolute; 144 | z-index: 9999; 145 | top: 100%; 146 | 147 | background: #fff; 148 | color: #000; 149 | border: 1px solid #aaa; 150 | border-top: 0; 151 | 152 | border-radius: 0 0 4px 4px; 153 | 154 | -webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15); 155 | box-shadow: 0 4px 5px rgba(0, 0, 0, .15); 156 | } 157 | 158 | .select2-drop.select2-drop-above { 159 | margin-top: 1px; 160 | border-top: 1px solid #aaa; 161 | border-bottom: 0; 162 | 163 | border-radius: 4px 4px 0 0; 164 | 165 | -webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); 166 | box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); 167 | } 168 | 169 | .select2-drop-active { 170 | border: 1px solid #5897fb; 171 | border-top: none; 172 | } 173 | 174 | .select2-drop.select2-drop-above.select2-drop-active { 175 | border-top: 1px solid #5897fb; 176 | } 177 | 178 | .select2-drop-auto-width { 179 | border-top: 1px solid #aaa; 180 | width: auto; 181 | } 182 | 183 | .select2-drop-auto-width .select2-search { 184 | padding-top: 4px; 185 | } 186 | 187 | .select2-container .select2-choice .select2-arrow { 188 | display: inline-block; 189 | width: 18px; 190 | height: 100%; 191 | position: absolute; 192 | right: 0; 193 | top: 0; 194 | 195 | border-left: 1px solid #aaa; 196 | border-radius: 0 4px 4px 0; 197 | 198 | background-clip: padding-box; 199 | 200 | background: #ccc; 201 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee)); 202 | background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%); 203 | background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%); 204 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0); 205 | background-image: linear-gradient(to top, #ccc 0%, #eee 60%); 206 | } 207 | 208 | html[dir="rtl"] .select2-container .select2-choice .select2-arrow { 209 | left: 0; 210 | right: auto; 211 | 212 | border-left: none; 213 | border-right: 1px solid #aaa; 214 | border-radius: 4px 0 0 4px; 215 | } 216 | 217 | .select2-container .select2-choice .select2-arrow b { 218 | display: block; 219 | width: 100%; 220 | height: 100%; 221 | background: url('select2.png') no-repeat 0 1px; 222 | } 223 | 224 | html[dir="rtl"] .select2-container .select2-choice .select2-arrow b { 225 | background-position: 2px 1px; 226 | } 227 | 228 | .select2-search { 229 | display: inline-block; 230 | width: 100%; 231 | min-height: 26px; 232 | margin: 0; 233 | padding-left: 4px; 234 | padding-right: 4px; 235 | 236 | position: relative; 237 | z-index: 10000; 238 | 239 | white-space: nowrap; 240 | } 241 | 242 | .select2-search input { 243 | width: 100%; 244 | height: auto !important; 245 | min-height: 26px; 246 | padding: 4px 20px 4px 5px; 247 | margin: 0; 248 | 249 | outline: 0; 250 | font-family: sans-serif; 251 | font-size: 1em; 252 | 253 | border: 1px solid #aaa; 254 | border-radius: 0; 255 | 256 | -webkit-box-shadow: none; 257 | box-shadow: none; 258 | 259 | background: #fff url('select2.png') no-repeat 100% -22px; 260 | background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); 261 | background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); 262 | background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); 263 | background: url('select2.png') no-repeat 100% -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0; 264 | } 265 | 266 | html[dir="rtl"] .select2-search input { 267 | padding: 4px 5px 4px 20px; 268 | 269 | background: #fff url('select2.png') no-repeat -37px -22px; 270 | background: url('select2.png') no-repeat -37px -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); 271 | background: url('select2.png') no-repeat -37px -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); 272 | background: url('select2.png') no-repeat -37px -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); 273 | background: url('select2.png') no-repeat -37px -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0; 274 | } 275 | 276 | .select2-drop.select2-drop-above .select2-search input { 277 | margin-top: 4px; 278 | } 279 | 280 | .select2-search input.select2-active { 281 | background: #fff url('select2-spinner.gif') no-repeat 100%; 282 | background: url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); 283 | background: url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); 284 | background: url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); 285 | background: url('select2-spinner.gif') no-repeat 100%, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0; 286 | } 287 | 288 | .select2-container-active .select2-choice, 289 | .select2-container-active .select2-choices { 290 | border: 1px solid #5897fb; 291 | outline: none; 292 | 293 | -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3); 294 | box-shadow: 0 0 5px rgba(0, 0, 0, .3); 295 | } 296 | 297 | .select2-dropdown-open .select2-choice { 298 | border-bottom-color: transparent; 299 | -webkit-box-shadow: 0 1px 0 #fff inset; 300 | box-shadow: 0 1px 0 #fff inset; 301 | 302 | border-bottom-left-radius: 0; 303 | border-bottom-right-radius: 0; 304 | 305 | background-color: #eee; 306 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(0.5, #eee)); 307 | background-image: -webkit-linear-gradient(center bottom, #fff 0%, #eee 50%); 308 | background-image: -moz-linear-gradient(center bottom, #fff 0%, #eee 50%); 309 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0); 310 | background-image: linear-gradient(to top, #fff 0%, #eee 50%); 311 | } 312 | 313 | .select2-dropdown-open.select2-drop-above .select2-choice, 314 | .select2-dropdown-open.select2-drop-above .select2-choices { 315 | border: 1px solid #5897fb; 316 | border-top-color: transparent; 317 | 318 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(0.5, #eee)); 319 | background-image: -webkit-linear-gradient(center top, #fff 0%, #eee 50%); 320 | background-image: -moz-linear-gradient(center top, #fff 0%, #eee 50%); 321 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0); 322 | background-image: linear-gradient(to bottom, #fff 0%, #eee 50%); 323 | } 324 | 325 | .select2-dropdown-open .select2-choice .select2-arrow { 326 | background: transparent; 327 | border-left: none; 328 | filter: none; 329 | } 330 | html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow { 331 | border-right: none; 332 | } 333 | 334 | .select2-dropdown-open .select2-choice .select2-arrow b { 335 | background-position: -18px 1px; 336 | } 337 | 338 | html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow b { 339 | background-position: -16px 1px; 340 | } 341 | 342 | .select2-hidden-accessible { 343 | border: 0; 344 | clip: rect(0 0 0 0); 345 | height: 1px; 346 | margin: -1px; 347 | overflow: hidden; 348 | padding: 0; 349 | position: absolute; 350 | width: 1px; 351 | } 352 | 353 | /* results */ 354 | .select2-results { 355 | max-height: 200px; 356 | padding: 0 0 0 4px; 357 | margin: 4px 4px 4px 0; 358 | position: relative; 359 | overflow-x: hidden; 360 | overflow-y: auto; 361 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 362 | } 363 | 364 | html[dir="rtl"] .select2-results { 365 | padding: 0 4px 0 0; 366 | margin: 4px 0 4px 4px; 367 | } 368 | 369 | .select2-results ul.select2-result-sub { 370 | margin: 0; 371 | padding-left: 0; 372 | } 373 | 374 | .select2-results li { 375 | list-style: none; 376 | display: list-item; 377 | background-image: none; 378 | } 379 | 380 | .select2-results li.select2-result-with-children > .select2-result-label { 381 | font-weight: bold; 382 | } 383 | 384 | .select2-results .select2-result-label { 385 | padding: 3px 7px 4px; 386 | margin: 0; 387 | cursor: pointer; 388 | 389 | min-height: 1em; 390 | 391 | -webkit-touch-callout: none; 392 | -webkit-user-select: none; 393 | -moz-user-select: none; 394 | -ms-user-select: none; 395 | user-select: none; 396 | } 397 | 398 | .select2-results-dept-1 .select2-result-label { padding-left: 20px } 399 | .select2-results-dept-2 .select2-result-label { padding-left: 40px } 400 | .select2-results-dept-3 .select2-result-label { padding-left: 60px } 401 | .select2-results-dept-4 .select2-result-label { padding-left: 80px } 402 | .select2-results-dept-5 .select2-result-label { padding-left: 100px } 403 | .select2-results-dept-6 .select2-result-label { padding-left: 110px } 404 | .select2-results-dept-7 .select2-result-label { padding-left: 120px } 405 | 406 | .select2-results .select2-highlighted { 407 | background: #3875d7; 408 | color: #fff; 409 | } 410 | 411 | .select2-results li em { 412 | background: #feffde; 413 | font-style: normal; 414 | } 415 | 416 | .select2-results .select2-highlighted em { 417 | background: transparent; 418 | } 419 | 420 | .select2-results .select2-highlighted ul { 421 | background: #fff; 422 | color: #000; 423 | } 424 | 425 | 426 | .select2-results .select2-no-results, 427 | .select2-results .select2-searching, 428 | .select2-results .select2-selection-limit { 429 | background: #f4f4f4; 430 | display: list-item; 431 | padding-left: 5px; 432 | } 433 | 434 | /* 435 | disabled look for disabled choices in the results dropdown 436 | */ 437 | .select2-results .select2-disabled.select2-highlighted { 438 | color: #666; 439 | background: #f4f4f4; 440 | display: list-item; 441 | cursor: default; 442 | } 443 | .select2-results .select2-disabled { 444 | background: #f4f4f4; 445 | display: list-item; 446 | cursor: default; 447 | } 448 | 449 | .select2-results .select2-selected { 450 | display: none; 451 | } 452 | 453 | .select2-more-results.select2-active { 454 | background: #f4f4f4 url('select2-spinner.gif') no-repeat 100%; 455 | } 456 | 457 | .select2-more-results { 458 | background: #f4f4f4; 459 | display: list-item; 460 | } 461 | 462 | /* disabled styles */ 463 | 464 | .select2-container.select2-container-disabled .select2-choice { 465 | background-color: #f4f4f4; 466 | background-image: none; 467 | border: 1px solid #ddd; 468 | cursor: default; 469 | } 470 | 471 | .select2-container.select2-container-disabled .select2-choice .select2-arrow { 472 | background-color: #f4f4f4; 473 | background-image: none; 474 | border-left: 0; 475 | } 476 | 477 | .select2-container.select2-container-disabled .select2-choice abbr { 478 | display: none; 479 | } 480 | 481 | 482 | /* multiselect */ 483 | 484 | .select2-container-multi .select2-choices { 485 | height: auto !important; 486 | height: 1%; 487 | margin: 0; 488 | padding: 0 5px 0 0; 489 | position: relative; 490 | 491 | border: 1px solid #aaa; 492 | cursor: text; 493 | overflow: hidden; 494 | 495 | background-color: #fff; 496 | background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eee), color-stop(15%, #fff)); 497 | background-image: -webkit-linear-gradient(top, #eee 1%, #fff 15%); 498 | background-image: -moz-linear-gradient(top, #eee 1%, #fff 15%); 499 | background-image: linear-gradient(to bottom, #eee 1%, #fff 15%); 500 | } 501 | 502 | html[dir="rtl"] .select2-container-multi .select2-choices { 503 | padding: 0 0 0 5px; 504 | } 505 | 506 | .select2-locked { 507 | padding: 3px 5px 3px 5px !important; 508 | } 509 | 510 | .select2-container-multi .select2-choices { 511 | min-height: 26px; 512 | } 513 | 514 | .select2-container-multi.select2-container-active .select2-choices { 515 | border: 1px solid #5897fb; 516 | outline: none; 517 | 518 | -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3); 519 | box-shadow: 0 0 5px rgba(0, 0, 0, .3); 520 | } 521 | .select2-container-multi .select2-choices li { 522 | float: left; 523 | list-style: none; 524 | } 525 | html[dir="rtl"] .select2-container-multi .select2-choices li 526 | { 527 | float: right; 528 | } 529 | .select2-container-multi .select2-choices .select2-search-field { 530 | margin: 0; 531 | padding: 0; 532 | white-space: nowrap; 533 | } 534 | 535 | .select2-container-multi .select2-choices .select2-search-field input { 536 | padding: 5px; 537 | margin: 1px 0; 538 | 539 | font-family: sans-serif; 540 | font-size: 100%; 541 | color: #666; 542 | outline: 0; 543 | border: 0; 544 | -webkit-box-shadow: none; 545 | box-shadow: none; 546 | background: transparent !important; 547 | } 548 | 549 | .select2-container-multi .select2-choices .select2-search-field input.select2-active { 550 | background: #fff url('select2-spinner.gif') no-repeat 100% !important; 551 | } 552 | 553 | .select2-default { 554 | color: #999 !important; 555 | } 556 | 557 | .select2-container-multi .select2-choices .select2-search-choice { 558 | padding: 3px 5px 3px 18px; 559 | margin: 3px 0 3px 5px; 560 | position: relative; 561 | 562 | line-height: 13px; 563 | color: #333; 564 | cursor: default; 565 | border: 1px solid #aaaaaa; 566 | 567 | border-radius: 3px; 568 | 569 | -webkit-box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05); 570 | box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05); 571 | 572 | background-clip: padding-box; 573 | 574 | -webkit-touch-callout: none; 575 | -webkit-user-select: none; 576 | -moz-user-select: none; 577 | -ms-user-select: none; 578 | user-select: none; 579 | 580 | background-color: #e4e4e4; 581 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0); 582 | background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eee)); 583 | background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); 584 | background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); 585 | background-image: linear-gradient(to top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); 586 | } 587 | html[dir="rtl"] .select2-container-multi .select2-choices .select2-search-choice 588 | { 589 | margin: 3px 5px 3px 0; 590 | padding: 3px 18px 3px 5px; 591 | } 592 | .select2-container-multi .select2-choices .select2-search-choice .select2-chosen { 593 | cursor: default; 594 | } 595 | .select2-container-multi .select2-choices .select2-search-choice-focus { 596 | background: #d4d4d4; 597 | } 598 | 599 | .select2-search-choice-close { 600 | display: block; 601 | width: 12px; 602 | height: 13px; 603 | position: absolute; 604 | right: 3px; 605 | top: 4px; 606 | 607 | font-size: 1px; 608 | outline: none; 609 | background: url('select2.png') right top no-repeat; 610 | } 611 | html[dir="rtl"] .select2-search-choice-close { 612 | right: auto; 613 | left: 3px; 614 | } 615 | 616 | .select2-container-multi .select2-search-choice-close { 617 | left: 3px; 618 | } 619 | 620 | html[dir="rtl"] .select2-container-multi .select2-search-choice-close { 621 | left: auto; 622 | right: 2px; 623 | } 624 | 625 | .select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover { 626 | background-position: right -11px; 627 | } 628 | .select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close { 629 | background-position: right -11px; 630 | } 631 | 632 | /* disabled styles */ 633 | .select2-container-multi.select2-container-disabled .select2-choices { 634 | background-color: #f4f4f4; 635 | background-image: none; 636 | border: 1px solid #ddd; 637 | cursor: default; 638 | } 639 | 640 | .select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice { 641 | padding: 3px 5px 3px 5px; 642 | border: 1px solid #ddd; 643 | background-image: none; 644 | background-color: #f4f4f4; 645 | } 646 | 647 | .select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none; 648 | background: none; 649 | } 650 | /* end multiselect */ 651 | 652 | 653 | .select2-result-selectable .select2-match, 654 | .select2-result-unselectable .select2-match { 655 | text-decoration: underline; 656 | } 657 | 658 | .select2-offscreen, .select2-offscreen:focus { 659 | clip: rect(0 0 0 0) !important; 660 | width: 1px !important; 661 | height: 1px !important; 662 | border: 0 !important; 663 | margin: 0 !important; 664 | padding: 0 !important; 665 | overflow: hidden !important; 666 | position: absolute !important; 667 | outline: 0 !important; 668 | left: 0px !important; 669 | top: 0px !important; 670 | } 671 | 672 | .select2-display-none { 673 | display: none; 674 | } 675 | 676 | .select2-measure-scrollbar { 677 | position: absolute; 678 | top: -10000px; 679 | left: -10000px; 680 | width: 100px; 681 | height: 100px; 682 | overflow: scroll; 683 | } 684 | 685 | /* Retina-ize icons */ 686 | 687 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 2dppx) { 688 | .select2-search input, 689 | .select2-search-choice-close, 690 | .select2-container .select2-choice abbr, 691 | .select2-container .select2-choice .select2-arrow b { 692 | background-image: url('select2x2.png') !important; 693 | background-repeat: no-repeat !important; 694 | background-size: 60px 40px !important; 695 | } 696 | 697 | .select2-search input { 698 | background-position: 100% -21px !important; 699 | } 700 | } 701 | -------------------------------------------------------------------------------- /web/select2/select2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/select2/select2.png -------------------------------------------------------------------------------- /web/select2/select2x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinp/dash/33e2501a16d14605db200ad1a8e4dfea36c9db74/web/select2/select2x2.png --------------------------------------------------------------------------------