提交 cdda71ae 编写于 作者: H huybrechts

[HUDSON-4151 FIXED] user-private views

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@20414 71c3de6d-444a-0410-be80-ed276b4c234a
上级 d6198529
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -24,6 +24,7 @@
package hudson.model;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
......@@ -46,6 +47,11 @@ public class AllView extends View {
super(name);
}
public AllView(String name, ViewGroup owner) {
this(name);
this.owner = owner;
}
@Override
public String getDescription() {
return Hudson.getInstance().getDescription();
......@@ -100,7 +106,7 @@ public class AllView extends View {
public static final class DescriptorImpl extends ViewDescriptor {
@Override
public boolean isInstantiable() {
for (View v : Hudson.getInstance().getViews())
for (View v : Stapler.getCurrentRequest().findAncestorObject(ViewGroup.class).getViews())
if(v instanceof AllView)
return false;
return true;
......
......@@ -98,6 +98,11 @@ public class ListView extends View {
initColumns();
}
public ListView(String name, ViewGroup owner) {
this(name);
this.owner = owner;
}
private Object readResolve() {
if(includeRegex!=null)
includePattern = Pattern.compile(includeRegex);
......
......@@ -49,6 +49,11 @@ public class MyView extends View {
super(name);
}
public MyView(String name, ViewGroup owner) {
this(name);
this.owner = owner;
}
@Override
public boolean contains(TopLevelItem item) {
return item.hasPermission(Job.CONFIGURE);
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Tom Huybrechts
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.model;
import hudson.Extension;
import hudson.Util;
import hudson.model.Descriptor.FormException;
import hudson.security.ACL;
import hudson.security.Permission;
import hudson.util.FormValidation;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import org.acegisecurity.AccessDeniedException;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
/**
* A UserProperty that remembers user-private views.
*
* @author Tom Huybrechts
*/
public class MyViewsProperty extends UserProperty implements ViewGroup, Action {
private static final Logger log = Logger.getLogger(MyViewsProperty.class.getName());
private String primaryViewName;
private CopyOnWriteArrayList<View> views = new CopyOnWriteArrayList<View>();
@DataBoundConstructor
public MyViewsProperty(String primaryViewName) {
this.primaryViewName = primaryViewName;
}
private MyViewsProperty() {
views.add(new AllView(hudson.model.Messages.Hudson_ViewName(), this));
primaryViewName = views.get(0).getViewName();
}
public String getPrimaryViewName() {
return primaryViewName;
}
public void setPrimaryViewName(String primaryViewName) {
this.primaryViewName = primaryViewName;
}
public User getUser() {
return user;
}
///// ViewGroup methods /////
public String getUrl() {
return user.getUrl() + "/my-views/";
}
public void save() throws IOException {
user.save();
}
public Collection<View> getViews() {
List<View> copy = new ArrayList<View>(views);
Collections.sort(copy, View.SORTER);
return copy;
}
public View getView(String name) {
for (View v : views) {
if (v.getViewName().equals(name)) {
return v;
}
}
return null;
}
public void deleteView(View view) throws IOException {
if (views.size() <= 1) {
throw new IllegalStateException();
}
views.remove(view);
if (view.getViewName().equals(primaryViewName)) {
primaryViewName = views.get(0).getViewName();
}
save();
}
public void onViewRenamed(View view, String oldName, String newName) {
if (primaryViewName.equals(oldName)) {
primaryViewName = newName;
try {
save();
} catch (IOException ex) {
log.log(Level.SEVERE, "error while saving user " + user.getId(), ex);
}
}
}
public void addView(View view) throws IOException {
views.add(view);
save();
}
public View getPrimaryView() {
if (primaryViewName != null) {
return getView(primaryViewName);
} else {
return getViews().iterator().next();
}
}
public HttpResponse doIndex() {
return new HttpRedirect("view/" + getPrimaryView().getViewName());
}
public synchronized void doCreateView(StaplerRequest req, StaplerResponse rsp)
throws IOException, ServletException, ParseException, FormException {
checkPermission(View.CREATE);
addView(View.create(req, rsp, this));
}
/**
* Checks if a private view with the given name exists.
* An error is returned if exists==true but the view does not exist.
* An error is also returned if exists==false but the view does exist.
**/
public FormValidation doViewExistsCheck(@QueryParameter String value, @QueryParameter boolean exists) {
checkPermission(View.CREATE);
String view = Util.fixEmpty(value);
if (view == null) return FormValidation.ok();
if (exists) {
return (getView(view)!=null) ?
FormValidation.ok() :
FormValidation.error(String.format("A view with name \"%s\" does not exist", view));
} else {
return (getView(view)==null) ?
FormValidation.ok() :
FormValidation.error(String.format("A view with name \"%s\" already exists", view));
}
}
public ACL getACL() {
return user.getACL();
}
public void checkPermission(Permission permission) throws AccessDeniedException {
getACL().checkPermission(permission);
}
public boolean hasPermission(Permission permission) {
return getACL().hasPermission(permission);
}
///// Action methods /////
public String getDisplayName() {
return "My Views";
}
public String getIconFileName() {
return "user.gif";
}
public String getUrlName() {
return "my-views";
}
@Extension
public static class DescriptorImpl extends UserPropertyDescriptor {
@Override
public String getDisplayName() {
return "My Views";
}
@Override
public UserProperty newInstance(User user) {
return new MyViewsProperty();
}
}
@Extension
public static class GlobalAction implements RootAction {
public String getDisplayName() {
return "My Views";
}
public String getIconFileName() {
// do not show when not logged in
if (User.current() == null ) {
return null;
}
return "user.gif";
}
public String getUrlName() {
return "/me/my-views";
}
public HttpResponse doIndex() {
return new HttpRedirect("/me/my-views");
}
}
}
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Tom Huybrechts
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.model;
import hudson.Extension;
import hudson.Util;
import hudson.model.Descriptor.FormException;
import hudson.util.FormValidation;
import java.io.IOException;
import java.util.Collection;
import javax.servlet.ServletException;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
/**
* A view that delegates to another.
* @author Tom Huybrechts
*
*/
public class ProxyView extends View {
private String proxiedViewName;
@DataBoundConstructor
public ProxyView(String name) {
super(name);
if (Hudson.getInstance().getView(name) != null) {
// if this is a valid global view name, let's assume the
// user wants to show it
proxiedViewName = name;
}
}
public View getProxiedView() {
if (proxiedViewName == null) {
// just so we avoid errors just after creation
return Hudson.getInstance().getPrimaryView();
} else {
return Hudson.getInstance().getView(proxiedViewName);
}
}
public String getProxiedViewName() {
return proxiedViewName;
}
public void setProxiedViewName(String proxiedViewName) {
this.proxiedViewName = proxiedViewName;
}
@Override
public Collection<TopLevelItem> getItems() {
return getProxiedView().getItems();
}
@Override
public boolean contains(TopLevelItem item) {
return getProxiedView().contains(item);
}
@Override
public void onJobRenamed(Item item, String oldName, String newName) {
if (oldName.equals(proxiedViewName)) {
proxiedViewName = newName;
}
}
@Override
protected void submit(StaplerRequest req) throws IOException, ServletException, FormException {
String proxiedViewName = req.getSubmittedForm().getString("proxiedViewName");
if (Hudson.getInstance().getView(proxiedViewName) == null) {
throw new FormException("Not an existing global view", "proxiedViewName");
}
this.proxiedViewName = proxiedViewName;
}
@Override
public Item doCreateItem(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
return getProxiedView().doCreateItem(req, rsp);
}
/**
* Fails if a global view with the given name does not exist.
*/
public FormValidation doViewExistsCheck(@QueryParameter String value) {
checkPermission(View.CREATE);
String view = Util.fixEmpty(value);
if(view==null) return FormValidation.ok();
if(Hudson.getInstance().getView(view)!=null)
return FormValidation.ok();
else
return FormValidation.error(String.format("Global view %s does not exist", value));
}
@Extension
public static class DescriptorImpl extends ViewDescriptor {
@Override
public String getDisplayName() {
return "Include a global view";
}
@Override
public boolean isInstantiable() {
// doesn't make sense to add a ProxyView to the global views
return !(Stapler.getCurrentRequest().findAncestorObject(ViewGroup.class) instanceof Hudson);
}
}
}
......@@ -25,6 +25,7 @@ package hudson.model;
import com.thoughtworks.xstream.XStream;
import hudson.CopyOnWrite;
import hudson.Extension;
import hudson.FeedAdapter;
import hudson.Util;
import hudson.XmlFile;
......@@ -38,8 +39,12 @@ import hudson.util.RunList;
import hudson.util.XStream2;
import net.sf.json.JSONObject;
import org.acegisecurity.AccessDeniedException;
import org.acegisecurity.Authentication;
import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerProxy;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
......@@ -60,8 +65,10 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.kohsuke.stapler.StaplerFallback;
/**
* Represents a user.
......@@ -514,4 +521,38 @@ public class User extends AbstractModelObject implements AccessControlled, Savea
public boolean hasPermission(Permission permission) {
return getACL().hasPermission(permission);
}
}
public Object getDynamic(String token) {
for (UserProperty property: getProperties().values()) {
if (property instanceof Action) {
Action a= (Action) property;
if(a.getUrlName().equals(token) || a.getUrlName().equals('/'+token))
return a;
}
}
return null;
}
@Extension
public static class Me implements RootAction, StaplerProxy {
public String getDisplayName() {
return null;
}
public String getIconFileName() {
return null;
}
public String getUrlName() {
return "me";
}
public Object getTarget() {
if (User.current() == null) {
throw new AccessDeniedException("/me is not available when not logged in");
}
return User.current();
}
}}
......@@ -27,7 +27,6 @@ import hudson.ExtensionPoint;
import hudson.Util;
import hudson.Extension;
import hudson.DescriptorExtensionList;
import hudson.Plugin;
import hudson.widgets.Widget;
import hudson.model.Descriptor.FormException;
import static hudson.model.Hudson.checkGoodName;
......@@ -40,7 +39,6 @@ import hudson.security.Permission;
import hudson.security.PermissionGroup;
import hudson.util.DescriptorList;
import hudson.util.RunList;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
......@@ -58,6 +56,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.text.ParseException;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
/**
* Encapsulates the rendering of the list of {@link TopLevelItem}s
......@@ -101,6 +101,11 @@ public abstract class View extends AbstractModelObject implements AccessControll
this.name = name;
}
protected View(String name, ViewGroup owner) {
this.name = name;
this.owner = owner;
}
/**
* Gets all the items in this collection in a read-only view.
*/
......@@ -499,12 +504,13 @@ public abstract class View extends AbstractModelObject implements AccessControll
/**
* Deletes this view.
*/
public synchronized void doDoDelete( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
public synchronized HttpResponse doDoDelete() throws IOException, ServletException {
requirePOST();
checkPermission(DELETE);
owner.deleteView(this);
rsp.sendRedirect2(req.getContextPath()+"/");
return new HttpRedirect("/" + owner.getUrl());
}
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -23,6 +23,8 @@
*/
package hudson.model;
import hudson.security.AccessControlled;
import java.io.IOException;
import java.util.Collection;
......@@ -34,7 +36,7 @@ import java.util.Collection;
* @author Kohsuke Kawaguchi
* @since 1.269
*/
public interface ViewGroup extends Saveable, ModelObject {
public interface ViewGroup extends Saveable, ModelObject, AccessControlled {
/**
* Deletes a view in this group.
*/
......
......@@ -23,21 +23,20 @@
*/
package hudson.security;
import hudson.ExtensionPoint;
import hudson.Extension;
import hudson.DescriptorExtensionList;
import hudson.slaves.Cloud;
import hudson.slaves.RetentionStrategy;
import hudson.Extension;
import hudson.ExtensionPoint;
import hudson.model.AbstractItem;
import hudson.model.AbstractProject;
import hudson.model.Computer;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.model.Hudson;
import hudson.model.Job;
import hudson.model.Node;
import hudson.model.User;
import hudson.model.View;
import hudson.model.Node;
import hudson.model.AbstractProject;
import hudson.slaves.Cloud;
import hudson.util.DescriptorList;
import java.io.Serializable;
......@@ -95,14 +94,14 @@ public abstract class AuthorizationStrategy implements Describable<Authorization
* This can be used as a basis for more fine-grained access control.
*
* <p>
* The default implementation returns {@link #getRootACL()}.
* The default implementation returns the ACL of the ViewGroup.
*
* @since 1.220
*/
public ACL getACL(View item) {
return getRootACL();
return item.getOwner().getACL();
}
/**
* Implementation can choose to provide different ACL for different items.
* This can be used as a basis for more fine-grained access control.
......
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Tom Huybrechts
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="${%Default View}"
description="${%The view selected by default when navigating to the users' private views}">
<f:textbox field="primaryViewName" checkUrl="'${rootURL}/${instance.url}/viewExistsCheck?value='+escape(this.value)+'&amp;exists=true'"/>
</f:entry>
</j:jelly>
\ No newline at end of file
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, id:cactusman, Yahoo! Inc., Tom Huybrechts
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<!--
New View page
TODO remove duplication with Hudson/newView.jelly
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<l:layout norefresh="true" permission="${app.primaryView.CREATE}">
<st:include page="sidepanel.jelly" it="${it.user}"/>
<l:main-panel>
<f:form method="post" action="createView" name="createView">
<f:entry title="${%View name}">
<f:textbox id="name" name="name" checkUrl="'${rootURL}/${it.url}/viewExistsCheck?value='+escape(this.value)+'&amp;exists=false'"
onchange="updateOk(this.form)" onkeyup="updateOk(this.form)" />
<script>
$('name').focus();
</script>
</f:entry>
<j:getStatic var="views" className="hudson.model.View" field="LIST" />
<j:forEach var="d" items="${views}">
<j:if test="${d.isInstantiable()}">
<f:block>
<j:set var="id" value="${h.generateId()}"/>
<input type="radio" name="mode" value="${d.clazz.name}" onchange="updateOk(this.form)" onclick="updateOk(this.form)" id="${id}" />
<st:nbsp/>
<label for="${id}"><b>${d.displayName}</b></label>
</f:block>
<f:entry>
<st:include page="${d.newViewDetailPage}" from="${d}" optional="true"/>
</f:entry>
</j:if>
</j:forEach>
<f:block>
<input type="submit" name="Submit" value="OK" id="ok" style="margin-left:5em" />
</f:block>
</f:form>
<script><![CDATA[
var okButton = makeButton($('ok'),null);
function updateOk(form) {
function state() {
if($('name').value.length==0) return true;
var radio = form.elements['mode'];
if(radio.checked) return false; // if there's only one radiobutton
for(i=0;i<radio.length;i++)
if(radio[i].checked)
return false;
return true;
}
okButton.set('disabled',state(),false);
}
updateOk(okButton.getForm());
]]></script>
</l:main-panel>
</l:layout>
</j:jelly>
\ No newline at end of file
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Tom Huybrechts
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="${%View name}"
description="${%The name of a global view that will be shown.}">
<select class="setting-input" name="proxiedViewName">
<j:forEach var="inst" items="${app.views}">
<f:option selected="${inst.viewName==it.proxiedViewName}" value="${inst.viewName}">${inst.viewName}</f:option>
</j:forEach>
</select>
</f:entry>
<!--
<f:textbox
field="proxiedViewName"
checkUrl="'${rootURL}/${it.url}/viewExistsCheck?value='+escape(this.value)" />
-->
</j:jelly>
\ No newline at end of file
......@@ -32,7 +32,9 @@ THE SOFTWARE.
<l:task icon="images/24x24/up.gif" href="${rootURL}/" title="${%Back to Dashboard}" />
<l:task icon="images/24x24/search.gif" href="${rootURL}/${it.url}/" title="${%Status}" />
<l:task icon="images/24x24/notepad.gif" href="${rootURL}/${it.url}/builds" title="${%Builds}" />
<l:task icon="images/24x24/notepad.gif" href="${rootURL}/${it.url}/my-views" title="${%My Views}" />
<l:task icon="images/24x24/setting.gif" href="${rootURL}/${it.url}/configure" title="${%Configure}" permission="${app.ADMINISTER}"/>
<!-- TODO add all UserProperties that are also actions -->
</l:tasks>
</l:side-panel>
</j:jelly>
\ No newline at end of file
......@@ -33,7 +33,7 @@ THE SOFTWARE.
<j:set var="instance" value="${it}" />
<j:set var="descriptor" value="${it.descriptor}" />
<f:form method="post" action="configSubmit">
<f:form method="post" action="configSubmit" name="viewConfig">
<f:entry title="${%Name}">
<f:textbox name="name" value="${it.viewName}" />
</f:entry>
......
......@@ -28,16 +28,16 @@ THE SOFTWARE.
<d:tag name="viewTabs">
<!-- view tab bar -->
<l:tabBar>
<j:forEach var="v" items="${app.views}">
<j:forEach var="v" items="${it.owner.views}">
<l:tab name="${v.viewName}" active="${v==it}" href="${rootURL}/${v.url}" />
</j:forEach>
<j:if test="${it.hasPermission(it.CREATE)}">
<l:tab name="+" href="${rootURL}/newView" active="false" />
<l:tab name="+" href="${rootURL}/${it.owner.url}newView" active="false" />
</j:if>
</l:tabBar>
</d:tag>
</d:taglib>
<j:when test="${empty(items)}">
<j:when test="${empty(it.items)}">
<j:if test="${!empty(app.items)}">
<local:viewTabs/>
<br/>
......
......@@ -42,7 +42,7 @@ THE SOFTWARE.
<l:task icon="images/24x24/notepad.gif" href="${rootURL}/${it.url}builds" title="${%Build History}"/>
<j:if test="${it.isEditable()}">
<!-- /configure URL on Hudson object is overloaded with Hudson's system config, so always use the explicit name. -->
<l:task icon="images/24x24/gear.gif" href="${rootURL}/view/${it.viewName}/configure" title="${%Edit View}" permission="${it.CONFIGURE}" />
<l:task icon="images/24x24/gear.gif" href="${rootURL}/${it.url}configure" title="${%Edit View}" permission="${it.CONFIGURE}" />
</j:if>
<j:if test="${!it.isDefault()}">
<!-- this is ugly, but Hudson delegates the rendering of its pages to its primary view -->
......
......@@ -123,6 +123,7 @@ THE SOFTWARE.
</plugin>
<plugin>
<artifactId>maven-remote-resources-plugin</artifactId>
<version>1.0</version>
<executions>
<execution>
<goals>
......
......@@ -42,13 +42,15 @@ THE SOFTWARE.
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- override with -DforkMode=pertest if you have "unable to create thread problems" on Windows -->
<!-- override with -DforkMode=pertest if you have "unable to create thread problems" on Windows
-->
<forkMode>once</forkMode>
<!--<reportFormat>plain</reportFormat>-->
<redirectTestOutputToFile>true</redirectTestOutputToFile>
<systemProperties>
<property>
<!-- use AntClassLoader that supports predictable file handle release -->
<!-- use AntClassLoader that supports predictable file handle release
-->
<name>hudson.ClassicPluginStrategy.useAntClassLoader</name>
<value>true</value>
</property>
......@@ -120,7 +122,14 @@ THE SOFTWARE.
<groupId>org.jvnet.hudson</groupId>
<artifactId>htmlunit</artifactId>
<version>2.2-hudson-10</version>
</dependency>
<exclusions>
<exclusion>
<!-- hides JDK DOM classes in Eclipse -->
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency><!-- temporary, until we bump to new htmlunit -->
<groupId>org.jvnet.hudson</groupId>
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -24,9 +24,15 @@
package hudson.model;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlRadioButtonInput;
import org.jvnet.hudson.test.Email;
import org.jvnet.hudson.test.HudsonTestCase;
import org.w3c.dom.Text;
import static hudson.model.Messages.Hudson_ViewName;
/**
* @author Kohsuke Kawaguchi
......@@ -53,4 +59,37 @@ public class ViewTest extends HudsonTestCase {
assertEquals(400,e.getStatusCode());
}
}
public void testPrivateView() throws Exception {
createFreeStyleProject("project1");
User user = User.get("me", true); // create user
WebClient wc = new WebClient();
HtmlPage userPage = wc.goTo("/user/me");
HtmlAnchor privateViewsLink = userPage.getFirstAnchorByText("My Views");
assertNotNull("My Views link not available", privateViewsLink);
HtmlPage privateViewsPage = (HtmlPage) privateViewsLink.click();
Text viewLabel = (Text) privateViewsPage.getFirstByXPath("//table[@id='viewList']//td[@class='active']/text()");
assertTrue("'All' view should be selected", viewLabel.getTextContent().contains(Hudson_ViewName()));
View listView = new ListView("listView", hudson);
hudson.addView(listView);
HtmlPage newViewPage = wc.goTo("/user/me/my-views/newView");
HtmlForm form = newViewPage.getFormByName("createView");
form.getInputByName("name").setValueAttribute("proxy-view");
((HtmlRadioButtonInput) form.getInputByValue("hudson.model.ProxyView")).setChecked(true);
HtmlPage proxyViewConfigurePage = submit(form);
View proxyView = user.getProperty(MyViewsProperty.class).getView("proxy-view");
assertNotNull(proxyView);
form = proxyViewConfigurePage.getFormByName("viewConfig");
form.getSelectByName("proxiedViewName").setSelectedAttribute("listView", true);
submit(form);
assertTrue(proxyView instanceof ProxyView);
assertEquals(((ProxyView) proxyView).getProxiedViewName(), "listView");
assertEquals(((ProxyView) proxyView).getProxiedView(), listView);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册