This commit is contained in:
Ryan Burnette 2020-09-17 14:50:23 -04:00
parent 4dd014136a
commit b844baef81
35 changed files with 337 additions and 17246 deletions

View File

@ -1,16 +1,31 @@
# cdadmin
## Features
**cdadmin** is administration app for continuous deployment of static websites.
**cdadmin** is intended to be used with static websites that are generated after
changes are pushed to a Git repository. This works with sites that are being
edited in code and tracked in Git, as well as with headless CMSs that are
pushing changes via Git.
cdadmin starts a web server that provides an admin interface as well as an API.
No authentication is provided, so you'll want to reverse proxy through something
that protects the endpoints.
## Admin
The main page of the admin show the staging and production rebuild status. It
allows the user to queue a rebuild of the staging and production environments.
It also allows the user to merge staging into production.
- Create a webserver that has an API and an admin interface
- Passwordless authentication using email, sign-in available for users who are on a list
- Handles webhooks that rebuild staging
- Gives admin a button to rebuild staging
- Gives users feedback on the staging rebuild status
- Handles webhooks that rebuild production
- Gives admin a button to rebuild production
- Gives users feedback on the production rebuild status
- Gives users a button to rebase production on staging (did I say that correctly?)
- Gives users a button to rebase production on staging (did I say that
correctly?)
## Email

View File

@ -1,4 +0,0 @@
{
"node": true,
"esversion": 8
}

View File

@ -1,6 +0,0 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---

3
html/axios.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,7 +0,0 @@
baseURL = "http://example.org/"
languageCode = "en-us"
title = "cdadmin"
disableKinds = ["RSS"]
[permalinks]
pages = "/:filename/"

77
html/configure.html Normal file
View File

@ -0,0 +1,77 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>configure cdadmin</title>
<link
rel="stylesheet"
href="https://unpkg.com/bootstrap@4.1.0/dist/css/bootstrap.min.css"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="app">
<div class="container">
<div class="header">
<h1>cdadmin</h1>
<p>
<a href="status.html">Status</a>
<a href="merge.html">Merge</a>
<a href="configure.html">Configure</a>
</p>
<h2>Configure</h2>
</div>
<pre>{{configuration}}</pre>
<vue-form-generator
:schema="schema"
:model="configuration"
></vue-form-generator>
<button class="btn btn-primary">Save</button>
</div>
</div>
<script src="https://unpkg.com/vue@2.6.12/dist/vue.js"></script>
<script src="https://unpkg.com/vue-form-generator@2.3.4/dist/vfg.js"></script>
<script src="https://unpkg.com/axios@0.20.0/dist/axios.min.js"></script>
<script>
new Vue({
el: '.app',
components: {
'vue-form-generator': VueFormGenerator.component
},
data: {
configuration: {
repository: '',
production: '',
staging: ''
},
schema: {
fields: [
{
type: 'input',
inputType: 'text',
label: 'Repository',
model: 'repository',
required: true
},
{
type: 'input',
inputType: 'text',
label: 'Production Branch',
model: 'production',
required: true
},
{
type: 'input',
inputType: 'text',
label: 'Staging Branch',
model: 'staging',
required: true
}
]
},
formOptions: {}
}
});
</script>
</body>
</html>

View File

@ -1,119 +0,0 @@
---
title: Sign In
type: page
---
<style>
html,
body {
height: 100%;
}
body {
display: -ms-flexbox;
display: -webkit-box;
display: flex;
-ms-flex-align: center;
-ms-flex-pack: center;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
}
.app {
width: 100%;
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-signin .checkbox {
font-weight: 400;
}
.form-signin .form-control {
position: relative;
box-sizing: border-box;
height: auto;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type='email'] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type='password'] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
</style>
<div class="app">
<form class="form-signin text-center" @submit="submit($event)">
<label for="inputEmail" class="sr-only">Email address</label>
<input
type="email"
id="inputEmail"
class="form-control"
placeholder="Email address"
required
autofocus
v-model="email"
/>
<br />
<button class="btn btn-lg btn-primary btn-block" type="submit">
Sign in
</button>
</form>
</div>
<script src="/axios.js"></script>
<script src="/vue.js"></script>
<script src="/messages.js"></script>
<script>
var app = new Vue({
data: {
email: ''
},
methods: {
submit: function (ev) {
var _this = this;
if (ev) {
ev.preventDefault();
}
axios
.post('/api/authentication/signin', {
email: _this.email
})
.then(function (response) {})
.catch(function (error) {
console.log(_this.email);
window.postMessage('Something went wrong. Please try again.');
});
}
}
});
var token = window.localStorage.getItem('token');
if (token) {
axios
.get('/api/authentication/me', {
headers: {
Authorization: 'Bearer ' + token
}
})
.then(function (response) {
window.location.href = '/';
})
.catch(function (error) {
window.localStorage.removeItem('token');
app.$mount('.app');
});
} else {
app.$mount('.app');
}
</script>

View File

@ -1,9 +0,0 @@
<!DOCTYPE html>
<html lang="{{ .Site.Language.Lang }}">
{{ partial "head.html" . }}
<body>
<!-- prettier-ignore -->
{{ block "main" . }}
{{ end }}
</body>
</html>

View File

@ -1 +0,0 @@
list

View File

@ -1 +0,0 @@
default single

View File

@ -1 +0,0 @@
{{ define "main" }} {{ .Content }} {{ end }}

View File

@ -1 +0,0 @@
<link rel="stylesheet" href="/bootstrap.min.css" />

View File

@ -1,5 +0,0 @@
<header class="header">
<div class="container">
<h1>cdadmin</h1>
</div>
</header>

97
html/merge.html Normal file
View File

@ -0,0 +1,97 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>cdadmin</title>
<link
rel="stylesheet"
href="https://unpkg.com/bootstrap@4.1.0/dist/css/bootstrap.min.css"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="app">
<div class="container">
<div class="header">
<h1>cdadmin</h1>
<p>
<a href="status.html">Status</a>
<a href="merge.html">Merge</a>
<a href="configure.html">Configure</a>
</p>
</div>
<div class="section">
<h2>Production</h2>
<p>
URL: <a href="#">URL</a><br />
Status: <br />
Branch: <br />
Commit:
</p>
<p>
<button class="btn btn-primary">Rebuild</button>
</p>
</div>
<section>
<h2>Staging</h2>
<p>
URL: <a href="#">URL</a><br />
Status: <br />
Branch: <br />
Commit:
</p>
<p>
<button class="btn btn-primary">Rebuild</button>
<button class="btn btn-primary">
Merge Staging Into Production
</button>
</p>
</section>
</div>
</div>
<script src="https://unpkg.com/vue@2.6.12/dist/vue.js"></script>
<script src="https://unpkg.com/vue-form-generator@2.3.4/dist/vfg.js"></script>
<script src="https://unpkg.com/axios@0.20.0/dist/axios.min.js"></script>
<script>
new Vue({
el: '.app',
components: {
'vue-form-generator': VueFormGenerator.component
},
data: {
configuration: {
repository: '',
production: '',
staging: ''
},
schema: {
fields: [
{
type: 'input',
inputType: 'text',
label: 'Repository',
model: 'repository',
required: true
},
{
type: 'input',
inputType: 'text',
label: 'Production Branch',
model: 'production',
required: true
},
{
type: 'input',
inputType: 'text',
label: 'Staging Branch',
model: 'staging',
required: true
}
]
},
formOptions: {}
}
});
</script>
</body>
</html>

3311
html/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +0,0 @@
{
"name": "html",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^0.20.0",
"bootstrap": "^4.5.2",
"vue": "^2.6.12"
}
}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
list

View File

@ -1 +0,0 @@
list

View File

@ -1,2 +0,0 @@

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>http://example.org/categories/</loc>
</url>
<url>
<loc>http://example.org/</loc>
</url>
<url>
<loc>http://example.org/signin/</loc>
</url>
<url>
<loc>http://example.org/tags/</loc>
</url>
</urlset>

View File

@ -1 +0,0 @@
list

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,14 +0,0 @@
(function () {
'use strict';
var app = new Vue({
data: {
messages: []
}
});
window.cdadmin = window.cdadmin || {};
window.cdadmin.pushMessage = function (str) {
window.alert(str);
};
})();

File diff suppressed because it is too large Load Diff

94
html/status.html Normal file
View File

@ -0,0 +1,94 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>cdadmin</title>
<link
rel="stylesheet"
href="https://unpkg.com/bootstrap@4.1.0/dist/css/bootstrap.min.css"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="app">
<div class="container">
<div class="header">
<h1>cdadmin</h1>
<p>
<a href="status.html">Status</a>
<a href="merge.html">Merge</a>
<a href="configure.html">Configure</a>
</p>
</div>
<div class="section">
<h2>Production</h2>
<p>
URL: <a href="#">URL</a><br />
Status: <br />
Branch: <br />
Commit:
</p>
<p>
<button class="btn btn-primary">Rebuild</button>
</p>
</div>
<section>
<h2>Staging</h2>
<p>
URL: <a href="#">URL</a><br />
Status: <br />
Branch: <br />
Commit:
</p>
<p>
<button class="btn btn-primary">Rebuild</button>
</p>
</section>
</div>
</div>
<script src="https://unpkg.com/vue@2.6.12/dist/vue.js"></script>
<script src="https://unpkg.com/vue-form-generator@2.3.4/dist/vfg.js"></script>
<script src="https://unpkg.com/axios@0.20.0/dist/axios.min.js"></script>
<script>
new Vue({
el: '.app',
components: {
'vue-form-generator': VueFormGenerator.component
},
data: {
configuration: {
repository: '',
production: '',
staging: ''
},
schema: {
fields: [
{
type: 'input',
inputType: 'text',
label: 'Repository',
model: 'repository',
required: true
},
{
type: 'input',
inputType: 'text',
label: 'Production Branch',
model: 'production',
required: true
},
{
type: 'input',
inputType: 'text',
label: 'Staging Branch',
model: 'staging',
required: true
}
]
},
formOptions: {}
}
});
</script>
</body>
</html>

17
html/style.css Normal file
View File

@ -0,0 +1,17 @@
.header {
padding: 3rem 0;
text-align: center;
}
.header h1 {
font-weight: bold;
}
.header h2 {
font-weight: bold;
}
.header p {
margin: 2rem 0;
padding: 0;
}
.header p a {
padding: 0 0.5rem;
}

24
package-lock.json generated Normal file
View File

@ -0,0 +1,24 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"axios": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz",
"integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==",
"requires": {
"follow-redirects": "^1.10.0"
}
},
"follow-redirects": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz",
"integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA=="
},
"vue": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz",
"integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg=="
}
}
}

6
vue.min.js vendored Normal file

File diff suppressed because one or more lines are too long