AzCon: improve input, usability, reliability (4 commits) (#4756)
* Azure: rewrite user input handler This commit replaces the AzureConnection's input handler with one that acts more like "getline()". Instead of the Read thread setting a state and WriteInput filling in the right member variable, the reader blocks on the user's input and receives it in an optional<string>. This moves the input number parsing and error case handling closer to the point where those inputs are used, as opposed to where they're collected. It also switches our input to be "line-based", which is a huge boon for typing tenant numbers >9. This fixes #3233. A simple line editor (supporting only backspace and CR) is included. It also enables echo on user input, and prints it in a nice pretty green color. It also enables input queueing: if the user types anything before the connection is established, it'll be sent once it is. Fixes #3233. * Azure: display the user's options and additional information in color This commit colorizes parts of the AzCon's strings that include "user options" -- things the user can type -- in yellow. This is to help with accessibility. The implementation here is based on a discussion with the team. Alternative options for coloration were investigated, such as: * Embedding escape sequences in the resource file. This would have been confusing for translators. The RESW file format doesn't support  escapes, so we would need some magic post-processing. * Embedding "markup" in the resource file (like #{93m}, ...) This still would have been annoying for translators. We settled on an implementation that takes resource names, colorizes them, and string-formats them into other resources. * Azure: follow the user's shell choice from the online portal Fixes #2266. * Azure: remove all credentials instead of just the first one
This commit is contained in:
parent
e58a648bd4
commit
44c4a8c925
|
@ -12,6 +12,7 @@
|
|||
#include <sstream>
|
||||
#include <stdlib.h>
|
||||
#include <LibraryResources.h>
|
||||
#include <unicode.hpp>
|
||||
|
||||
#include "AzureConnection.g.cpp"
|
||||
|
||||
|
@ -32,6 +33,38 @@ static constexpr int CurrentCredentialVersion = 1;
|
|||
static constexpr auto PasswordVaultResourceName = L"Terminal";
|
||||
static constexpr auto HttpUserAgent = L"Terminal/0.0";
|
||||
|
||||
#define FAILOUT_IF_OPTIONAL_EMPTY(optional) \
|
||||
do \
|
||||
{ \
|
||||
if (!((optional).has_value())) \
|
||||
{ \
|
||||
return E_FAIL; \
|
||||
} \
|
||||
} while (0, 0)
|
||||
|
||||
static constexpr int USER_INPUT_COLOR = 93; // yellow - the color of something the user can type
|
||||
static constexpr int USER_INFO_COLOR = 97; // white - the color of clarifying information
|
||||
|
||||
static inline std::wstring _colorize(const unsigned int colorCode, const std::wstring_view text)
|
||||
{
|
||||
return wil::str_printf<std::wstring>(L"\x1b[%um%.*s\x1b[m", colorCode, gsl::narrow_cast<size_t>(text.size()), text.data());
|
||||
}
|
||||
|
||||
// Takes N resource names, loads the first one as a format string, and then
|
||||
// loads all the remaining ones into the %s arguments in the first one after
|
||||
// colorizing them in the USER_INPUT_COLOR.
|
||||
// This is intended to be used to drop UserEntry resources into an existing string.
|
||||
template<typename... Args>
|
||||
static inline std::wstring _formatResWithColoredUserInputOptions(const std::wstring_view resourceKey, Args&&... args)
|
||||
{
|
||||
return wil::str_printf<std::wstring>(GetLibraryResourceString(resourceKey).data(), (_colorize(USER_INPUT_COLOR, GetLibraryResourceString(args)).data())...);
|
||||
}
|
||||
|
||||
static inline std::wstring _formatTenantLine(int tenantNumber, const std::wstring_view tenantName, const std::wstring_view tenantID)
|
||||
{
|
||||
return wil::str_printf<std::wstring>(RS_(L"AzureIthTenant").data(), _colorize(USER_INPUT_COLOR, std::to_wstring(tenantNumber)).data(), _colorize(USER_INFO_COLOR, tenantName).data(), tenantID.data());
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
// This function exists because the clientID only gets added by the release pipelines
|
||||
|
@ -45,8 +78,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
AzureConnection::AzureConnection(const uint32_t initialRows, const uint32_t initialCols) :
|
||||
_initialRows{ initialRows },
|
||||
_initialCols{ initialCols },
|
||||
_maxStored{},
|
||||
_maxSize{},
|
||||
_expiry{}
|
||||
{
|
||||
}
|
||||
|
@ -55,7 +86,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
// - helper that will write an unterminated string (generally, from a resource) to the output stream.
|
||||
// Arguments:
|
||||
// - str: the string to write.
|
||||
void AzureConnection::_WriteStringWithNewline(const winrt::hstring& str)
|
||||
void AzureConnection::_WriteStringWithNewline(const std::wstring_view str)
|
||||
{
|
||||
_TerminalOutputHandlers(str + L"\r\n");
|
||||
}
|
||||
|
@ -79,6 +110,35 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
_transitionToState(ConnectionState::Connecting);
|
||||
}
|
||||
|
||||
std::optional<std::wstring> AzureConnection::_ReadUserInput(InputMode mode)
|
||||
{
|
||||
std::unique_lock<std::mutex> inputLock{ _inputMutex };
|
||||
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
_currentInputMode = mode;
|
||||
|
||||
_TerminalOutputHandlers(L"\x1b[92m"); // Make prompted user input green
|
||||
|
||||
_inputEvent.wait(inputLock, [this, mode]() {
|
||||
return _currentInputMode != mode || _isStateAtOrBeyond(ConnectionState::Closing);
|
||||
});
|
||||
|
||||
_TerminalOutputHandlers(L"\x1b[m");
|
||||
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::wstring readInput{};
|
||||
_userInput.swap(readInput);
|
||||
return readInput;
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - ascribes to the ITerminalConnection interface
|
||||
// - handles the different possible inputs in the different states
|
||||
|
@ -92,108 +152,46 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
return;
|
||||
}
|
||||
|
||||
// Parse the input differently depending on which state we're in
|
||||
switch (_state)
|
||||
{
|
||||
// The user has stored connection settings, let them choose one of them, create a new one or remove all stored ones
|
||||
case AzureState::AccessStored:
|
||||
{
|
||||
const auto s = winrt::to_string(data);
|
||||
int storeNum = -1;
|
||||
try
|
||||
{
|
||||
storeNum = std::stoi(s);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::lock_guard<std::mutex> lg{ _commonMutex };
|
||||
if (data == RS_(L"AzureUserEntry_RemoveStored"))
|
||||
{
|
||||
_removeOrNew = true;
|
||||
}
|
||||
else if (data == RS_(L"AzureUserEntry_NewLogin"))
|
||||
{
|
||||
_removeOrNew = false;
|
||||
}
|
||||
|
||||
if (_removeOrNew.has_value())
|
||||
{
|
||||
_canProceed.notify_one();
|
||||
}
|
||||
else
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureInvalidAccessInput"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (storeNum >= _maxStored)
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureNumOutOfBoundsError"));
|
||||
return;
|
||||
}
|
||||
std::lock_guard<std::mutex> lg{ _commonMutex };
|
||||
_storedNumber = storeNum;
|
||||
_canProceed.notify_one();
|
||||
return;
|
||||
}
|
||||
// The user has multiple tenants in their Azure account, let them choose one of them
|
||||
case AzureState::TenantChoice:
|
||||
{
|
||||
int tenantNum = -1;
|
||||
try
|
||||
{
|
||||
tenantNum = std::stoi(winrt::to_string(data));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureNonNumberError"));
|
||||
return;
|
||||
}
|
||||
if (tenantNum >= _maxSize)
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureNumOutOfBoundsError"));
|
||||
return;
|
||||
}
|
||||
std::lock_guard<std::mutex> lg{ _commonMutex };
|
||||
_tenantNumber = tenantNum;
|
||||
_canProceed.notify_one();
|
||||
return;
|
||||
}
|
||||
// User has the option to save their connection settings for future logins
|
||||
case AzureState::StoreTokens:
|
||||
{
|
||||
std::lock_guard<std::mutex> lg{ _commonMutex };
|
||||
if (data == RS_(L"AzureUserEntry_Yes"))
|
||||
{
|
||||
_store = true;
|
||||
}
|
||||
else if (data == RS_(L"AzureUserEntry_No"))
|
||||
{
|
||||
_store = false;
|
||||
}
|
||||
|
||||
if (_store.has_value())
|
||||
{
|
||||
_canProceed.notify_one();
|
||||
}
|
||||
else
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureInvalidStoreInput"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
// We are connected, send user's input over the websocket
|
||||
case AzureState::TermConnected:
|
||||
if (_state == AzureState::TermConnected)
|
||||
{
|
||||
// If we're connected, we don't need to do any fun input shenanigans.
|
||||
websocket_outgoing_message msg;
|
||||
const auto str = winrt::to_string(data);
|
||||
msg.set_utf8_message(str);
|
||||
|
||||
_cloudShellSocket.send(msg).get();
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock{ _inputMutex };
|
||||
if (data.size() > 0 && (gsl::at(data, 0) == UNICODE_BACKSPACE || gsl::at(data, 0) == UNICODE_DEL)) // BS or DEL
|
||||
{
|
||||
if (_userInput.size() > 0)
|
||||
{
|
||||
_userInput.pop_back();
|
||||
_TerminalOutputHandlers(L"\x08 \x08"); // overstrike the character with a space
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_TerminalOutputHandlers(data); // echo back
|
||||
|
||||
switch (_currentInputMode)
|
||||
{
|
||||
case InputMode::Line:
|
||||
if (data.size() > 0 && gsl::at(data, 0) == UNICODE_CARRIAGERETURN)
|
||||
{
|
||||
_TerminalOutputHandlers(L"\r\n"); // we probably got a \r, so we need to advance to the next line.
|
||||
_currentInputMode = InputMode::None; // toggling the mode indicates completion
|
||||
_inputEvent.notify_one();
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
default:
|
||||
std::copy(data.cbegin(), data.cend(), std::back_inserter(_userInput));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method description:
|
||||
|
@ -231,7 +229,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
{
|
||||
if (_transitionToState(ConnectionState::Closing))
|
||||
{
|
||||
_canProceed.notify_all();
|
||||
_inputEvent.notify_all();
|
||||
|
||||
if (_state == AzureState::TermConnected)
|
||||
{
|
||||
// Close the websocket connection
|
||||
|
@ -404,7 +403,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
_state = AzureState::DeviceFlow;
|
||||
return S_FALSE;
|
||||
}
|
||||
_maxStored = 0;
|
||||
|
||||
int numTenants{ 0 };
|
||||
for (const auto& entry : credList)
|
||||
{
|
||||
auto nameJson = json::value::parse(entry.UserName().c_str());
|
||||
|
@ -422,12 +422,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
continue;
|
||||
}
|
||||
|
||||
winrt::hstring tenantLine{ wil::str_printf<std::wstring>(RS_(L"AzureIthTenant").c_str(), _maxStored, nameJson.at(L"displayName").as_string().c_str(), nameJson.at(L"tenantID").as_string().c_str()) };
|
||||
_WriteStringWithNewline(tenantLine);
|
||||
_maxStored++;
|
||||
_WriteStringWithNewline(_formatTenantLine(numTenants, nameJson.at(L"displayName").as_string(), nameJson.at(L"tenantID").as_string()));
|
||||
numTenants++;
|
||||
}
|
||||
|
||||
if (!_maxStored)
|
||||
if (!numTenants)
|
||||
{
|
||||
if (oldVersionEncountered)
|
||||
{
|
||||
|
@ -439,33 +438,53 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
}
|
||||
|
||||
_WriteStringWithNewline(RS_(L"AzureEnterTenant"));
|
||||
_WriteStringWithNewline(RS_(L"AzureNewLogin"));
|
||||
_WriteStringWithNewline(RS_(L"AzureRemoveStored"));
|
||||
_WriteStringWithNewline(_formatResWithColoredUserInputOptions(USES_RESOURCE(L"AzureNewLogin"), USES_RESOURCE(L"AzureUserEntry_NewLogin")));
|
||||
_WriteStringWithNewline(_formatResWithColoredUserInputOptions(USES_RESOURCE(L"AzureRemoveStored"), USES_RESOURCE(L"AzureUserEntry_RemoveStored")));
|
||||
|
||||
std::unique_lock<std::mutex> storedLock{ _commonMutex };
|
||||
_canProceed.wait(storedLock, [=]() {
|
||||
return (_storedNumber >= 0 && _storedNumber < _maxStored) || _removeOrNew.has_value() || _isStateAtOrBeyond(ConnectionState::Closing);
|
||||
});
|
||||
// User might have closed the tab while we waited for input
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
int selectedTenant{ -1 };
|
||||
do
|
||||
{
|
||||
return E_FAIL;
|
||||
}
|
||||
else if (_removeOrNew.has_value() && _removeOrNew.value())
|
||||
{
|
||||
// User wants to remove the stored settings
|
||||
_RemoveCredentials();
|
||||
_state = AzureState::DeviceFlow;
|
||||
return S_OK;
|
||||
}
|
||||
else if (_removeOrNew.has_value() && !_removeOrNew.value())
|
||||
{
|
||||
// User wants to login with a different account
|
||||
_state = AzureState::DeviceFlow;
|
||||
return S_OK;
|
||||
}
|
||||
auto maybeTenantSelection = _ReadUserInput(InputMode::Line);
|
||||
FAILOUT_IF_OPTIONAL_EMPTY(maybeTenantSelection);
|
||||
|
||||
const auto& tenantSelection = maybeTenantSelection.value();
|
||||
if (tenantSelection == RS_(L"AzureUserEntry_RemoveStored"))
|
||||
{
|
||||
// User wants to remove the stored settings
|
||||
_RemoveCredentials();
|
||||
_state = AzureState::DeviceFlow;
|
||||
return S_OK;
|
||||
}
|
||||
else if (tenantSelection == RS_(L"AzureUserEntry_NewLogin"))
|
||||
{
|
||||
// User wants to login with a different account
|
||||
_state = AzureState::DeviceFlow;
|
||||
return S_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
selectedTenant = std::stoi(tenantSelection);
|
||||
if (selectedTenant < 0 || selectedTenant >= numTenants)
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureNumOutOfBoundsError"));
|
||||
continue; // go 'round again
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// suppress exceptions in conversion
|
||||
}
|
||||
}
|
||||
|
||||
// if we got here, we didn't break out of the loop early and need to go 'round again
|
||||
_WriteStringWithNewline(_formatResWithColoredUserInputOptions(USES_RESOURCE(L"AzureInvalidAccessInput"), USES_RESOURCE(L"AzureUserEntry_NewLogin"), USES_RESOURCE(L"AzureUserEntry_RemoveStored")));
|
||||
} while (true);
|
||||
|
||||
// User wants to login with one of the saved connection settings
|
||||
auto desiredCredential = credList.GetAt(_storedNumber);
|
||||
auto desiredCredential = credList.GetAt(selectedTenant);
|
||||
desiredCredential.RetrievePassword();
|
||||
auto nameJson = json::value::parse(desiredCredential.UserName().c_str());
|
||||
auto passWordJson = json::value::parse(desiredCredential.Password().c_str());
|
||||
|
@ -567,27 +586,43 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
try
|
||||
{
|
||||
const auto tenantListAsArray = _tenantList.as_array();
|
||||
_maxSize = gsl::narrow<int>(tenantListAsArray.size());
|
||||
for (int i = 0; i < _maxSize; i++)
|
||||
auto numTenants = gsl::narrow<int>(tenantListAsArray.size());
|
||||
for (int i = 0; i < numTenants; i++)
|
||||
{
|
||||
const auto& tenant = tenantListAsArray.at(i);
|
||||
const auto [tenantId, tenantDisplayName] = _crackTenant(tenant);
|
||||
winrt::hstring tenantLine{ wil::str_printf<std::wstring>(RS_(L"AzureIthTenant").c_str(), i, tenantDisplayName.c_str(), tenantId.c_str()) };
|
||||
_WriteStringWithNewline(tenantLine);
|
||||
_WriteStringWithNewline(_formatTenantLine(i, tenantDisplayName, tenantId));
|
||||
}
|
||||
_WriteStringWithNewline(RS_(L"AzureEnterTenant"));
|
||||
// Use a lock to wait for the user to input a valid number
|
||||
std::unique_lock<std::mutex> tenantNumberLock{ _commonMutex };
|
||||
_canProceed.wait(tenantNumberLock, [=]() {
|
||||
return (_tenantNumber >= 0 && _tenantNumber < _maxSize) || _isStateAtOrBeyond(ConnectionState::Closing);
|
||||
});
|
||||
// User might have closed the tab while we waited for input
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
{
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
const auto& chosenTenant = tenantListAsArray.at(_tenantNumber);
|
||||
int selectedTenant{ -1 };
|
||||
do
|
||||
{
|
||||
auto maybeTenantSelection = _ReadUserInput(InputMode::Line);
|
||||
FAILOUT_IF_OPTIONAL_EMPTY(maybeTenantSelection);
|
||||
|
||||
const auto& tenantSelection = maybeTenantSelection.value();
|
||||
try
|
||||
{
|
||||
selectedTenant = std::stoi(tenantSelection);
|
||||
|
||||
if (selectedTenant < 0 || selectedTenant >= numTenants)
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureNumOutOfBoundsError"));
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// suppress exceptions in conversion
|
||||
}
|
||||
|
||||
// if we got here, we didn't break out of the loop early and need to go 'round again
|
||||
_WriteStringWithNewline(RS_(L"AzureNonNumberError"));
|
||||
} while (true);
|
||||
|
||||
const auto& chosenTenant = tenantListAsArray.at(selectedTenant);
|
||||
std::tie(_tenantID, _displayName) = _crackTenant(chosenTenant);
|
||||
|
||||
// We have to refresh now that we have the tenantID
|
||||
|
@ -609,24 +644,28 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
// - S_OK otherwise
|
||||
HRESULT AzureConnection::_StoreHelper()
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureStorePrompt"));
|
||||
_WriteStringWithNewline(_formatResWithColoredUserInputOptions(USES_RESOURCE(L"AzureStorePrompt"), USES_RESOURCE(L"AzureUserEntry_Yes"), USES_RESOURCE(L"AzureUserEntry_No")));
|
||||
// Wait for user input
|
||||
std::unique_lock<std::mutex> storeLock{ _commonMutex };
|
||||
_canProceed.wait(storeLock, [=]() {
|
||||
return _store.has_value() || _isStateAtOrBeyond(ConnectionState::Closing);
|
||||
});
|
||||
// User might have closed the tab while we waited for input
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
do
|
||||
{
|
||||
return E_FAIL;
|
||||
}
|
||||
auto maybeStoreCredentials = _ReadUserInput(InputMode::Line);
|
||||
FAILOUT_IF_OPTIONAL_EMPTY(maybeStoreCredentials);
|
||||
|
||||
if (_store.value())
|
||||
{
|
||||
// User has opted to store the connection settings
|
||||
_StoreCredential();
|
||||
_WriteStringWithNewline(RS_(L"AzureTokensStored"));
|
||||
}
|
||||
const auto& storeCredentials = maybeStoreCredentials.value();
|
||||
if (storeCredentials == RS_(L"AzureUserEntry_Yes"))
|
||||
{
|
||||
_StoreCredential();
|
||||
_WriteStringWithNewline(RS_(L"AzureTokensStored"));
|
||||
break;
|
||||
}
|
||||
else if (storeCredentials == RS_(L"AzureUserEntry_No"))
|
||||
{
|
||||
break; // we're done, but the user wants nothing.
|
||||
}
|
||||
|
||||
// if we got here, we didn't break out of the loop early and need to go 'round again
|
||||
_WriteStringWithNewline(_formatResWithColoredUserInputOptions(USES_RESOURCE(L"AzureInvalidStoreInput"), USES_RESOURCE(L"AzureUserEntry_Yes"), USES_RESOURCE(L"AzureUserEntry_No")));
|
||||
} while (true);
|
||||
|
||||
_state = AzureState::TermConnecting;
|
||||
return S_OK;
|
||||
|
@ -654,11 +693,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
_WriteStringWithNewline(RS_(L"AzureSuccess"));
|
||||
|
||||
// Request for a terminal for said cloud shell
|
||||
// We only support bash for now, so don't bother with the user's preferred shell
|
||||
// fyi: we can't call powershell yet because it sends VT sequences we don't support yet
|
||||
// TODO: GitHub #1883
|
||||
//const auto shellType = settingsResponse.at(L"properties").at(L"preferredShellType").as_string();
|
||||
const auto shellType = L"bash";
|
||||
const auto shellType = settingsResponse.at(L"properties").at(L"preferredShellType").as_string();
|
||||
_WriteStringWithNewline(RS_(L"AzureRequestingTerminal"));
|
||||
const auto socketUri = _GetTerminal(shellType);
|
||||
_TerminalOutputHandlers(L"\r\n");
|
||||
|
@ -668,6 +703,14 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
connReqTask.wait();
|
||||
|
||||
_state = AzureState::TermConnected;
|
||||
|
||||
std::wstring queuedUserInput{};
|
||||
std::swap(_userInput, queuedUserInput);
|
||||
if (queuedUserInput.size() > 0)
|
||||
{
|
||||
WriteInput(static_cast<winrt::hstring>(queuedUserInput)); // send the user's queued up input back through
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
@ -833,9 +876,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
http_request shellRequest(L"PUT");
|
||||
shellRequest.set_request_uri(L"providers/Microsoft.Portal/consoles/default?api-version=2018-10-01");
|
||||
_HeaderHelper(shellRequest);
|
||||
const auto innerBody = json::value::parse(U("{ \"osType\" : \"linux\" }"));
|
||||
json::value body;
|
||||
body[U("properties")] = innerBody;
|
||||
// { "properties": { "osType": "linux" } }
|
||||
auto body = json::value::object({ { U("properties"), json::value::object({ { U("osType"), json::value::string(U("linux")) } }) } });
|
||||
shellRequest.set_body(body);
|
||||
|
||||
// Send the request and get the response as a json value
|
||||
|
@ -914,17 +956,15 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
_WriteStringWithNewline(RS_(L"AzureNoTokens"));
|
||||
return;
|
||||
}
|
||||
while (credList.Size() > 0)
|
||||
|
||||
for (const auto& cred : credList)
|
||||
{
|
||||
try
|
||||
{
|
||||
vault.Remove(credList.GetAt(0));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureTokensRemoved"));
|
||||
return;
|
||||
vault.Remove(cred);
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
_WriteStringWithNewline(RS_(L"AzureTokensRemoved"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,12 +31,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
private:
|
||||
uint32_t _initialRows{};
|
||||
uint32_t _initialCols{};
|
||||
int _storedNumber{ -1 };
|
||||
int _maxStored;
|
||||
int _tenantNumber{ -1 };
|
||||
int _maxSize;
|
||||
std::condition_variable _canProceed;
|
||||
std::mutex _commonMutex;
|
||||
|
||||
enum class AzureState
|
||||
{
|
||||
|
@ -51,9 +45,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
|
||||
AzureState _state{ AzureState::AccessStored };
|
||||
|
||||
std::optional<bool> _store;
|
||||
std::optional<bool> _removeOrNew;
|
||||
|
||||
wil::unique_handle _hOutputThread;
|
||||
|
||||
static DWORD WINAPI StaticOutputThreadProc(LPVOID lpParameter);
|
||||
|
@ -67,17 +58,17 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
const utility::string_t _loginUri{ U("https://login.microsoftonline.com/") };
|
||||
const utility::string_t _resourceUri{ U("https://management.azure.com/") };
|
||||
const utility::string_t _wantedResource{ U("https://management.core.windows.net/") };
|
||||
int _expireLimit{ 2700 };
|
||||
const int _expireLimit{ 2700 };
|
||||
web::json::value _tenantList;
|
||||
utility::string_t _displayName;
|
||||
utility::string_t _tenantID;
|
||||
utility::string_t _accessToken;
|
||||
utility::string_t _refreshToken;
|
||||
int _expiry;
|
||||
int _expiry{ 0 };
|
||||
utility::string_t _cloudShellUri;
|
||||
utility::string_t _terminalID;
|
||||
|
||||
void _WriteStringWithNewline(const winrt::hstring& str);
|
||||
void _WriteStringWithNewline(const std::wstring_view str);
|
||||
web::json::value _RequestHelper(web::http::client::http_client theClient, web::http::http_request theRequest);
|
||||
web::json::value _GetDeviceCode();
|
||||
web::json::value _WaitForUser(utility::string_t deviceCode, int pollInterval, int expiresIn);
|
||||
|
@ -90,6 +81,18 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
void _StoreCredential();
|
||||
void _RemoveCredentials();
|
||||
|
||||
enum class InputMode
|
||||
{
|
||||
None = 0,
|
||||
Line
|
||||
};
|
||||
InputMode _currentInputMode{ InputMode::None };
|
||||
std::wstring _userInput;
|
||||
std::condition_variable _inputEvent;
|
||||
std::mutex _inputMutex;
|
||||
|
||||
std::optional<std::wstring> _ReadUserInput(InputMode mode);
|
||||
|
||||
web::websockets::client::websocket_client _cloudShellSocket;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -123,14 +123,14 @@
|
|||
<data name="AzureEnterTenant" xml:space="preserve">
|
||||
<value>Please enter the desired tenant number.</value>
|
||||
</data>
|
||||
<data name="AzureNewLogin" xml:space="preserve">
|
||||
<value>Enter "n" to login with a different account.</value>
|
||||
<data name="AzureNewLogin" xml:space="default">
|
||||
<value>Enter %s to login with a different account</value>
|
||||
</data>
|
||||
<data name="AzureRemoveStored" xml:space="preserve">
|
||||
<value>Enter "r" to remove the above saved connection settings.</value>
|
||||
<data name="AzureRemoveStored" xml:space="default">
|
||||
<value>Enter %s to remove the above saved connection settings.</value>
|
||||
</data>
|
||||
<data name="AzureInvalidAccessInput" xml:space="preserve">
|
||||
<value>Please enter a valid number to access the stored connection settings, n to make a new one, or r to remove the stored ones.</value>
|
||||
<data name="AzureInvalidAccessInput" xml:space="default">
|
||||
<value>Please enter a valid number to access the stored connection settings, %s to make a new one, or %s to remove the stored ones.</value>
|
||||
</data>
|
||||
<data name="AzureNonNumberError" xml:space="preserve">
|
||||
<value>Please enter a number.</value>
|
||||
|
@ -144,11 +144,11 @@
|
|||
<data name="AzureNoCloudAccount" xml:space="preserve">
|
||||
<value>You have not set up your cloud shell account yet. Please go to https://shell.azure.com to set it up.</value>
|
||||
</data>
|
||||
<data name="AzureStorePrompt" xml:space="preserve">
|
||||
<value>Do you want to save these connection settings for future logins? [y/n]</value>
|
||||
<data name="AzureStorePrompt" xml:space="default">
|
||||
<value>Do you want to save these connection settings for future logins? [%s/%s]</value>
|
||||
</data>
|
||||
<data name="AzureInvalidStoreInput" xml:space="preserve">
|
||||
<value>Please enter y or n</value>
|
||||
<data name="AzureInvalidStoreInput" xml:space="default">
|
||||
<value>Please enter %s or %s</value>
|
||||
</data>
|
||||
<data name="AzureRequestingCloud" xml:space="preserve">
|
||||
<value>Requesting a cloud shell instance...</value>
|
||||
|
@ -183,8 +183,8 @@
|
|||
<data name="AzureUnknownTenantName" xml:space="preserve">
|
||||
<value><unknown tenant name></value>
|
||||
</data>
|
||||
<data name="AzureIthTenant" xml:space="preserve">
|
||||
<value>Tenant %d: %s (%s)</value>
|
||||
<data name="AzureIthTenant" xml:space="default">
|
||||
<value>Tenant %s: %s (%s)</value>
|
||||
</data>
|
||||
<data name="AzureSuccessfullyAuthenticated" xml:space="preserve">
|
||||
<value>Authenticated.</value>
|
||||
|
@ -217,4 +217,4 @@ If this resource spans multiple lines, it will not be displayed properly. Yeah.<
|
|||
<data name="TelnetInternetOrServerIssue" xml:space="preserve">
|
||||
<value>Could not connect to telnet server.</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
|
|
Loading…
Reference in a new issue