Updated DigitalOcean UX, proxied requests to it

This commit is contained in:
Ivan Gromov 2020-10-28 00:54:45 +05:00
parent ca3230c0de
commit 80f04de3ec
4 changed files with 144 additions and 62 deletions

View file

@ -1,18 +1,28 @@
import asyncio
import concurrent.futures
import json
import os
import sys
from os.path import join, dirname, expanduser
import yaml
import boto3
from os.path import join, dirname, expanduser
from aiohttp import web, ClientSession
import concurrent.futures
import sys
import os
from google.auth.transport.requests import AuthorizedSession
from google.oauth2 import service_account
from playbook import PlaybookCLI
try:
import boto3
HAS_BOTO3 = True
except ImportError:
HAS_BOTO3 = False
try:
from google.auth.transport.requests import AuthorizedSession
from google.oauth2 import service_account
HAS_GOOGLE_LIBRARIES = True
except ImportError:
HAS_GOOGLE_LIBRARIES = False
routes = web.RouteTableDef()
PROJECT_ROOT = dirname(dirname(__file__))
pool = None
@ -63,7 +73,7 @@ async def playbook_post_handler(request):
@routes.delete('/playbook')
async def playbook_delete_handler(request):
async def playbook_delete_handler(_):
global task_future
if not task_future:
return web.json_response({'ok': False})
@ -105,6 +115,30 @@ async def post_exit(_):
sys.exit(1)
@routes.get('/do_config')
async def check_do_config(_):
return web.json_response({"ok": 'DO_API_TOKEN' in os.environ})
@routes.get('/do_regions')
async def do_regions(request):
if 'token' in request.query:
token = request.query['token']
elif 'DO_API_TOKEN' in os.environ:
token = os.environ['DO_API_TOKEN']
else:
return web.json_response({'error': 'no token provided'}, status=400)
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer {0}'.format(token),
}
async with ClientSession(headers=headers) as session:
async with session.get('https://api.digitalocean.com/v2/regions') as r:
json_body = await r.json()
return web.json_response(json_body, status=r.status)
@routes.post('/lightsail_regions')
async def lightsail_regions(request):
data = await request.json()
@ -132,7 +166,7 @@ async def ec2_regions(request):
@routes.get('/gce_config')
async def check_gce_config(request):
async def check_gce_config(_):
gce_file = join(PROJECT_ROOT, 'configs', 'gce.json')
response = {}
try:
@ -189,7 +223,7 @@ async def check_vultr_config(request):
@routes.get('/vultr_regions')
async def vultr_regions(request):
async def vultr_regions(_):
async with ClientSession() as session:
async with session.get('https://api.vultr.com/v1/regions/list') as r:
json_body = await r.json()
@ -197,7 +231,7 @@ async def vultr_regions(request):
@routes.get('/scaleway_config')
async def check_scaleway_config(request):
async def check_scaleway_config(_):
return web.json_response({"ok": 'SCW_TOKEN' in os.environ})

View file

@ -5,41 +5,37 @@
Enter your API token. The token must have read and write permissions
(<a href="https://cloud.digitalocean.com/settings/api/tokens" target="_blank" rel="noopener noreferrer">https://cloud.digitalocean.com/settings/api/tokens</a>):
</label>
<input
<div v-if="ui_token_from_env">
<input
type="password"
class="form-control"
v-bind:disabled="ui_loading_check"
v-bind:value="'1234567890abcdef'"
/>
<div v-if="ui_token_from_env" class="form-text alert alert-success" role="alert">
The token was read from the environment variable
</div>
</div>
<div v-else>
<input
type="text"
class="form-control"
id="id_do_token"
name="do_token"
v-bind:disabled="ui_loading_check"
v-model="do_token"
@blur="load_do_regions"
/>
@blur="load_regions"
/>
</div>
</div>
<div class="form-group">
<label
v-if="do_regions.length > 0"
for="id_region"
>What region should the server be located in?</label>
<label
v-if="do_regions.length === 0"
for="id_region"
>Please enter API key above to select region</label>
<label v-if="do_region_loading" for="id_region">Loading regions...</label>
<select
name="region"
id="id_region"
class="form-control"
v-model="do_region"
v-bind:disabled="do_region_loading"
>
<option value disabled>Select region</option>
<option
v-for="(region, i) in do_regions"
v-bind:key="region.slug"
v-bind:value="region.slug"
>{{region.name}}</option>
</select>
</div>
<button v-on:click="submit" v-bind:disabled="!do_region" class="btn btn-primary" type="button">Next</button>
<region-select v-model="region"
v-bind:options="ui_region_options"
v-bind:loading="ui_loading_check || ui_loading_regions"
v-bind:error="ui_region_error">
</region-select>
<button v-on:click="submit"
v-bind:disabled="!is_valid" class="btn btn-primary" type="button">Next</button>
</div>
</template>
@ -48,34 +44,76 @@ module.exports = {
data: function() {
return {
do_token: null,
do_region: null,
do_regions: [],
do_region_loading: false
region: null,
// helper variables
ui_loading_check: false,
ui_loading_regions: false,
ui_region_error: null,
ui_token_from_env: false,
ui_region_options: []
}
},
computed: {
is_valid() {
return (this.do_config || this.ui_token_from_env) && this.region;
}
},
created: function() {
this.check_config();
},
methods: {
load_do_regions: function () {
this.do_region_loading = true;
fetch('https://api.digitalocean.com/v2/regions', {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.do_token}`
check_config() {
this.ui_loading_check = true;
return fetch("/do_config")
.then(r => r.json())
.then(response => {
if (response.ok) {
this.ui_token_from_env = true;
this.load_regions();
}
})
.then(r => r.json())
.then(r => {
this.do_regions = r.regions;
.finally(() => {
this.ui_loading_check = false;
});
},
load_regions() {
if (this.ui_token_from_env || this.do_token) {
this.ui_loading_regions = true;
this.ui_region_error = null;
const url = this.ui_token_from_env ? "/do_regions" : "/do_regions?token=" + this.do_token;
fetch(url)
.then((r) => {
if (r.status === 200) {
return r.json();
}
throw new Error(r.status);
})
.then((data) => {
this.ui_region_options = data.regions.map(i => ({key: i.slug, value: i.name}));
})
.catch((err) => {
this.ui_region_error = err;
})
.finally(() => {
this.do_region_loading = false;
this.ui_loading_regions = false;
});
}
},
submit() {
this.$emit('submit', {
do_token: this.do_token,
region: this.do_region
})
if (this.ui_token_from_env) {
this.$emit("submit", {
region: this.region
});
} else {
this.$emit("submit", {
do_token: this.do_token,
region: this.region
});
}
}
},
components: {
"region-select": window.httpVueLoader("/static/region-select.vue"),
}
};
</script>

View file

@ -103,6 +103,6 @@ module.exports = {
},
components: {
"region-select": window.httpVueLoader("/static/region-select.vue"),
},
}
};
</script>

View file

@ -8,6 +8,7 @@
<select
name="region"
class="form-control"
v-bind:class="{ 'is-invalid': has_error }"
v-bind:value="value"
v-bind:disabled="ui_disabled"
v-on:input="$emit('input', $event.target.value)"
@ -20,24 +21,33 @@
>{{ region.value }}</option
>
</select>
<div v-if="has_error" class="invalid-feedback">
There was an error during fetching regions
</div>
</div>
</template>
<script>
module.exports = {
props: ["value", "options", "loading"],
props: ["value", "options", "loading", "error"],
computed: {
has_options: function() {
return this.options && this.options.length;
},
ui_disabled: function() {
return !this.options.length || this.loading;
return !this.has_options || this.loading;
},
ui_show_slot: function() {
return !this.loading && !this.options.length
return !this.loading && !this.has_options
},
ui_show_loading: function() {
return this.loading;
},
ui_show_select_prompt: function() {
return this.options.length > 0;
return this.has_options;
},
has_error: function() {
return !!this.error;
}
}
};