Files
speckle-server/modules/apiexplorer/explorer.html
T
Dimitrie Stefanescu d9414b5f91 feat(apps): graphql explorer proper challenges & logout
+ adds server information in the header
2020-06-10 13:31:08 +01:00

440 lines
10 KiB
HTML

<!DOCTYPE html>
<html>
<html>
<head>
<title>Speckle GraphQL API</title>
<link href="https://unpkg.com/graphiql/graphiql.min.css" rel="stylesheet" />
</head>
<body style="margin: 0; background-color: #262E37">
<div id='speckle-header'>
<div class='header'>
<span class='header-title'>Speckle GraphQL API</span>
<span class='header-text' style='float: right;'>Hello <b><span id='username'>$Name</span> @ <span class='' style='' id='serverDetails'>$ServerDetails</span></b> <button class='logout-button' onclick="logout()">logout</button></span>
</div>
<div class='warning'>
<div>
<span>👋 &nbsp;Heads up! Speckle's GraphQL Explorer makes use of your <b>real, live, production data</b> from this server.</span>
</div>
</div>
</div>
<div id="graphiql" style="height: 90vh;"></div>
<script crossorigin src="https://unpkg.com/react/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom/umd/react-dom.production.min.js"></script>
<script crossorigin src="https://unpkg.com/graphiql/graphiql.min.js"></script>
<script>
const graphQLFetcher = graphQLParams =>
fetch( '/graphql', {
method: 'post',
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + localStorage.getItem( 'AuthToken' ) },
body: JSON.stringify( graphQLParams ),
} )
.then( response => response.json( ) )
.catch( ( ) => response.text( ) )
function redirectToAuth( ) {
localStorage.setItem( 'challenge', Math.random( ).toString( 36 ).substring( 2, 15 ) + Math.random( ).toString( 36 ).substring( 2, 15 ) )
window.location = '/auth?appId=explorer&challenge=' + localStorage.getItem( 'challenge' )
}
async function accessCodeExchange( accessCode ) {
window.history.replaceState( {}, document.title, '/explorer' )
let response = await fetch( '/auth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify( {
accessCode: accessCode,
appId: 'explorer',
appSecret: 'explorer',
challenge: localStorage.getItem( 'challenge' )
} )
} )
let data = await response.json( )
if ( data.hasOwnProperty( 'token' ) ) {
localStorage.removeItem( 'challenge' )
localStorage.setItem( 'AuthToken', data.token )
localStorage.setItem( 'RefreshToken', data.refreshToken )
await setUserName( )
await initGQL( data.token )
}
}
async function initGQL( token ) {
// GraphQLPlayground.init( document.getElementById( 'root' ), {
// endpointUrl: '/graphql',
// headers: {
// 'Authorization': 'Bearer ' + token
// }
// } )
ReactDOM.render(
React.createElement( GraphiQL, { fetcher: graphQLFetcher } ),
document.getElementById( 'graphiql' ),
)
await setServerInfo( )
}
async function setUserName( ) {
let testResponse = await fetch( '/graphql', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + localStorage.getItem( 'AuthToken' ),
'Content-Type': 'application/json'
},
body: JSON.stringify( { query: `{ user { name } }` } )
} )
let data = ( await testResponse.json( ) ).data
document.getElementById( 'username' ).innerHTML = data.user.name
}
async function setServerInfo( ) {
let testResponse = await fetch( '/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify( { query: `{ serverInfo { name company } }` } )
} )
let data = ( await testResponse.json( ) ).data
document.getElementById( 'serverDetails' ).innerHTML = `<b>${data.serverInfo.name}</b> deployed by <b>${data.serverInfo.company}</b>`
}
function logout( ) {
localStorage.removeItem( 'AuthToken' )
localStorage.removeItem( 'RefreshToken' )
window.location = '/explorer'
}
window.addEventListener( 'load', async function ( event ) {
let urlParams = new URLSearchParams( window.location.search )
let accessCode = urlParams.get( 'access_code' )
if ( accessCode ) {
accessCodeExchange( accessCode )
} else {
let token = localStorage.getItem( 'AuthToken' )
if ( token ) {
let testResponse = await fetch( '/graphql', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: JSON.stringify( { query: `{ user { name } }` } )
} )
let data = ( await testResponse.json( ) ).data
// if res.data.user is non null, means the ping was ok & token is valid
if ( data.user ) {
console.log( data.user )
document.getElementById( 'username' ).innerHTML = data.user.name
await initGQL( token )
}
} else {
redirectToAuth( )
}
}
} )
</script>
</body>
<style>
#speckle-header {
padding: 0px;
box-sizing: border-box;
font-family: Roboto, sans-serif;
}
.warning {
padding: 20px;
box-sizing: border-box;
background-color: #262E37;
color: white;
}
.header {
padding: 10px 20px;
box-sizing: border-box;
background-color: #0A66FF;
color: white;
}
.header-title {
font-size: 20px;
font-weight: 500;
letter-spacing: 0.25px;
line-height: 40px;
}
.header-text {
font-size: small;
font-weight: 200;
line-height: 40px;
}
#serverDetails {
font-size: small;
font-weight: 200;
}
.logout-button {
background-color: #0A66FF;
border-radius: 2px;
border: 1px solid #FFFFFF;
display: inline-block;
cursor: pointer;
color: #FFFFFF;
font-family: Arial;
font-size: small;
padding: 3px 15px;
text-decoration: none;
margin-left: 5px;
/*text-shadow: 0px 1px 0px #2f6627;*/
}
.logout-button:hover {
background-color: #262E37;
}
/* GraphiQL One Dark Alt (Dark Mode) theme by Ben Keating[1]
* Colors taken from Atom's One Dark theme[2]. Add this file to the end of
* your <head> block[3] to override built-in default styling.
*
* [1]. https://twitter.com/flowpoke
* [2]. https://github.com/atom/atom/tree/master/packages/one-dark-ui
* [3]. e.g. `.../site-packages/graphene_django/templates/graphene/graphiql.html`
*/
.CodeMirror {
background: #282D34 !important;
}
.graphiql-container .doc-explorer-contents,
.graphiql-container .history-contents {
background-color: #21262B;
border-top: 1px solid #181A1F;
}
.graphiql-container .toolbar-button {
background: #1c2125 !important;
box-shadow: none !important;
color: #5c626d !important;
border: 1px solid #181a1f !important;
}
.graphiql-container .result-window .CodeMirror-gutters {
background: #282D33;
border: none !important;
}
.graphiql-container .resultWrap {
border-left: solid 1px #181a1f;
}
.graphiql-container .variable-editor-title {
background: #21262b;
border-bottom: 1px solid #181a1f;
border-top: 1px solid #181a1f;
color: #cacdd3;
}
.graphiql-container .topBar {
background: #21262B;
border-color: #181A1F;
}
.graphiql-container .docExplorerHide {
color: #606671;
}
.graphiql-container .doc-explorer-title,
.graphiql-container .history-title,
.doc-explorer-back {
color: #CACDD3 !important;
}
.graphiql-container .doc-explorer {
background: #21262b;
}
.graphiql-container .docExplorerWrap,
.graphiql-container .historyPaneWrap {
box-shadow: none;
}
.graphiql-container .docExplorerShow {
border-left: none;
}
.graphiql-container .docExplorerShow,
.graphiql-container .historyShow {
background: #21262b;
border-bottom: 1px solid #181A1E;
color: #CACDD3;
}
.graphiql-container .docExplorerShow:before,
.graphiql-container .doc-explorer-back:before {
border-color: #CACDD3;
}
.graphiql-container .search-box {
margin: auto auto 10px auto;
border: none;
}
.graphiql-container .search-box input {
background: #1E2127;
padding-left: 28px;
}
.graphiql-container .search-box .search-box-clear,
.graphiql-container .search-box .search-box-clear:hover {
background: #1d2126;
}
.graphiql-container .search-box:before {
color: #c1c4ca;
font-size: 21px;
left: 8px;
}
.graphiql-container,
.graphiql-container button,
.graphiql-container input {
color: #9299A7;
}
.CodeMirror-gutters {
border: none !important;
background-color: #282d33;
}
.graphiql-container .execute-button {
background: #21262b;
border: 1px solid rgb(91, 98, 107);
box-shadow: none !important;
fill: #c9ccd2;
}
.graphiql-container .history-contents p {
border: none;
}
.graphiql-container .historyPaneWrap {
background: #21262b;
}
.graphiql-container .execute-options>li.selected,
.graphiql-container .toolbar-menu-items>li.hover,
.graphiql-container .toolbar-menu-items>li:active,
.graphiql-container .toolbar-menu-items>li:hover,
.graphiql-container .toolbar-select-options>li.hover,
.graphiql-container .toolbar-select-options>li:active,
.graphiql-container .toolbar-select-options>li:hover,
.graphiql-container .history-contents>p:hover,
.graphiql-container .history-contents>p:active {
background: #383C41;
}
.graphiql-container .doc-category-title {
border-bottom: 1px solid #181a1f;
color: #cacdd3;
}
.graphiql-container .field-name {
color: #9CA3AC;
}
.graphiql-container .type-name {
color: #95be76;
}
.cm-property {
color: #A5ACB8;
}
.cm-string {
color: #97BE7B;
}
.cm-variable {
color: #a87f5b;
}
.cm-attribute {
color: #B58860;
}
.cm-def {
color: #CC3932;
}
.cm-keyword {
color: #7cf3ff;
}
.graphiql-container .keyword {
color: #9ea5b0;
}
.graphiql-container .arg-name {
color: #b5875d;
}
.graphiql-container .doc-category-item {
color: #BC6069;
}
a {
color: #7b9ad4;
}
.CodeMirror-lint-tooltip {
background: #1a1e22 !important;
color: red;
}
.cm-atom {
color: #d27caf;
}
.CodeMirror-hints {
background: #21262a;
box-shadow: 0 16px 13px -10px rgba(0, 0, 0, 0.3);
}
.CodeMirror-hint {
border-top: solid 1px #212629;
color: #8ab16f;
}
.CodeMirror-hint-information {
border-top: solid 1px #181a1e;
}
li.CodeMirror-hint-active {
background-color: #262c2f;
border-top-color: #212629;
color: #b8ff87;
}
.CodeMirror-hint-information .content {
color: #a4abb7;
}
</style>
</html>