Add settings screen for editing GPG public keys
The UI is almost identical to the UI for editing SSH keys (although
implemented with UiBinder).
Change-Id: Ic6cf4dc9d7f71b00efea00a86498660d113ebb2b
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
index c80d867..ff2121d 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
@@ -26,6 +26,7 @@
public static final String SETTINGS = "/settings/";
public static final String SETTINGS_PREFERENCES = "/settings/preferences";
public static final String SETTINGS_SSHKEYS = "/settings/ssh-keys";
+ public static final String SETTINGS_GPGKEYS = "/settings/gpg-keys";
public static final String SETTINGS_HTTP_PASSWORD = "/settings/http-password";
public static final String SETTINGS_WEBIDENT = "/settings/web-identities";
public static final String SETTINGS_MYGROUPS = "/settings/group-memberships";
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/rpc/Natives.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/rpc/Natives.java
index 7087888..dcd96da 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/rpc/Natives.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/rpc/Natives.java
@@ -91,6 +91,20 @@
return arr;
}
+ public static JsArrayString arrayOf(Iterable<String> elements) {
+ JsArrayString arr = JavaScriptObject.createArray().cast();
+ for (String elem : elements) {
+ arr.push(elem);
+ }
+ return arr;
+ }
+
+ public static JsArrayString arrayOf(String element) {
+ JsArrayString arr = JavaScriptObject.createArray().cast();
+ arr.push(element);
+ return arr;
+ }
+
private Natives() {
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
index e7381f8..946888d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
@@ -29,6 +29,7 @@
import static com.google.gerrit.common.PageLinks.SETTINGS_AGREEMENTS;
import static com.google.gerrit.common.PageLinks.SETTINGS_CONTACT;
import static com.google.gerrit.common.PageLinks.SETTINGS_EXTENSION;
+import static com.google.gerrit.common.PageLinks.SETTINGS_GPGKEYS;
import static com.google.gerrit.common.PageLinks.SETTINGS_HTTP_PASSWORD;
import static com.google.gerrit.common.PageLinks.SETTINGS_MYGROUPS;
import static com.google.gerrit.common.PageLinks.SETTINGS_NEW_AGREEMENT;
@@ -40,6 +41,7 @@
import com.google.gerrit.client.account.MyAgreementsScreen;
import com.google.gerrit.client.account.MyContactInformationScreen;
+import com.google.gerrit.client.account.MyGpgKeysScreen;
import com.google.gerrit.client.account.MyGroupsScreen;
import com.google.gerrit.client.account.MyIdentitiesScreen;
import com.google.gerrit.client.account.MyPasswordScreen;
@@ -536,6 +538,10 @@
return new MySshKeysScreen();
}
+ if (matchExact(SETTINGS_GPGKEYS, token)) {
+ return new MyGpgKeysScreen();
+ }
+
if (matchExact(SETTINGS_WEBIDENT, token)) {
return new MyIdentitiesScreen();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
index c6eb2de..c2a7637 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
@@ -184,6 +184,7 @@
String sshHostKeyPanelKnownHostEntry();
String sshKeyPanelEncodedKey();
String sshKeyPanelInvalid();
+ String sshKeyTable();
String stringListPanelButtons();
String topMostCell();
String topmenu();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
index a796f94..367644f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
@@ -17,10 +17,13 @@
import com.google.gerrit.client.VoidResult;
import com.google.gerrit.client.info.AccountInfo;
import com.google.gerrit.client.rpc.CallbackGroup;
+import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.NativeString;
+import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.rpc.RestApi;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.user.client.rpc.AsyncCallback;
import java.util.Set;
@@ -147,4 +150,42 @@
protected UsernameInput() {
}
}
+
+ public static void addGpgKey(String account, String armored,
+ AsyncCallback<NativeMap<GpgKeyInfo>> cb) {
+ new RestApi("/accounts/")
+ .id(account)
+ .view("gpgkeys")
+ .post(GpgKeysInput.add(armored), cb);
+ }
+
+ public static void removeGpgKeys(String account,
+ Iterable<String> fingerprints, AsyncCallback<NativeMap<GpgKeyInfo>> cb) {
+ new RestApi("/accounts/")
+ .id(account)
+ .view("gpgkeys")
+ .post(GpgKeysInput.remove(fingerprints), cb);
+ }
+
+ private static class GpgKeysInput extends JavaScriptObject {
+ static GpgKeysInput add(String key) {
+ return createAdd(Natives.arrayOf(key));
+ }
+
+ static GpgKeysInput remove(Iterable<String> fingerprints) {
+ return createRemove(Natives.arrayOf(fingerprints));
+ }
+
+ private static native GpgKeysInput createAdd(JsArrayString keys) /*-{
+ return {'add': keys};
+ }-*/;
+
+ private static native GpgKeysInput createRemove(
+ JsArrayString fingerprints) /*-{
+ return {'remove': fingerprints};
+ }-*/;
+
+ protected GpgKeysInput() {
+ }
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
index 4c3cc29..6234f02 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
@@ -50,14 +50,15 @@
String myMenuReset();
String tabAccountSummary();
- String tabPreferences();
- String tabWatchedProjects();
- String tabContactInformation();
- String tabSshKeys();
- String tabHttpAccess();
- String tabWebIdentities();
- String tabMyGroups();
String tabAgreements();
+ String tabContactInformation();
+ String tabGpgKeys();
+ String tabHttpAccess();
+ String tabMyGroups();
+ String tabPreferences();
+ String tabSshKeys();
+ String tabWatchedProjects();
+ String tabWebIdentities();
String buttonShowAddSshKey();
String buttonCloseAddSshKey();
@@ -94,6 +95,10 @@
String sshHostKeyFingerprint();
String sshHostKeyKnownHostEntry();
+ String gpgKeyId();
+ String gpgKeyFingerprint();
+ String gpgKeyUserIds();
+
String webIdStatus();
String webIdEmail();
String webIdIdentity();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
index 36cb765..eee7a60 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
@@ -36,14 +36,15 @@
changeScreenNewUi = New Screen
tabAccountSummary = Profile
-tabPreferences = Preferences
-tabWatchedProjects = Watched Projects
-tabContactInformation = Contact Information
-tabSshKeys = SSH Public Keys
-tabHttpAccess = HTTP Password
-tabWebIdentities = Identities
-tabMyGroups = Groups
tabAgreements = Agreements
+tabContactInformation = Contact Information
+tabGpgKeys = GPG Public Keys
+tabHttpAccess = HTTP Password
+tabMyGroups = Groups
+tabPreferences = Preferences
+tabSshKeys = SSH Public Keys
+tabWatchedProjects = Watched Projects
+tabWebIdentities = Identities
buttonShowAddSshKey = Add Key ...
buttonCloseAddSshKey = Close
@@ -73,6 +74,10 @@
sshHostKeyFingerprint = Fingerprint:
sshHostKeyKnownHostEntry = Entry for <code>~/.ssh/known_hosts</code>:
+gpgKeyId = ID
+gpgKeyFingerprint = Fingerprint
+gpgKeyUserIds = User IDs
+
webIdStatus = Status
webIdEmail = Email Address
webIdIdentity = Identity
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/GpgKeyInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/GpgKeyInfo.java
new file mode 100644
index 0000000..d1bb426
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/GpgKeyInfo.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.account;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+public class GpgKeyInfo extends JavaScriptObject {
+ public final native String id() /*-{ return this.id; }-*/;
+ public final native String fingerprint() /*-{ return this.fingerprint; }-*/;
+ public final native JsArrayString userIds() /*-{ return this.user_ids; }-*/;
+ public final native String key() /*-{ return this.key; }-*/;
+
+ protected GpgKeyInfo() {
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGpgKeysScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGpgKeysScreen.java
new file mode 100644
index 0000000..6d88e38
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGpgKeysScreen.java
@@ -0,0 +1,283 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.account;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.client.ui.FancyFlexTable;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.http.client.Response;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.rpc.StatusCodeException;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.InlineLabel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwtexpui.clippy.client.CopyableLabel;
+import com.google.gwtexpui.globalkey.client.NpTextArea;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class MyGpgKeysScreen extends SettingsScreen {
+ interface Binder extends UiBinder<HTMLPanel, MyGpgKeysScreen> {}
+ private static final Binder uiBinder = GWT.create(Binder.class);
+
+ @UiField(provided = true) GpgKeyTable keys;
+ @UiField Button deleteKey;
+ @UiField Button addKey;
+
+ @UiField VerticalPanel addKeyBlock;
+ @UiField NpTextArea keyText;
+
+ @UiField VerticalPanel errorPanel;
+ @UiField Label errorText;
+
+ @UiField Button clearButton;
+ @UiField Button addButton;
+ @UiField Button closeButton;
+
+ @Override
+ protected void onInitUI() {
+ super.onInitUI();
+ keys = new GpgKeyTable();
+ add(uiBinder.createAndBindUi(this));
+ keys.updateDeleteButton();
+ }
+
+ @Override
+ protected void onLoad() {
+ super.onLoad();
+ refreshKeys();
+ }
+
+ @UiHandler("deleteKey")
+ void onDeleteKey(@SuppressWarnings("unused") ClickEvent e) {
+ keys.deleteChecked();
+ }
+
+ @UiHandler("addKey")
+ void onAddKey(@SuppressWarnings("unused") ClickEvent e) {
+ showAddKeyBlock(true);
+ }
+
+ @UiHandler("clearButton")
+ void onClearButton(@SuppressWarnings("unused") ClickEvent e) {
+ keyText.setText("");
+ keyText.setFocus(true);
+ errorPanel.setVisible(false);
+ }
+
+ @UiHandler("closeButton")
+ void onCloseButton(@SuppressWarnings("unused") ClickEvent e) {
+ showAddKeyBlock(false);
+ }
+
+ @UiHandler("addButton")
+ void onAddButton(@SuppressWarnings("unused") ClickEvent e) {
+ doAddKey();
+ }
+
+ private void refreshKeys() {
+ AccountApi.self().view("gpgkeys").get(NativeMap.copyKeysIntoChildren("id",
+ new GerritCallback<NativeMap<GpgKeyInfo>>() {
+ @Override
+ public void onSuccess(NativeMap<GpgKeyInfo> result) {
+ List<GpgKeyInfo> list = Natives.asList(result.values());
+ // TODO(dborowitz): Sort on something more meaningful, like
+ // created date?
+ Collections.sort(list, new Comparator<GpgKeyInfo>() {
+ @Override
+ public int compare(GpgKeyInfo a, GpgKeyInfo b) {
+ return a.id().compareTo(b.id());
+ }
+ });
+ keys.clear();
+ keyText.setText("");
+ errorPanel.setVisible(false);
+ addButton.setEnabled(true);
+ if (!list.isEmpty()) {
+ keys.setVisible(true);
+ for (GpgKeyInfo k : list) {
+ keys.addOneKey(k);
+ }
+ showKeyTable(true);
+ showAddKeyBlock(false);
+ } else {
+ keys.setVisible(false);
+ showAddKeyBlock(true);
+ showKeyTable(false);
+ }
+
+ display();
+ }
+ }));
+ }
+
+ private void showAddKeyBlock(boolean show) {
+ addKey.setVisible(!show);
+ addKeyBlock.setVisible(show);
+ }
+
+ private void showKeyTable(boolean show) {
+ keys.setVisible(show);
+ deleteKey.setVisible(show);
+ addKey.setVisible(show);
+ }
+
+ private void doAddKey() {
+ if (keyText.getText().isEmpty()) {
+ return;
+ }
+ addButton.setEnabled(false);
+ keyText.setEnabled(false);
+ AccountApi.addGpgKey("self", keyText.getText(),
+ new AsyncCallback<NativeMap<GpgKeyInfo>>() {
+ @Override
+ public void onSuccess(NativeMap<GpgKeyInfo> result) {
+ keyText.setEnabled(true);
+ refreshKeys();
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ keyText.setEnabled(true);
+ addButton.setEnabled(true);
+ if (caught instanceof StatusCodeException) {
+ StatusCodeException sce = (StatusCodeException) caught;
+ if (sce.getStatusCode() == Response.SC_CONFLICT
+ || sce.getStatusCode() == Response.SC_BAD_REQUEST) {
+ errorText.setText(sce.getEncodedResponse());
+ } else {
+ errorText.setText(sce.getMessage());
+ }
+ } else {
+ errorText.setText(
+ "Unexpected error saving key: " + caught.getMessage());
+ }
+ errorPanel.setVisible(true);
+ }
+ });
+ }
+
+ private class GpgKeyTable extends FancyFlexTable<GpgKeyInfo> {
+ private final ValueChangeHandler<Boolean> updateDeleteHandler;
+
+ GpgKeyTable() {
+ table.setWidth("");
+ table.setText(0, 1, Util.C.gpgKeyId());
+ table.setText(0, 2, Util.C.gpgKeyFingerprint());
+ table.setText(0, 3, Util.C.gpgKeyUserIds());
+
+ FlexCellFormatter fmt = table.getFlexCellFormatter();
+ fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().iconHeader());
+ fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().dataHeader());
+ fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().dataHeader());
+ fmt.addStyleName(0, 3, Gerrit.RESOURCES.css().dataHeader());
+
+ updateDeleteHandler = new ValueChangeHandler<Boolean>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> event) {
+ updateDeleteButton();
+ }
+ };
+ }
+
+ private void addOneKey(GpgKeyInfo k) {
+ int row = table.getRowCount();
+ table.insertRow(row);
+ applyDataRowStyle(row);
+
+ CheckBox sel = new CheckBox();
+ sel.addValueChangeHandler(updateDeleteHandler);
+ table.setWidget(row, 0, sel);
+ table.setWidget(row, 1, new CopyableLabel(k.id()));
+ table.setText(row, 2, k.fingerprint());
+
+ VerticalPanel userIds = new VerticalPanel();
+ for (int i = 0; i < k.userIds().length(); i++) {
+ userIds.add(new InlineLabel(k.userIds().get(i)));
+ }
+ table.setWidget(row, 3, userIds);
+
+ FlexCellFormatter fmt = table.getFlexCellFormatter();
+ fmt.addStyleName(row, 0, Gerrit.RESOURCES.css().iconCell());
+ fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().dataCell());
+ fmt.addStyleName(row, 2, Gerrit.RESOURCES.css().dataCell());
+ fmt.addStyleName(row, 3, Gerrit.RESOURCES.css().dataCell());
+
+ setRowItem(row, k);
+ }
+
+ private void updateDeleteButton() {
+ for (int row = 1; row < table.getRowCount(); row++) {
+ if (isChecked(row)) {
+ deleteKey.setEnabled(true);
+ return;
+ }
+ }
+ deleteKey.setEnabled(false);
+ }
+
+ private void deleteChecked() {
+ deleteKey.setEnabled(false);
+ List<String> toDelete = new ArrayList<>(table.getRowCount());
+ for (int row = 1; row < table.getRowCount(); row++) {
+ if (isChecked(row)) {
+ toDelete.add(getRowItem(row).fingerprint());
+ }
+ }
+ AccountApi.removeGpgKeys("self", toDelete,
+ new GerritCallback<NativeMap<GpgKeyInfo>>() {
+ @Override
+ public void onSuccess(NativeMap<GpgKeyInfo> result) {
+ refreshKeys();
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ deleteKey.setEnabled(true);
+ super.onFailure(caught);
+ }
+ });
+ }
+
+ private boolean isChecked(int row) {
+ return ((CheckBox) table.getWidget(row, 0)).getValue();
+ }
+
+ private void clear() {
+ while (table.getRowCount() > 1) {
+ table.removeRow(1);
+ }
+ for (int i = table.getRowCount() - 1; i >= 1; i++) {
+ table.removeRow(i);
+ }
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGpgKeysScreen.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGpgKeysScreen.ui.xml
new file mode 100644
index 0000000..dc73736
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGpgKeysScreen.ui.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2015 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:expui='urn:import:com.google.gwtexpui.globalkey.client'>
+ <ui:with field='res' type='com.google.gerrit.client.GerritResources'/>
+
+ <ui:style gss='false'>
+ .errorHeader {
+ font-weight: bold;
+ }
+ .errorText {
+ white-space: pre-wrap;
+ padding-bottom: 6px;
+ }
+ </ui:style>
+
+ <g:HTMLPanel>
+ <g:Widget ui:field='keys' addStyleNames='{res.css.sshKeyTable}'/>
+ <g:FlowPanel>
+ <g:Button ui:field='deleteKey'>
+ <div><ui:msg>Delete</ui:msg></div>
+ </g:Button>
+ <g:Button ui:field='addKey'>
+ <div><ui:msg>Add Key ...</ui:msg></div>
+ </g:Button>
+ </g:FlowPanel>
+ <g:VerticalPanel ui:field='addKeyBlock'
+ styleName='{res.css.addSshKeyPanel}'
+ visible='false'>
+ <g:Label>Add GPG Public Key</g:Label>
+ <g:DisclosurePanel>
+ <g:header>How to generate a GPG key</g:header>
+ <g:HTMLPanel>
+ <ol>
+ <li>
+ From the Terminal or Git Bash, run <em>gpg --gen-key</em> and
+ follow the prompts to create the key.
+ </li>
+ <li>
+ Use the default kind. Use the default (or higher) keysize. Choose
+ any value for your expiration.
+ </li>
+ <li>
+ The user ID should contain one of your registered email addresses.
+ </li>
+ <li>Setting a passphrase is strongly recommended.</li>
+ <li>Note the ID of your new key.</li>
+ <li>
+ To export your key, run the following and paste the full output
+ into the text box:
+ <br/>
+ <code>gpg --export -a <key ID></code>
+ </li>
+ </ol>
+ </g:HTMLPanel>
+ </g:DisclosurePanel>
+ <expui:NpTextArea
+ visibleLines='12'
+ characterWidth='80'
+ spellCheck='false'
+ ui:field='keyText'/>
+ <g:VerticalPanel ui:field='errorPanel' visible='false'>
+ <g:Label styleName='{style.errorHeader}'>Error adding GPG key:</g:Label>
+ <g:Label styleName='{style.errorText}' ui:field='errorText'/>
+ </g:VerticalPanel>
+ <g:FlowPanel>
+ <g:Button ui:field='clearButton'>
+ <div><ui:msg>Clear</ui:msg></div>
+ </g:Button>
+ <g:Button ui:field='addButton'>
+ <div><ui:msg>Add</ui:msg></div>
+ </g:Button>
+ <g:Button ui:field='closeButton'>
+ <div><ui:msg>Close</ui:msg></div>
+ </g:Button>
+ </g:FlowPanel>
+ </g:VerticalPanel>
+ </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
index ac140ff..2f3a819 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
@@ -45,6 +45,9 @@
if (Gerrit.info().auth().isHttpPasswordSettingsEnabled()) {
linkByGerrit(Util.C.tabHttpAccess(), PageLinks.SETTINGS_HTTP_PASSWORD);
}
+ if (Gerrit.info().receive().enableSignedPush()) {
+ linkByGerrit(Util.C.tabGpgKeys(), PageLinks.SETTINGS_GPGKEYS);
+ }
linkByGerrit(Util.C.tabWebIdentities(), PageLinks.SETTINGS_WEBIDENT);
linkByGerrit(Util.C.tabMyGroups(), PageLinks.SETTINGS_MYGROUPS);
if (Gerrit.info().auth().useContributorAgreements()) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
index c26c437..0914efd 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
@@ -1074,6 +1074,10 @@
width: 100%;
}
+.sshKeyTable td.dataCell, .sshKeyTable td.iconCell {
+ vertical-align: top;
+}
+
.createProjectPanel {
margin-bottom: 10px;
background-color: trimColor;