mirror of
https://github.com/sosedoff/pgweb.git
synced 2024-12-15 03:36:33 +03:00
WIP
This commit is contained in:
parent
d579819946
commit
9bd0508219
6
api.go
6
api.go
@ -11,6 +11,10 @@ type Error struct {
|
||||
Message string `json:"error"`
|
||||
}
|
||||
|
||||
func API_Home(c *gin.Context) {
|
||||
c.File("./static/index.html")
|
||||
}
|
||||
|
||||
func API_RunQuery(c *gin.Context) {
|
||||
query := strings.TrimSpace(c.Request.FormValue("query"))
|
||||
|
||||
@ -52,7 +56,7 @@ func API_GetTable(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, res.Format())
|
||||
c.JSON(200, res)
|
||||
}
|
||||
|
||||
func API_History(c *gin.Context) {
|
||||
|
1
main.go
1
main.go
@ -78,6 +78,7 @@ func main() {
|
||||
|
||||
router := gin.Default()
|
||||
|
||||
router.GET("/", API_Home)
|
||||
router.GET("/info", API_Info)
|
||||
router.GET("/tables", API_GetTables)
|
||||
router.GET("/tables/:table", API_GetTable)
|
||||
|
226
static/css/app.css
Normal file
226
static/css/app.css
Normal file
@ -0,0 +1,226 @@
|
||||
#nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 250;
|
||||
right: 0;
|
||||
height: 50px;
|
||||
box-shadow: inset 0 -1px 0 0 #b8b7b5;
|
||||
background: #d8d7d6;
|
||||
}
|
||||
|
||||
#nav ul {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
#nav ul li {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
float: left;
|
||||
height: 49px;
|
||||
line-height: 49px;
|
||||
font-size: 13px;
|
||||
padding: 0px 14px;
|
||||
color: #6c6b6b;
|
||||
font-weight: 500;
|
||||
margin: 0 1px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#nav ul li:first-child {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
#nav ul li:hover {
|
||||
color: rgba(0,0,0,0.9);
|
||||
}
|
||||
|
||||
#nav ul li.selected {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
color: #000;
|
||||
border-right: 1px solid #b8b7b5;
|
||||
border-left: 1px solid #b8b7b5;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#nav ul li.selected:before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 0; right: 0;
|
||||
height: 1px;
|
||||
widows: 100%;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#nav ul li.selected:first-child {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
position: fixed;
|
||||
width: 250px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: #3a3633;
|
||||
border-right: 1px solid #b4b4b4;
|
||||
font-size: 13px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
#sidebar .wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#sidebar ul {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#sidebar ul li {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
#sidebar ul li:hover {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
#body {
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
left: 250px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
#input {
|
||||
height: 255px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#input .wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#input .actions {
|
||||
background: #fff;
|
||||
padding: 10px;
|
||||
height: 50px;
|
||||
border-top: solid 1px #e1e2e3;
|
||||
border-bottom: solid 1px #b8b7b5;
|
||||
}
|
||||
|
||||
#input .actions .btn {
|
||||
line-height: 30px;
|
||||
height: 30px;
|
||||
padding: 0px 13px;
|
||||
margin: 0px;
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
background: #7eb54e;
|
||||
}
|
||||
|
||||
#input .actions .btn:focus {
|
||||
outline: 0 none;
|
||||
box-shadow: 0;
|
||||
}
|
||||
#input .actions .btn:hover {
|
||||
background: #7eb154;
|
||||
}
|
||||
|
||||
#output {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 256px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
#output.full {
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
#results {
|
||||
font-size: 12px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#results.empty td {
|
||||
border: 0px none;
|
||||
}
|
||||
|
||||
#results tr:nth-child(even) > td {
|
||||
border: none;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
#results tr:nth-child(odd) > td {
|
||||
border: none;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#results th {
|
||||
background: #f3f3f3;
|
||||
border-top: none;
|
||||
border-bottom: 1px solid #eae9e9;
|
||||
padding: 3px 9px;
|
||||
line-height: 24px;
|
||||
text-transform: uppercase;
|
||||
font-size: 10px;
|
||||
color: #6a6a6a;
|
||||
font-weight: bold;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
#results tr.selected td {
|
||||
background: #3874d7;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
#results td {
|
||||
color: #3a3633;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
vertical-align: middle;
|
||||
border: 0px none;
|
||||
}
|
||||
|
||||
#results th:first-child,
|
||||
#results td:first-child {
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
#custom_query {
|
||||
height: 205px;
|
||||
}
|
||||
|
||||
#sidebar ul {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#sidebar ul li {
|
||||
list-style: none;
|
||||
list-style-type: none;
|
||||
line-height: 28px;
|
||||
padding: 0px 8px;
|
||||
cursor: pointer;
|
||||
color: #a8a8a8 !important;
|
||||
}
|
||||
|
||||
#sidebar li.selected {
|
||||
color: #fff !important;
|
||||
font-weight: bold;
|
||||
background: rgba(69,69,69,.6);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
@ -1,191 +1,34 @@
|
||||
<style>
|
||||
#nav {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 39px;
|
||||
background: #ccc;
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
width: 249px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 40;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
overflow: scroll;
|
||||
border-right: 1px solid #aaa;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#body {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 250px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
#input {
|
||||
position: absolute;
|
||||
border-bottom: 1px solid #aaa;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
height: 249px;
|
||||
}
|
||||
|
||||
#output {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 250px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
#results {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#results td {
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#custom_query {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
ul li {
|
||||
list-style: none;
|
||||
list-style-type: none;
|
||||
line-height: 25px;
|
||||
padding: 0px 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
ul li:hover {
|
||||
background: #ddd;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="nav"></div>
|
||||
|
||||
<div id="sidebar"><ul id="tables"></ul></div>
|
||||
<div id="nav">
|
||||
<ul>
|
||||
<li id="table_content">Content</li>
|
||||
<li id="table_structure">Structure</li>
|
||||
<li id="table_indexes">Indexes</li>
|
||||
<li id="table_query" class="selected">Run Query</li>
|
||||
<li id="table_history">History</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="sidebar">
|
||||
<div class="wrapper"><ul id="tables"></ul></div>
|
||||
</div>
|
||||
<div id="body">
|
||||
<div id="input">
|
||||
<textarea id="custom_query"></textarea>
|
||||
<input type="button" value="Run" class="btn btn-sm btn-primary" id="run" />
|
||||
<a href="#" class="btn btn-default btn-sm">Query History</a>
|
||||
<div class="wrapper">
|
||||
<div id="custom_query"></div>
|
||||
<div class="actions">
|
||||
<input type="button" id="run" value="Run Query" class="btn btn-sm btn-primary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="output">
|
||||
<table id="results" class="table table-striped"></table>
|
||||
<div class="wrapper">
|
||||
<table id="results" class="table table-striped"></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<link rel="stylesheet" href="/app/css/bootstrap.min.css" />
|
||||
<script type="text/javascript"></script>
|
||||
<script type="text/javascript" src="/app/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/app/js/ace.js"></script>
|
||||
<script type="text/javascript" src="/app/js/ace-pgsql.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
function getTables(cb) {
|
||||
$.getJSON("/tables", function(resp) {
|
||||
cb(resp);
|
||||
});
|
||||
}
|
||||
|
||||
function executeQuery(query, cb) {
|
||||
$.ajax({
|
||||
url: "/query",
|
||||
method: "post",
|
||||
cache: false,
|
||||
data: { query: query, format: "json" },
|
||||
success: function(data) {
|
||||
cb(data);
|
||||
},
|
||||
error: function(xhr, status, data) {
|
||||
cb(jQuery.parseJSON(xhr.responseText));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildTable(results) {
|
||||
$("#results").text("");
|
||||
|
||||
if (!results.rows) {
|
||||
$("<tr><td>No records found</tr></tr>").appendTo("#results");
|
||||
return;
|
||||
}
|
||||
|
||||
if (results.error) {
|
||||
$("<tr><td>ERROR: " + results.error + "</tr></tr>").appendTo("#results");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var cols = "";
|
||||
var rows = ""
|
||||
|
||||
results.columns.forEach(function(col) {
|
||||
cols += "<th>" + col + "</th>";
|
||||
});
|
||||
|
||||
results.rows.forEach(function(row) {
|
||||
var r = "";
|
||||
for (i in row) { r += "<td>" + row[i] + "</td>"; }
|
||||
rows += "<tr>" + r + "</tr>";
|
||||
});
|
||||
|
||||
$("<thead>" + cols + "</thead><tbody>" + rows + "</tobdy>").appendTo("#results");
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
/*
|
||||
var editor = ace.edit("custom_query");
|
||||
editor.getSession().setMode("ace/mode/pgsql");
|
||||
editor.getSession().setTabSize(2);
|
||||
editor.getSession().setUseSoftTabs(true);
|
||||
*/
|
||||
|
||||
$("#run").on("click", function() {
|
||||
var query = $("#custom_query").val();
|
||||
|
||||
executeQuery(query, function(data) {
|
||||
buildTable(data);
|
||||
});
|
||||
});
|
||||
|
||||
$("#tables").on("click", "li", function() {
|
||||
var query = "SELECT * FROM " + $(this).text() + " ORDER BY id DESC LIMIT 100";
|
||||
|
||||
executeQuery(query, function(data) {
|
||||
buildTable(data);
|
||||
});
|
||||
});
|
||||
|
||||
getTables(function(data) {
|
||||
data.forEach(function(item) {
|
||||
$("<li>" + item + "</li>").appendTo("#tables");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
<link rel="stylesheet" href="/static/css/bootstrap.css" />
|
||||
<link rel="stylesheet" href="/static/css/app.css" />
|
||||
<script type="text/javascript" src="/static/js/jquery.js"></script>
|
||||
<script type="text/javascript" src="/static/js/ace.js"></script>
|
||||
<script type="text/javascript" src="/static/js/ace-pgsql.js"></script>
|
||||
<script type="text/javascript" src="/static/js/app.js"></script>
|
191
static/js/app.js
Normal file
191
static/js/app.js
Normal file
@ -0,0 +1,191 @@
|
||||
function apiCall(path, cb) {
|
||||
$.getJSON(path, function(resp) { cb(resp); });
|
||||
}
|
||||
|
||||
function getTables(cb) { apiCall("/tables", cb); }
|
||||
function getTableStructure(table, cb) { apiCall("/tables/" + table, cb); }
|
||||
function getTableIndexes(table, cb) { apiCall("/tables/" + table + "/indexes", cb); }
|
||||
function getHistory(cb) { apiCall("/history", cb); }
|
||||
|
||||
function executeQuery(query, cb) {
|
||||
$.ajax({
|
||||
url: "/query",
|
||||
method: "post",
|
||||
cache: false,
|
||||
data: { query: query, format: "json" },
|
||||
success: function(data) {
|
||||
cb(data);
|
||||
},
|
||||
error: function(xhr, status, data) {
|
||||
cb(jQuery.parseJSON(xhr.responseText));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadTables() {
|
||||
getTables(function(data) {
|
||||
data.forEach(function(item) {
|
||||
$("<li>" + item + "</li>").appendTo("#tables");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function buildTable(results) {
|
||||
$("#results").text("").removeClass("empty");
|
||||
|
||||
if (results.error) {
|
||||
$("<tr><td>ERROR: " + results.error + "</tr></tr>").appendTo("#results");
|
||||
$("#results").addClass("empty");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!results.rows) {
|
||||
$("<tr><td>No records found</tr></tr>").appendTo("#results");
|
||||
$("#results").addClass("empty");
|
||||
return;
|
||||
}
|
||||
|
||||
var cols = "";
|
||||
var rows = ""
|
||||
|
||||
results.columns.forEach(function(col) {
|
||||
cols += "<th>" + col + "</th>";
|
||||
});
|
||||
|
||||
results.rows.forEach(function(row) {
|
||||
var r = "";
|
||||
for (i in row) { r += "<td>" + row[i] + "</td>"; }
|
||||
rows += "<tr>" + r + "</tr>";
|
||||
});
|
||||
|
||||
$("<thead>" + cols + "</thead><tbody>" + rows + "</tobdy>").appendTo("#results");
|
||||
}
|
||||
|
||||
function setCurrentTab(id) {
|
||||
$("#nav ul li.selected").removeClass("selected");
|
||||
$("#" + id).addClass("selected");
|
||||
}
|
||||
|
||||
function showQueryHistory() {
|
||||
getHistory(function(data) {
|
||||
var rows = [];
|
||||
|
||||
for(i in data) {
|
||||
rows.unshift([parseInt(i) + 1, data[i]]);
|
||||
}
|
||||
|
||||
buildTable({ columns: ["id", "query"], rows: rows });
|
||||
|
||||
setCurrentTab("table_history");
|
||||
$("#input").hide();
|
||||
$("#output").addClass("full");
|
||||
});
|
||||
}
|
||||
|
||||
function showTableIndexes() {
|
||||
var name = $("#tables li.selected").text();
|
||||
|
||||
if (name.length == 0) {
|
||||
alert("Please select a table!");
|
||||
return;
|
||||
}
|
||||
|
||||
getTableIndexes(name, function(data) {
|
||||
setCurrentTab("table_indexes");
|
||||
buildTable(data);
|
||||
|
||||
$("#input").hide();
|
||||
$("#output").addClass("full");
|
||||
});
|
||||
}
|
||||
|
||||
function showTableContent() {
|
||||
var name = $("#tables li.selected").text();
|
||||
|
||||
if (name.length == 0) {
|
||||
alert("Please select a table!");
|
||||
return;
|
||||
}
|
||||
|
||||
var query = "SELECT * FROM " + name + " LIMIT 100;";
|
||||
|
||||
executeQuery(query, function(data) {
|
||||
buildTable(data);
|
||||
setCurrentTab("table_content");
|
||||
|
||||
$("#input").hide();
|
||||
$("#output").addClass("full");
|
||||
});
|
||||
}
|
||||
|
||||
function showTableStructure() {
|
||||
var name = $("#tables li.selected").text();
|
||||
|
||||
if (name.length == 0) {
|
||||
alert("Please select a table!");
|
||||
return;
|
||||
}
|
||||
|
||||
getTableStructure(name, function(data) {
|
||||
setCurrentTab("table_structure");
|
||||
buildTable(data);
|
||||
});
|
||||
}
|
||||
|
||||
function runQuery() {
|
||||
setCurrentTab("table_query");
|
||||
|
||||
executeQuery(editor.getValue(), function(data) {
|
||||
buildTable(data);
|
||||
$("#input").show();
|
||||
$("#output").removeClass("full");
|
||||
});
|
||||
}
|
||||
|
||||
var editor;
|
||||
|
||||
$(document).ready(function() {
|
||||
editor = ace.edit("custom_query");
|
||||
editor.getSession().setMode("ace/mode/pgsql");
|
||||
editor.getSession().setTabSize(2);
|
||||
editor.getSession().setUseSoftTabs(true);
|
||||
|
||||
$("#table_content").on("click", function() {
|
||||
showTableContent();
|
||||
});
|
||||
|
||||
$("#table_structure").on("click", function() {
|
||||
showTableStructure();
|
||||
});
|
||||
|
||||
$("#table_indexes").on("click", function() {
|
||||
showTableIndexes();
|
||||
});
|
||||
|
||||
$("#table_history").on("click", function() {
|
||||
showQueryHistory();
|
||||
});
|
||||
|
||||
$("#table_query").on("click", function() {
|
||||
setCurrentTab("table_query");
|
||||
$("#input").show();
|
||||
$("#output").removeClass("full");
|
||||
});
|
||||
|
||||
$("#run").on("click", function() {
|
||||
runQuery();
|
||||
});
|
||||
|
||||
$("#results").on("click", "tr", function() {
|
||||
$("#results tr.selected").removeClass();
|
||||
$(this).addClass("selected");
|
||||
});
|
||||
|
||||
$("#tables").on("click", "li", function() {
|
||||
$("#tables li.selected").removeClass("selected");
|
||||
$(this).addClass("selected");
|
||||
showTableContent();
|
||||
});
|
||||
|
||||
loadTables();
|
||||
});
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user