Skip to content

Commit 19e8fc6

Browse files
committed
first release
1 parent c25d6dc commit 19e8fc6

11 files changed

Lines changed: 322 additions & 2 deletions

File tree

‎AzureKuduInstanceSelector.zip‎

4.87 KB
Binary file not shown.

‎Chrome Web Store/screenshot.png‎

36.9 KB
Loading

‎README.md‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,25 @@
1-
# AzureKuduInstanceSelector
2-
Launch Azure App Service Kudu and console with selecting an instance on Chrome.
1+
# Instance selector for Azure App Service Kudu
2+
3+
Chrome Extension to launch Azure App Service Kudu or console with an instance selected.
4+
In Azure App Service for Linux, where multiple instances are running, it is difficult to make an SSH connection to the intended instance.
5+
This Chrome extension makes it easy for you to do that.
6+
7+
## Feature
8+
9+
- SSH connection to the specified instance
10+
- Open Kudu with a specific instance
11+
12+
## Requirement
13+
14+
- Google chrome
15+
16+
## How to use
17+
18+
1. Open the App Service page where you want to make an SSH connection or open Kudu in Azure Portal.
19+
1. Click 'Launch Azure Kudu' extension icon on Google Chrome.
20+
1. Click the "Kudu" or "console" button of the target instance.
21+
22+
## Copyright
23+
24+
© 2020 pnop, Inc.
25+

‎css/popup.css‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
html {
2+
padding: 0;
3+
margin: 0;
4+
}
5+
6+
body {
7+
margin: 0px;
8+
padding: 0;
9+
background-color: #ffffff;
10+
overflow-x: hidden;
11+
min-width: 200px;
12+
}
13+
14+
ul.instances {
15+
padding: 0;
16+
margin: 0;
17+
position: relative;
18+
}
19+
20+
li.instance {
21+
color: #0078d4;
22+
border-left: solid 6px #0078d4;
23+
background-color: #f1f8ff;
24+
margin: 3px 0;
25+
line-height: 1.5;
26+
padding: 0.5em;
27+
list-style-type: none;
28+
display: flex;
29+
justify-content: flex-end;
30+
flex-wrap: nowrap;
31+
}
32+
33+
li.instance span {
34+
display: inline-block;
35+
font-weight: bold;
36+
white-space: nowrap;
37+
margin: auto auto auto 0;
38+
padding-right: 0.5em;
39+
}
40+
41+
li.instance a {
42+
position: relative;
43+
display: inline-block;
44+
padding: 0.25em 0.25em;
45+
margin-left: 0.25em;
46+
margin-right: 0;
47+
text-decoration: none;
48+
color: #ffffff;
49+
background: #ff9933;
50+
border-radius: 4px;
51+
box-shadow: inset 0 2px 0 rgba(255,255,255,0.2), inset 0 -2px 0 rgba(0, 0, 0, 0, 0.05);
52+
font-weight: bold;
53+
border: solid 2px #ff6600;
54+
}
55+
56+
li.instance a:active {
57+
box-shadow: 0 0 2px rgba(0, 0, 0, 0, 0.30);
58+
}
59+
60+
.dontwork {
61+
padding: 0.5em 1em;
62+
}
63+
64+
.dontwork .message {
65+
white-space: nowrap;
66+
}

‎icon/icon128.png‎

400 Bytes
Loading

‎icon/icon16.png‎

197 Bytes
Loading

‎icon/icon48.png‎

245 Bytes
Loading

‎js/background.js‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
var authorizationToken;
2+
3+
chrome.webRequest.onBeforeSendHeaders.addListener(function(details){
4+
var headers = details.requestHeaders;
5+
for( var i = 0, l = headers.length; i < l; ++i ) {
6+
if (headers[i].name === 'Authorization') {
7+
authorizationToken = details.requestHeaders[i].value;
8+
break;
9+
}
10+
}
11+
return {requestHeaders: details.requestHeaders};
12+
},{
13+
urls: [
14+
"https://portal.azure.com/api/*", "https://portal.azure.com/AzureHubs/api/*", "https://management.azure.com/*",
15+
"https://portal.azure.cn/api/*", "https://portal.azure.cn/AzureHubs/api/*", "https://management.chinacloudapi.cn/*",
16+
"https://portal.microsoftazure.de/api/*", "https://portal.microsoftazure.de/AzureHubs/api/*", "https://management.microsoftazure.de/*",
17+
"https://portal.azure.us/api/*", "https://portal.azure.us/AzureHubs/api/*", "https://management.usgovcloudapi.net/*"
18+
]
19+
}, ['requestHeaders','blocking']);
20+
21+
chrome.runtime.onConnect.addListener( port => {
22+
var subResourceMap = {};
23+
port.onMessage.addListener( arg => {
24+
if( arg.name == "get-accesstoken" ){
25+
port.postMessage({
26+
authorizationToken: authorizationToken
27+
});
28+
return true;
29+
}
30+
});
31+
});
32+
33+
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
34+
chrome.declarativeContent.onPageChanged.addRules([{
35+
conditions: [
36+
new chrome.declarativeContent.PageStateMatcher({
37+
pageUrl: {
38+
schemes: ['https'],
39+
hostEquals: 'portal.azure.com'
40+
}
41+
}),
42+
new chrome.declarativeContent.PageStateMatcher({
43+
pageUrl: {
44+
schemes: ['https'],
45+
hostEquals: 'portal.azure.cn'
46+
}
47+
}),
48+
new chrome.declarativeContent.PageStateMatcher({
49+
pageUrl: {
50+
schemes: ['https'],
51+
hostEquals: 'portal.azure.us'
52+
}
53+
}),
54+
new chrome.declarativeContent.PageStateMatcher({
55+
pageUrl: {
56+
schemes: ['https'],
57+
hostEquals: 'portal.microsoftazure.de'
58+
}
59+
})
60+
],
61+
actions: [
62+
new chrome.declarativeContent.ShowPageAction()
63+
]
64+
}]);
65+
});

‎js/popup.js‎

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
'use strict';
2+
3+
class AzureKuduInstanceSelector extends React.Component {
4+
constructor(props) {
5+
super(props);
6+
this.state = {
7+
error: null,
8+
isLoaded: false,
9+
items: []
10+
};
11+
}
12+
13+
getCurrentUrl() {
14+
return new Promise(function (resolve) {
15+
chrome.tabs.getSelected(tab=>{
16+
resolve(tab.url);
17+
})
18+
});
19+
}
20+
21+
async componentDidMount() {
22+
// Get current tab url
23+
const currentTabUrl = await this.getCurrentUrl();
24+
25+
// Get subscriptionid, resource group, App Service
26+
// https://portal.azure.com/#@{AADTENANT}/resource/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/{RESOURCEGROUP}/providers/Microsoft.Web/sites/{SITE}/kudu
27+
const regexpWeb = /https:\/\/portal\.azure\.com\/[^\/]*\/resource\/subscriptions\/([^\/]*)\/resourceGroups\/([^\/]*)\/providers\/Microsoft\.Web\/sites\/([^\/]*)/i;
28+
// https://portal.azure.com/#blade/WebsitesExtension/FunctionsIFrameBlade/id/%2Fsubscriptions%2F00000000-0000-0000-0000-000000000000%2Fresourcegroups%2F{RESOURCEGROUOP}%2Fproviders%2FMicrosoft.Web%2Fsites%2F{SITE}
29+
const regexpBlade = /https:\/\/portal\.azure\.com\/#blade\/WebsitesExtension\/FunctionsIFrameBlade\/id\/\/subscriptions\/([^\/]*)\/resourcegroups\/([^\/]*)\/providers\/Microsoft\.Web\/sites\/([^\/]*)/i;
30+
var appservice = [];
31+
if (regexpWeb.test(currentTabUrl)) {
32+
appservice = currentTabUrl.match(regexpWeb);
33+
} else if (regexpBlade.test(decodeURIComponent(currentTabUrl))) {
34+
appservice = decodeURIComponent(currentTabUrl).match(regexpBlade);
35+
} else {
36+
var result = {
37+
dontwork: true,
38+
message: [
39+
"This page is not an App Serivce page.",
40+
"Please go to the App Service page and click on this again."
41+
]
42+
}
43+
this.setState({
44+
isLoaded: true,
45+
items: result
46+
})
47+
return;
48+
}
49+
50+
// Get AccessToken from Background
51+
port.postMessage({name: "get-accesstoken"});
52+
port.onMessage.addListener(response => {
53+
var authorizationToken = response.authorizationToken;
54+
// Get instances
55+
fetch("https://management.azure.com"
56+
+ "/subscriptions/" + appservice[1]
57+
+ "/resourceGroups/" + appservice[2]
58+
+ "/providers/Microsoft.Web/sites/" + appservice[3]
59+
+ "/instances?api-version=2019-08-01",
60+
{
61+
headers: {
62+
'Authorization': authorizationToken,
63+
'Content-Type': 'application/json'
64+
}
65+
}).then(res => res.json()).then(
66+
(result) => {
67+
this.setState({
68+
isLoaded: true,
69+
items: result
70+
});
71+
},
72+
(error) => {
73+
this.setState({
74+
isLoaded: true,
75+
error
76+
});
77+
}
78+
)
79+
});
80+
}
81+
82+
render() {
83+
const {error, isLoaded, items} = this.state;
84+
if (error) {
85+
console.error(error);
86+
return e('div', {className: "dontwork"}, 'Error: ' + error.message);
87+
} else if (!isLoaded) {
88+
return e('div', {className: "dontwork"}, 'Loading...');
89+
} else {
90+
if (items.dontwork === undefined) {
91+
const regexp = /https:\/\/[^\.]*\.scm\.azurewebsites\.net/i;
92+
var li = items.value.map(
93+
item=> (
94+
React.createElement('li', {key: item.name, className: "instance"},
95+
React.createElement('span', {className: "name"}, "instance: " + item.name.substr(0, 6)),
96+
React.createElement('a', {href: item.properties.consoleUrl.match(regexp) + "?instance=" + item.name, target: "_blank"}, "Kudu"),
97+
React.createElement('a', {href: item.properties.consoleUrl, target: "_blank"}, "console" )
98+
)
99+
)
100+
)
101+
return e('ul', {className: "instances"}, li);
102+
} else {
103+
var div = items.message.map(
104+
item=> (
105+
React.createElement('div', {className: "message"}, item)
106+
)
107+
)
108+
return e('div', {className: "dontwork"}, div);
109+
}
110+
}
111+
}
112+
}
113+
114+
const e = React.createElement;
115+
var port = chrome.runtime.connect({name: "my-background-port"});
116+
117+
const domContainer = document.querySelector('#popupContainer');
118+
ReactDOM.render(e(AzureKuduInstanceSelector), domContainer);

‎manifest.json‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"manifest_version": 2,
3+
"name": "Instance selector for Azure App Service Kudu",
4+
"version": "0.1.1",
5+
"description": "Launch Azure App Service Kudu or console with selecting an instance.",
6+
"permissions": [
7+
"https://portal.azure.com/api/*", "https://portal.azure.com/AzureHubs/api/*", "https://management.azure.com/*",
8+
"https://portal.azure.cn/api/*", "https://portal.azure.cn/AzureHubs/api/*", "https://management.chinacloudapi.cn/*",
9+
"https://portal.microsoftazure.de/api/*", "https://portal.microsoftazure.de/AzureHubs/api/*", "https://management.microsoftazure.de/*",
10+
"https://portal.azure.us/api/*", "https://portal.azure.us/AzureHubs/api/*", "https://management.usgovcloudapi.net/*",
11+
"webRequest", "webRequestBlocking", "declarativeContent"
12+
],
13+
"icons" : {
14+
"16": "icon/icon16.png",
15+
"48": "icon/icon48.png",
16+
"128": "icon/icon128.png"
17+
},
18+
"page_action": {
19+
"default_popup": "popup.html",
20+
"default_title": "Launch Azure Kudu",
21+
"default_icon": {
22+
"16": "icon/icon16.png",
23+
"48": "icon/icon48.png",
24+
"128": "icon/icon128.png"
25+
}
26+
},
27+
"background": {
28+
"scripts": ["js/background.js"],
29+
"persistent": true
30+
},
31+
"content_security_policy": "script-src 'self' 'unsafe-eval' https://unpkg.com; object-src 'self'",
32+
"homepage_url": "https://github.com/pnopjp/AzureKuduInstanceSelector"
33+
}

0 commit comments

Comments
 (0)