UIService.java [src/csip] Revision: 71821307bfe742c00c6dc582c171224a9ac59935  Date: Fri Apr 21 11:46:19 MDT 2017
/*
 * $Id$
 *
 * This file is part of the Cloud Services Integration Platform (CSIP),
 * a Model-as-a-Service framework, API and application suite.
 *
 * 2012-2017, Olaf David and others, OMSLab, Colorado State University.
 *
 * OMSLab licenses this file to you under the MIT license.
 * See the LICENSE file in the project root for more information.
 */
package csip;

import csip.utils.*;
import java.net.URI;
import java.text.DateFormat;
import java.util.*;
import java.util.logging.*;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import org.codehaus.jettison.json.JSONException;

/**
 * ControlService
 *
 * @author od
 */
@Path("ui")
public class UIService {

    static final String ARCHIVE_HEADER = "<tr bgcolor=\"#D5DADE\">"
            + "<th>suid</th>"
            + "<th>status</th>"
            + "<th>req IP</th>"
            + "<th>archived at</th>"
            + "<th>expiration</th>"
            + "<th>service</th>"
            + "<th>files</th>"
            + "<th> </th>"
            + "</tr>\n";

    static final String ARCHIVE_ROW = "<tr bgcolor=\"%6$s\">"
            + "<td>%1$s</td>"
            + "<td>%2$s</td>"
            + "<td><a href=\"http://freegeoip.net/?q=%4$s&map=1\">%3$s</a></td>"
            + "<td>%4$s</td>"
            + "<td>%8$s</td>"
            + "<td>%5$s</td>"
            + "<td><a href=\"/%7$s/a/download/%1$s\">download</a></td>"
            + "<td><a href=\"/%7$s/a/remove/%1$s\">del</a> </td>"
            + "</tr>\n";

    static final String HEADER = "<tr bgcolor=\"#D5DADE\">"
            + "<th colspan=2>suid/json</th>"
            + "<th>status</th>"
            + "<th>node IP</th>"
            + "<th>req IP</th>"
            + "<th>start</th>"
            + "<th>expiration</th>"
            + "<th>runtime</th>"
            + "<th>service</th>"
            + "<th>attach</th>"
            + "<th> </th>"
            + "</tr>\n";
    //
    static final String DEF_BACKGROUND = "#F0F0F0";
    static final String ZOMBIE_BACKGROUND = "#E19BFF";    //purple
    static final String FAILED_BACKGROUND = "#FFCFD8";    // red
    static final String FINISHED_BACKGROUND = "#D7FFC2";  // green

    static final Logger LOG = Logger.getLogger(UIService.class.getName());


    static StringBuilder appendLine(StringBuilder b, String col, String... td) {
        b.append("<tr bgcolor=\"").append(col).append("\">");
        for (String t : td) {
            b.append("<td>").append(t).append("</td>");
        }
        b.append("</tr>\n");
        return b;
    }


    static String getIPLink(String ip) {
        if ("127.0.0.1".equals(ip)) {
            return ip;
        }
        StringBuilder b = new StringBuilder();
        b.append("<a href=\"http://freegeoip.net/?q=").append(ip).append("&map=1\">").append(ip).append("</a></td>");
        return b.toString();
    }


    static String getLinks(String[] row, String context) {
        boolean service_done = false;
        String status = row[1];
        String suid = row[0];
        String hasReport = row[9];

        if (status.equals(ModelDataService.FAILED) || status.equals(ModelDataService.FINISHED)) {
            service_done = true;
        }

        StringBuilder b = new StringBuilder();
        b.append("<a href=\"/").append(context).append("/q/req/").append(suid).append("\">req</a>");
        if (service_done) {
            b.append("&nbsp;&nbsp;<a href=\"/").append(context).append("/q/res/").append(suid).append("\">res</a>");
            if (Boolean.parseBoolean(hasReport)) {
                b.append("&nbsp;&nbsp;<a href=\"/").append(context).append("/r/").append(suid).append("\">rep</a>");
            }
        }
        return b.toString();
    }


    String getLogLink(String context, String id) {
        return "<a href=\"/" + context + "/q/log/" + id + "\">log</a>";
    }

/////////////////////////////////

    /**
     * Session UI
     *
     * @param u
     * @param skip
     * @param limit
     * @param sort
     * @param order
     * @return
     * @throws Exception
     */
    @GET
    @Path("")
    @Produces(MediaType.TEXT_HTML)
    public String getSessions(@Context UriInfo u, @Context HttpServletRequest req,
            @DefaultValue("0")
            @QueryParam("skip") int skip,
            @DefaultValue("25")
            @QueryParam("limit") int limit,
            @DefaultValue("tst")
            @QueryParam("sort") String sort,
            @DefaultValue("d")
            @QueryParam("order") String order)
            throws Exception {

        Utils.checkRemoteAccessACL(req);

        URI uri = u.getBaseUri();
        String path = uri.getPath();
        String context = uri.getPath();
        context = context.substring(1, context.indexOf('/', 2));

        SessionStore s = Config.getSessionStore();
        long start = System.currentTimeMillis();
        Set<String> keys = s.keys(skip, limit, sort, order.equalsIgnoreCase("a"));

        Date now = new Date();
        List<String[]> l = new ArrayList<>();
        for (String id : keys) {
            ModelSession v = s.getSession(id);
            if (v == null) {
                continue;  // this should not happen
            }
            Date startsim = Dates.parse(v.getTstamp());
            String cputime = v.getCputime();
            String duration = cputime.isEmpty() ? Dates.duration(startsim, now) : Dates.duration(Long.parseLong(cputime));
            l.add(new String[]{
                id.substring(id.indexOf(':') + 1),
                v.getStatus(),
                v.getNodeIP(),
                v.getReqIP(),
                v.getTstamp(),
                v.getExpDate(),
                duration,
                v.getService(),
                Arrays.toString(v.getAttachments()),
                Boolean.toString(v.hasReport())
            });
        }

        int this_start = skip + 1;
        int this_end = keys.size() + skip;
        int prev_start = this_start - limit - 1;
        int next_start = this_end;

        long count = s.getCount();

        String prev_link = prev_start < 0 ? "" : "href=\"" + path + "ui?skip=" + prev_start + "&limit=" + limit + "&sort=" + sort + "&order=" + order + "\"";
        String next_link = count == this_end ? "" : "href=\"" + path + "ui?skip=" + next_start + "&limit=" + limit + "&sort=" + sort + "&order=" + order + "\"";

        String prev = "<a " + prev_link + "><</a>";
        String next = "<a " + next_link + ">></a>";
        String begin = "<a " + "href=\"" + path + "ui?skip=" + 0 + "&limit=" + limit + "&sort=" + sort + "&order=" + order + "\"" + "><<</a>";
        String end = "<a " + "href=\"" + path + "ui?skip=" + (count - count % limit) + "&limit=" + limit + "&sort=" + sort + "&order=" + order + "\"" + ">>></a>";

        StringBuilder b = new StringBuilder();
        b.append("<html><head><title>Sessions - " + context + "</title></head><body>");
        b.append("<table width=\"100%\" border=\"0\" cellpadding=\"1\"><tr>");
        b.append("<td  width=\"33%\">");
        b.append("<a href=\"" + path + "ui/archive\">Archive</a> | ");
        b.append("<a href=\"" + path + "\">Services</a> ");
        b.append("<a href=\"" + path + "ui/conf\">Config</a> | ");
        b.append("<a href=\"" + path + "q/check\">Check</a> ");
        b.append("<a href=\"" + path + "c/clean\">Clean</a>");
//        b.append("<a href=\"" + path + "a/clean\">CleanArchive</a> ");
        b.append("</td>");
        b.append("<td width=\"33%\">");
        b.append("<p align=\"center\">" + begin + "&nbsp;&nbsp;" + prev + "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
                + this_start + " - " + this_end + " (of " + count + ") &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + next + "&nbsp;&nbsp;" + end + "</p>");
        b.append("</td width=\"33%\">");
        b.append("<td><p align=\"right\"> <b>Sessions</b> at ");
        b.append(Dates.nowISO(Config.getString("csip.timezone", "UTC")));
        b.append("</p></td>");
        b.append("</tr></table>");

        b.append("<table width=\"100%\" border=\"0\" cellpadding=\"5\">");
        b.append("<tbody align=\"left\" style=\"font-family:monospaced;\">");
        b.append(HEADER);
        DateFormat df = Dates.newISOFormat();
        String no = df.format(new Date());

        for (String[] c : l) {
            String status = c[1];
            String color = DEF_BACKGROUND;
            String exp = c[5];
            if (status.equals(ModelDataService.FAILED)) {
                color = FAILED_BACKGROUND;
            } else if (status.equals(ModelDataService.FINISHED)) {
                color = FINISHED_BACKGROUND;
            }

            if (!exp.isEmpty() && exp.compareTo(no) < 0) {  // check is zombie that is past expiration
                color = ZOMBIE_BACKGROUND;
            }
            String links = getLinks(c, context);
            String iplink = getIPLink(c[3]);
            String logLink = getLogLink(context, c[0]);
            String canLink = "<a href=\"/" + context + "/c/cancel/" + c[0] + "\">stop</a>";
            appendLine(b, color, c[0], links, c[1], c[2], iplink, c[4], c[5], c[6], c[7], c[8], logLink + " " + canLink);
        }
        b.append("</tbody></table>");
        long end_ = System.currentTimeMillis();
        b.append("<em>" + (end_ - start) + " ms</em>");
        b.append("</body><html>");
        return b.toString();
    }


    /**
     * Archive UI.
     *
     * @param u
     * @param skip
     * @param limit
     * @param sort
     * @param order
     * @return
     * @throws Exception
     */
    @GET
    @Path("archive")
    @Produces(MediaType.TEXT_HTML)
    public String getArchives(@Context UriInfo u, @Context HttpServletRequest req,
            @DefaultValue("0") @QueryParam("skip") int skip,
            @DefaultValue("25") @QueryParam("limit") int limit,
            @DefaultValue("ctime") @QueryParam("sort") String sort,
            @DefaultValue("d") @QueryParam("order") String order) // descending order
            throws Exception {

        Utils.checkRemoteAccessACL(req);

        if (!Config.isArchiveEnabled()) {
            return "<html><body>Archive not enabled.</body></html>";
        }

        URI uri = u.getBaseUri();
        String path = uri.getPath();
        String context = uri.getPath();
        context = context.substring(1, context.indexOf('/', 2));

        ArchiveStore s = Config.getArchiveStore();
        Set<String> keys = s.keys(skip, limit, sort, order.equalsIgnoreCase("a"));

        long start = System.currentTimeMillis();
        List<String[]> l = new ArrayList<>();
        for (String id : keys) {
            ModelArchive v = s.getArchive(id);
            if (v == null) {
                continue;  // this should not happen
            }
            l.add(new String[]{
                id,
                v.getStatus(),
                v.getReqIP(),
                v.getCtime(),
                v.getService(),
                v.getEtime()
            });
        }

        int this_start = skip + 1;
        int this_end = keys.size() + skip;
        int prev_start = this_start - limit - 1;
        int next_start = this_end;

        String prev_link = prev_start < 0 ? "" : "href=\"" + path + "ui/archive?skip=" + prev_start + "&limit=" + limit + "&sort=" + sort + "&order=" + order + "\"";
        String next_link = s.getCount() == this_end ? "" : "href=\"" + path + "ui/archive?skip=" + next_start + "&limit=" + limit + "&sort=" + sort + "&order=" + order + "\"";

        String prev = "<a " + prev_link + "><</a>";
        String next = "<a " + next_link + ">></a>";
        String begin = "<a " + "href=\"" + path + "ui/archive?skip=" + 0 + "&limit=" + limit + "&sort=" + sort + "&order=" + order + "\"" + "><<</a>";
        String end = "<a " + "href=\"" + path + "ui/archive?skip=" + (s.getCount() - s.getCount() % limit) + "&limit=" + limit + "&sort=" + sort + "&order=" + order + "\"" + ">>></a>";

        // create the page.
        StringBuilder b = new StringBuilder();
        b.append("<html><head><title> Archives - " + context + "</title></head><body>");

        b.append("<table width=\"100%\" border=\"0\" cellpadding=\"1\"><tr>");
        b.append("<td  width=\"33%\">");
        b.append("<a href=\"" + path + "ui\">Sessions</a> | ");
        b.append("<a href=\"" + path + "a/clean\">Clean</a> ");
        b.append("</td>");
        b.append("<td width=\"33%\">");
        b.append("<p align=\"center\">" + begin + "&nbsp;&nbsp;" + prev + "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
                + this_start + " - " + this_end + " (of " + s.getCount() + ") &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + next + "&nbsp;&nbsp;" + end + "</p>");
        b.append("</td width=\"33%\">");
        b.append("<td><p align=\"right\"> <b>Archives</b> at ");
        b.append(Dates.nowISO(Config.getString("csip.timezone", "UTC")));
        b.append("</p></td>");
        b.append("</tr></table>");

        b.append("<table width=\"100%\" border=\"0\" cellpadding=\"5\">");
        b.append("<tbody align=\"left\" style=\"font-family:monospaced;\">");
        b.append(ARCHIVE_HEADER);
        for (String[] c : l) {
            String color = c[1].equals(ModelDataService.FAILED) ? FAILED_BACKGROUND : FINISHED_BACKGROUND;
            b.append(String.format(ARCHIVE_ROW, c[0], c[1], c[2], c[3], c[4], color, context, c[5]));
        }
        b.append("</tbody></table>");
        long endtime = System.currentTimeMillis();
        b.append("<em>" + (endtime - start) + " ms</em>");
        b.append("</body><html>");
        return b.toString();
    }


    /**
     * Get the service configuration
     * @return
     * @throws JSONException
     */
    @GET
    @Path("conf")
    @Produces(MediaType.TEXT_HTML)
    public String getHtml(@Context UriInfo u,  @Context HttpServletRequest req) throws JSONException {
        Utils.checkRemoteAccessACL(req);

        Properties p = Config.getMergedProperties();
        Map<Object, Object> m = new TreeMap<>(p);
        StringBuilder o = new StringBuilder();
        for (Object key : m.keySet()) {
            String val = p.get(key).toString();
            String key_ = key.toString();
            val = val.replaceAll("password=.+[;]?", "password=****;");
            if (key_.toLowerCase().contains("password")) {
                val = "****";
            }
            o.append("<b>" + key_ + "</b>: " + val + "<br>");
        }

        // Add chunk DBs if applicable
        Collection<PostgresChunk> pcs = Config.getPostgresChunks();
        // Add PGChunks to output if they exist...
        if ((pcs != null) && (pcs.size() > 0)) {
            int i = 1;
            for (PostgresChunk ch : pcs) {
                o.append("<b>pgdb" + i + "</b>: " + ch.getJSON().toString() + "<br>");
                i++;
            }
        }
        return "<html><body><h1>CSIP Configuration </h1>" + Services.LOCAL_IP_ADDR + "<p>" + o.toString() + "</p></body></html>";
    }
}